Intall and configure CoreDNS
This article will show you how to set up a CoreDNS server, allowing internal DNS names resolved by the CoreDNS server to be used to access network resources through your LAN.
CoreDNS©
This article will show you how to set up a CoreDNS server, allowing internal DNS names resolved by the CoreDNS server to be used to access network resources through your LAN. We’ll follow these steps:
- Disable Stub Resolver
- Install CoreDNS, via Docker or via Deb.
- Configure CoreDNS
Disable stub resolver
The first step to running CoreDNS on the Hub is to turn off any existing DNS server on the Hub (to free up port 53
).
If you’re running systemd
on the server, you’ll need to disable systemd’s own stub resolver. Edit the /etc/systemd/resolved.conf
file, and set its DNSStubListener
field to no:
# file: "/etc/systemd/resolved.conf"
DNSStubListener=no
Then apply your changes by running the following command:
sudo systemctl restart systemd-resolved
If you want the Hub itself to use CoreDNS
for its own DNS lookups, also modify the DNS
and Domains
settings of the /etc/systemd/resolved.conf
file to the following:
# file: "/etc/resolv.conf"
DNS=127.0.0.1
Domains=~.
But make sure to change these two settings only after you’ve finished installing and configuring CoreDNS, and have verified that it’s resolving external domain names successfully.
Install CoreDNS
CoreDNS runs as single executable. You can simply download the latest release archive from the CoreDNS releases page on GitHub, extract the archive (which contains a single executable file), set permissions on the executable, and run it. Alternatively, you can pull the latest CoreDNS Docker image to run CoreDNS as a Docker container; or you can build a deb or RPM file to install CoreDNS as a systemd service on Debian- or Fedora-based Linux distributions.
With DOCKER
You can launch CoreDNS via Docker Compose using the following docker-compose.yml
file:
# file: "/srv/coredns/docker-compose.yml"
coredns:
image: coredns/coredns
command: -conf /etc/coredns/Corefile
ports:
- 53:53/udp
- 53:53/tcp
volumes:
- ./conf:/etc/coredns
For example, create a /srv/coredns/
directory on the Hub, and place the above docker-compose.yml
file in it. Then create a conf/
subdirectory of /srv/coredns/
, and place the following Corefile into it:
# file: "/srv/coredns/conf/Corefile"
. {
whoami
log
}
Start up Docker Compose from the /srv/coredns
directory:
cd /srv/coredns
sudo docker-compose up
Pulling coredns (coredns/coredns:)...
latest: Pulling from coredns/coredns
9731739b2823: Pull complete
4dfb45b72a09: Pull complete
Digest: sha256:017727efcfeb7d053af68e51436ce8e65edbc6ca573720afb4f79c8594036955
Status: Downloaded newer image for coredns/coredns:latest
Creating coredns_coredns_1 ... done
Attaching to coredns_coredns_1
coredns_1 | .:53
coredns_1 | CoreDNS-1.10.0
coredns_1 | linux/arm64, go1.19.1, 596a9f9
Test it out by running the following command on the server :
dig @127.0.0.1 example.com
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22048
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 9b3888d9496d270e (echoed)
;; QUESTION SECTION:
;example.com. IN A
;; ADDITIONAL SECTION:
example.com. 0 IN A 172.17.0.1
_udp.example.com. 0 IN SRV 0 0 41596 .
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:13:03 UTC 2023
;; MSG SIZE rcvd: 114
Because we’ve configured CoreDNS with the whoami plugin (for testing purposes), the response in the “additional section” will show the IP address (172.17.0.1
) and UDP port (41596
) from which you queried CoreDNS — not the actual DNS information of example.com
.
You’ll see a corresponding log entry for the query in CoreDNS’s output:
coredns_1 | [INFO] 172.17.0.1:41596 - 22048 "A IN example.com. udp 52 false 1232" NOERROR qr,aa,rd 91 0.000098518s
With .deb
On Debian (or a Debian-based Linux distribution like Ubuntu), you can install CoreDNS through a deb package. This will set up a convenient systemd service for you.
First, clone the CoreDNS Deployment repo:
git clone https://github.com/coredns/deployment.git coredns/deployment
Cloning into 'coredns/deployment'...
remote: Enumerating objects: 970, done.
remote: Counting objects: 100% (111/111), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 970 (delta 57), reused 70 (delta 35), pack-reused 859
Receiving objects: 100% (970/970), 270.48 KiB | 8.20 MiB/s, done.
Resolving deltas: 100% (525/525), done.
Enter the cloned repo, and run the following command to build a .deb of the latest CoreDNS release:
$ cd coredns/deployment
$ dpkg-buildpackage -us -uc -b
Command 'dpkg-buildpackage' not found, but can be installed with:
sudo apt install dpkg-dev
You may have to install the following dependencies to successfully build the deb:
$ sudo apt install dpkg-dev debhelper jq
Reading package lists... Done
...
$ dpkg-buildpackage -us -uc -b
dpkg-buildpackage: info: source package coredns
...
After the build command succeeds, navigate up a directory, and you should have a brand new CoreDNS deb package:
$ cd ..
ls -1
coredns_0-0_arm64.buildinfo
coredns_0-0_arm64.changes
coredns_1.10.0-0~22.040_arm64.deb
deployment
Install the deb with the following command:
$ sudo dpkg -i coredns*.deb
Selecting previously unselected package coredns.
(Reading database ... 71988 files and directories currently installed.)
Preparing to unpack coredns_1.10.0-0~22.040_arm64.deb ...
Unpacking coredns (1.10.0-0~22.040) ...
Setting up coredns (1.10.0-0~22.040) ...
Created symlink /etc/systemd/system/multi-user.target.wants/coredns.service → /lib/systemd/system/coredns.service.
Processing triggers for man-db (2.10.2-1) ...
This will install CoreDNS as a systemd service, listening on UDP and TCP port 53
. By default, it will be configured with a test Corefile similar to the one we used for the Docker container above:
$ cat /etc/coredns/Corefile
# Default Corefile, see https://coredns.io for more information.
# Answer every below the root, with the whoami plugin. Log all queries
# and errors on standard output.
. {
whoami # coredns.io/plugins/whoami
log # coredns.io/plugins/log
errors # coredns.io/plugins/errors
}
And we’ll see a similar result as with the Docker container above if we run a test query against it:
$ dig @127.0.0.1 example.com
...
;; ADDITIONAL SECTION:
example.com. 0 IN A 127.0.0.1
_udp.example.com. 0 IN SRV 0 0 39581 .
...
And we’ll see similar log output from journald for it:
$ journalctl -u coredns.service
Jan 19 20:33:57 hub systemd[1]: Started CoreDNS DNS server.
Jan 19 20:33:57 hub coredns[42762]: .:53
Jan 19 20:33:57 hub coredns[42762]: CoreDNS-1.10.0
Jan 19 20:33:57 hub coredns[42762]: linux/arm64, go1.19.1, 596a9f9
Jan 19 20:37:01 hub coredns[42762]: [INFO] 127.0.0.1:39581 - 58758 "A IN example.com. udp 52 false 1232" NOERROR qr,aa,rd 91 0.024847392s
Configure CoreDNS
Now we can update the test Corefile
(at /srv/coredns/conf/Corefile
if you installed Via Docker, or /etc/coredns/Corefile
if you installed Via .deb) with some useful configuration for our internal domains.
Via inline hosts
The simplest way to have CoreDNS resolve our internal DNS names is to list them out with the traditional /etc/hosts
file format inside the Corefile itself. For example, we can list the three DNS names we want to resolve (chat.wg.corp
, printer.ny.corp
, and files.eng.corp
) and their respective IP addresses directly in our Corefile
:
# file: "/etc/coredns/Corefile"
. {
hosts {
10.0.0.11 chat.wg.corp
192.168.200.22 printer.ny.corp
10.10.10.43 files.eng.corp
}
errors
}
If we update our Corefile
to the above and restart CoreDNS, CoreDNS can now resolve those three DNS names — but only those three DNS names:
$ dig +short @127.0.0.1 chat.wg.corp
10.0.0.11
$ dig +short @127.0.0.1 printer.ny.corp
192.168.200.22
$ dig +short @127.0.0.1 files.eng.corp
10.10.10.43
$ dig +short @127.0.0.1 repo.eng.corp
$ dig +short @127.0.0.1 example.com
What we really want to do for all the domains not listed in our Corefile
is forward all eng.corp
queries to the Eng DNS server at 10.10.10.44
, and forward all other queries to a public DNS server like Quad9. We can do that by adjusting our Corefile to add a fallthrough setting to the hosts plugin, plus add two forward plugins — one for our Eng DNS resolver, and the other for the Quad9 resolvers:
# file: "/etc/coredns/Corefile"
. {
hosts {
10.0.0.11 chat.wg.corp
192.168.200.22 printer.ny.corp
fallthrough
}
forward eng.corp 10.10.10.44
forward . 9.9.9.9 149.112.112.112
errors
}
Restart CoreDNS, and now we can resolve both the hardcoded DNS names from our hosts
plugin, as well as any DNS name from our private Eng DNS server (like repo.eng.corp
) — and all public domains (like example.com
), too:
$ dig +short @127.0.0.1 chat.wg.corp
10.0.0.11
$ dig +short @127.0.0.1 printer.ny.corp
192.168.200.22
$ dig +short @127.0.0.1 files.eng.corp
10.10.10.43
$ dig +short @127.0.0.1 repo.eng.corp
10.10.10.49
$ dig +short @127.0.0.1 example.com
93.184.216.34
You can selectively “overwrite” public DNS entries with CoreDNS host files — just like you can with the /etc/hosts
file on your local computer. For example, with the following Corefile, we could make www.example.com
resolve to 10.0.0.11
:
# file: "/etc/coredns/Corefile"
. {
hosts {
10.0.0.11 www.example.com
fallthrough
}
forward . 9.9.9.9 149.112.112.112
errors
}
With the above configuration, when using CoreDNS as our DNS resolver (like it will be when a client’s WireGuard interface is up), www.example.com
will resolve to an internal WireGuard IP address of 10.0.0.11
; and when not using CoreDNS (like it will be when the same client’s WireGuard interface is shut down), www.example.com
will resolve to its normal public IP address:
$ dig +short @127.0.0.1 www.example.com
10.0.0.11
$ dig +short @9.9.9.9 www.example.com
93.184.216.34
Via single hosts file
We can also pull out separate domains into separate host files, for our own administrative convenience. For example, we could list our wg.corp
DNS entries in an /etc/coredns/hosts/wg.corp
file, and our ny.corp DNS entries in an /etc/coredns/hosts/ny.corp
file:
# file: "/etc/coredns/hosts/wg.corp"
10.0.0.11 chat.wg.corp
# file: "/etc/coredns/hosts/ny.corp"
192.168.200.22 printer.ny.corp
The hosts plugin can only be used once per top-level block in a Corefile
however, so we now have to structure our Corefile
with separate top-level blocks for our wg.corp
and ny.corp
domains (and if we do that, we might as well also pull our eng.corp
domain into its own separate top-level block, as well):
# file: "/etc/coredns/Corefile"
wg.corp {
hosts /etc/coredns/hosts/wg.corp {
reload 60s
}
errors
}
ny.corp {
hosts /etc/coredns/hosts/ny.corp {
reload 60s
}
errors
}
eng.corp {
forward . 10.10.10.44
errors
}
. {
forward . 9.9.9.9 149.112.112.112
errors
}
Via separate zonz files
We can also use traditional RFC 1035-style zone files to define our custom DNS entries. This allows us to take full advantage of DNS features, like using CNAME
or SRV
records, or applying different TTL (Time To Live) values to different DNS entries. For example, we could create the following zone file for the wg.corp
domain:
# file: "/etc/coredns/zones/db.wg.corp"
$ORIGIN wg.corp.
$TTL 1h
@ IN SOA (
ns ; primary nameserver
. ; zone-admin email
1 ; serial number
24h ; refresh interval
2h ; retry interval
1000h ; expire interval
10m ; negative TTL
)
IN NS ns
chat IN A 10.0.0.11
hub IN A 10.0.0.3
ns 5m IN A 10.0.0.3
_irc._tcp IN SRV 10 10 6667 chat.wg.corp.
# file: "/etc/coredns/zones/db.ny.corp"
$ORIGIN ny.corp.
$TTL 1h
@ IN SOA (
ns.wg.corp. ; primary nameserver
. ; zone-admin email
1 ; serial number
24h ; refresh interval
2h ; retry interval
1000h ; expire interval
10m ; negative TTL
)
IN NS ns.wg.corp.
canon650i IN A 192.168.200.22
printer IN CNAME canon650i
A few important things to remember when editing zone files:
- CoreDNS requires each zone to have a valid SOA record (even though the only SOA fields that really matter for our use case are the serial number and negative TTL).
- Increment the serial number of each zone file every time you update it.
- Include a trailing . when specifying fully-qualified domain names.
- Keep your TTLs low until you’ve tested everything out.
If we placed the two above files in the /etc/coredns/zones
directory as db.wg.corp
and db.ny.corp
, we can apply them via CoreDNS’s auto plugin:
# file: "/etc/coredns/Corefile"
. {
auto {
directory /etc/coredns/zones
reload 60s
}
forward eng.corp 10.10.10.44
forward . 9.9.9.9 149.112.112.112
errors
}
CoreDNS’s auto
plugin by default requires zone files to be named with a pattern like db.{origin}
, where {origin}
is the zone’s domain name — like db.wg.corp
for a zone file where wg.corp is the domain name. If you want to name your zone files differently, see the auto plugin’s documentation for details on how to set up a custom file pattern; or use the file plugin instead of the auto
plugin to specify each zone file individually.
After restarting, we should be able to lookup our IRC (Internet Relay Chat) SRV
record:
$ dig @127.0.0.1 SRV _irc._tcp.wg.corp
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 SRV _irc._tcp.wg.corp
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60358
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 50115fc0e15cc095 (echoed)
;; QUESTION SECTION:
;_irc._tcp.wg.corp. IN SRV
;; ANSWER SECTION:
_irc._tcp.wg.corp. 3600 IN SRV 10 10 6667 chat.wg.corp.
;; AUTHORITY SECTION:
wg.corp. 3600 IN NS ns.wg.corp.
;; ADDITIONAL SECTION:
chat.wg.corp. 3600 IN A 10.0.0.11
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:20:38 UTC 2023
;; MSG SIZE rcvd: 166
As well as the CNAME
record for the NY Office Printer:
$ dig @127.0.0.1 printer.ny.corp
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @127.0.0.1 printer.ny.corp
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29728
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 71a27f43c8748c85 (echoed)
;; QUESTION SECTION:
;printer.ny.corp. IN A
;; ANSWER SECTION:
printer.ny.corp. 3600 IN CNAME canon650i.ny.corp.
canon650i.ny.corp. 3600 IN A 192.168.200.22
;; AUTHORITY SECTION:
ny.corp. 3600 IN NS ns.wg.corp.
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Jan 19 20:20:49 UTC 2023
;; MSG SIZE rcvd: 166