“It’s always DNS” is a famous meme among network people. Name resolution is technically quite simple. It’s “just” translating a hostname like jan.wildeboer.net to an IP address. What could possibly go wrong? I am a radical optimist and detail-obsessed knowledge collector, so I decided to find out. As part of my goal to make my home network a little island of Digital Sovereignty, meaning that everything at home should JustWork™, even with no working internet connection, a DNS server is needed.
Based on and extended from my gist Bind on Fedora 42 as DNS server.
I admit, I have a lot of experience with DNS and BIND. But I still consider myself to be merely on the GoodEnough™ side of things. I know how to get DNS configured for my domains. And I want you to feel fearless too. The best place to fail with DNS is the network at home. It limits the impact :)
So read this blog post either as report or as a HOWTO. Both ways can be fun!
In my homelab I have a Raspberry Pi 4 that runs infrastructure services. DNS is one of them, my private CA (Certificate Authority) another. The CA runs as a container on Podman. For DNS I use Bind. It thus has to serve 3 networks:
192.168.1.0/24 My home IPv4 network
My home IPv4 network 172.16.0.0/16 IPv4 Network on the second ethernet ports of my homelab servers
IPv4 Network on the second ethernet ports of my homelab servers 10.88.0.0/16 The (virtual) podman network
It uses my Fritz box (7490) as forwarder, so I can resolve all hosts, including the DHCP entries that the Fritz Box hands out under its default local domain name fritz.box . For my homelab however, I use the homelab.jhw domain name. That’s what the Bind DNS server has to take care of.
WARNING
I really should use the official .internal TLD (Top Level Domain) for my homelab network, but I decided against it. This introduces the risk of name resolution problems, should someone offer a public .jhw TLD in future. It’s a risk I am willing to accept in exchange for using a 3 letter TLD at home. Don’t be like me! Use .internal instead. With that out of the way, let’s continue.
What we (well, I) have
Let’s gather what I have in my home network.
inf01.homelab.jhw at 192.168.1.10 : A Raspberry Pi 4 4GB, running Fedora 42 and podman with my Certificate Authority as a container that should be reachable as ca.homelab.jhw . See Be the LetsEncrypt in your homelab with step-ca for more details.
at : A Raspberry Pi 4 4GB, running Fedora 42 and podman with my Certificate Authority as a container that should be reachable as . See Be the LetsEncrypt in your homelab with step-ca for more details. 3 ThinkCentre Tiny PCs in the homelab.jhw zone, called hl01 ( 192.168.1.11 ), hl02 ( 192.168.1.12 ) and hl03 ( 192.168.1.13 ), running RHEL10 (Red Hat Enterprise Linux)
zone, called hl01 ( ), hl02 ( ) and hl03 ( ), running RHEL10 (Red Hat Enterprise Linux) A Fritz Box 7490 at 192.168.1.254
Let’s install BIND on inf01
We need to do two things. Install BIND and some utilities on inf01 and open the firewall for DNS traffic.
dnf install bind bind-utils firewall-cmd --add-service=dns --permanent
That was easy enough :)
Configure BIND
To run BIND in the correct way, we need to work on 3 configuration files.
/etc/named.conf The main configuration file where we tell BIND on which networks it should listen and what zones it will serve.
The main configuration file where we tell BIND on which networks it should listen and what zones it will serve. /var/named/forward.homelab.jhw The forward zone file that maps hostnames in the homelab.jhw domain to IP addresses on my home network
The forward zone file that maps hostnames in the domain to IP addresses on my home network /var/named/reverse.homelab.jhw The reverse zone for the 192.168.1.0/24 network range, that looks a bit confusing, that does the opposite. It maps IP addresses to hostnames.
The reverse zone for the network range, that looks a bit confusing, that does the opposite. It maps IP addresses to hostnames. /var/named/reverse2.homelab.jhw The second reverse zone for the 172.16.0.0/16 network range.
Let’s start with /etc/named.conf .
// // named.conf // options { listen-on port 53 { 127.0.0.1; 192.168.1.10; 172.16.1.10; 10.88.0.1; }; listen-on-v6 port 53 { ::1; fdda:a4da:69a5:0:2783:8c26:b2f1:a6f7; }; allow-query { localhost; 192.168.1.0/24; 172.16.0.0/16; 10.88.0.0/16; }; directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; memstatistics-file "/var/named/data/named_mem_stats.txt"; secroots-file "/var/named/data/named.secroots"; recursing-file "/var/named/data/named.recursing"; forwarders { 192.168.1.254; }; recursion yes; dnssec-validation no; managed-keys-directory "/var/named/dynamic"; geoip-directory "/usr/share/GeoIP"; pid-file "/run/named/named.pid"; session-keyfile "/run/named/session.key"; /* https://fedoraproject.org/wiki/Changes/CryptoPolicy */ include "/etc/crypto-policies/back-ends/bind.config"; }; logging { channel default_debug { file "data/named.run"; severity dynamic; }; }; zone "." IN { type hint; file "named.ca"; }; zone "homelab.jhw" IN { type master; file "forward.homelab.jhw"; allow-update { none; }; allow-query { any; }; }; zone "1.168.192.in-addr.arpa" IN { type master; file "reverse.homelab.jhw"; allow-update { none; }; allow-query { any; }; }; zone "16.172.in-addr.arpa" IN { type master; file "reverse2.homelab.jhw"; allow-update { none; }; allow-query { any; }; }; include "/etc/named.rfc1912.zones"; include "/etc/named.root.key";
The first block declare the general options. Yes, it looks complicated and it is, but let’s walk you through every relevant line (the lines not mentioned are default entries that don’t need to be changed).
listen-on port 53 { 127.0.0.1; 192.168.1.10; 172.16.1.10; 10.88.0.1; }; listen-on-v6 port 53 { ::1; fdda:a4da:69a5:0:2783:8c26:b2f1:a6f7; }; allow-query { localhost; 192.168.1.0/24; 172.16.0.0/16; 10.88.0.0/16; };
Here we tell BIND that it should listen for queries on port 53 on localhost , 192.168.1.10 , the IPv4 address in my hoem network, 172.16.1.10 , the second IPv4 address configured and 10.88.0.1 , the virtual IPv4 address the Raspberry uses to bridge to the local podman containers.
The second line does the same for IPv6, but that is something we will discuss in Part 2.
The third line tells BIND from whom to accept queries. Essentially from everyone on the three IPv4 networks we are listening to.
directory "/var/named";
This is the directory where BIND will look for its zone files, that we will define later.
forwarders { 192.168.1.254; }; recursion yes;
Now what if someone asks for a hostname that is outside of homelab.jhw ? In that case we tell BIND to forward that question to 192.168.1.254 , our Fritz Box. We will allow recursion and cache results we get from our Fritz box to avoid unneeded traffic.
dnssec-validation no;
Our simple setup will not bother with DNSSEC at the moment. Maybe we will have a Part 3 for that.
OK. That was the options part. We will ignore the logging part and the zone "." IN block.
Next (and finally) we define three zone entries (and zone files). A forward zone called homelab.jhw for our domain and two reverse zones for the IP addresses in the 192.168.1.0/24 range called 1.168.192.in-addr.arpa . Yep. That’s 192.168.1 reversed. 1.168.192. That’s why it’s called the reverse zone ;) We also have 16.172.in-addr.arpa for the 172.16.0.0/16 range. Let’s look at them.
zone "homelab.jhw" IN { type master; file "forward.homelab.jhw"; allow-update { none; }; allow-query { any; }; };
It’s a zone, all right. It’s the master for this zone, meaning that this DNS server will be the Source of Truth to answer all queries for the homelab.jhw hostnames.
The exact mapping of all hostnames to IP addresses is in a file called forward.homelab.jhw in the directory /var/named . Remember how we defined that path at the beginning in the options part? Great! We also tell BIND that we do not allow dynamic updates for this zone, meaning that what’s in the file is all we will look at. Finally we tell BIND that any machine in the network is allowed to ask for a reply.
zone "1.168.192.in-addr.arpa" IN { type master; file "reverse.homelab.jhw"; allow-update { none; }; allow-query { any; }; }; zone "16.172.in-addr.arpa" IN { type master; file "reverse2.homelab.jhw"; allow-update { none; }; allow-query { any; }; };
The reverse zones with the weird looking zone names are almost the same, except that we define these in two files called reverse.homelab.jhw for the reverse lookup of the 192.168.1.0/24 range and reverse2.homelab.jhw for the 172.16.0.0/16 range. Why these zones have weird names will be explained later.
So now we go to the zone files!
Forward zone for homelab.jhw
The forward zone resolves names to IP addresses using A records (and other types like TXT, CAA and many more exist, but we won’t cover that in this post). It also contains CNAME entries, if you have services on one machine that should be reachable via more than one hostnames. In my homelab the CA (Certificate Authority) server is a container that runs on inf01.homelab.jhw , but should be reachable as ca.homelab.jhw in the home network. The CNAME entry does exactly that. It tells clients that when they want to talk to ca.homelab.jhw they can. By actually talking to inf01.homelab.jhw .
Now here is the big, important lessen for zone files. They have a serial number. Which MUST be incremented with every change. If you don’t, weird things WILL happen. So:
NEVER FORGET TO INCREASE THE SERIAL WITH EVERY CHANGE TO A ZONE FILE. OR RISK DNS HELL.
/var/named/forward.homelab.jhw
$TTL 3600 @ IN SOA inf01.homelab.jhw. root.homelab.jhw. ( 2025082706 ;Serial 3600 ;Refresh 1800 ;Retry 604800 ;Expire 86400 ;Minimum TTL ) @ IN NS inf01.homelab.jhw. @ IN A 192.168.1.10 inf01 IN A 192.168.1.10 hl01 IN A 192.168.1.11 hl02 IN A 192.168.1.12 hl03 IN A 192.168.1.13 ca IN CNAME inf01.homelab.jhw. inf01-m IN A 172.16.1.10 hl01-m IN A 172.16.1.11 hl02-m IN A 172.16.1.12 hl03-m IN A 172.16.1.13
Again, let’s go through this.
$TTL 3600
The default Time To Live (TTL) for DNS entries is set at 3600 seconds. That’s 1 hour. This means that when a machine in the network gets a DNS reply, it will not ask again for the same thing until the TTL has passed.
@ IN SOA inf01.homelab.jhw. root.homelab.jhw. ( 2025082706 ;Serial 3600 ;Refresh 1800 ;Retry 604800 ;Expire 86400 ;Minimum TTL )
The Start Of Authority (SOA) block. Here we say which DNS server is the owner of this domain. It’s inf01.homelab.jhw. (yes, that dot at the end is REALLY important). The root.homelab.jhw actually means [email protected] and is the email address responsible for this domain. Don’t think to much about why and what :)
@ IN NS inf01.homelab.jhw. @ IN A 192.168.1.10
The first “real” DNS entries! They are special, as the @ indicates, which means they represent the domain itself. We first define the nameserver (again? yes, don*‘t ask) as NS record. And right after that we define the A record as the IP address 192.168.1.10 .
Did you notice that . at the end of inf01.homelab.jhw. ? That’s another VERY important thing. The TL;DR is that this final . tells DNS to stop doing fancy recursion and lookups. Just look for the hostname `inf01.homelab.jhw. Period. (pun intended). Don’t care too much about this. Just remember:
EVERY HOSTNAME RECORD ENDS WITH A . YOU WILL FORGET THIS. YOU WILL FIX THIS.
inf01 IN A 192.168.1.10 hl01 IN A 192.168.1.11 hl02 IN A 192.168.1.12 hl03 IN A 192.168.1.13
Here come the A records for 192.168.1.0/24 ! We finally get to map hostnames to IP addresses. For real! It now is quite self-explanatory, isn’t it? The hostname gets an A record that is the IP address in my local network. And as these are IP addresses, no . is needed at the end.
ca IN CNAME inf01.homelab.jhw.
And here is the CNAME record. Which maps the hostname ca.homelab.jhw to the Canonical NAME (CNAME) inf01.homelab.jhw. . This is a hostname at the end! So it needs the . Period :)
inf01-m IN A 172.16.1.10 hl01-m IN A 172.16.1.11 hl02-m IN A 172.16.1.12 hl03-m IN A 172.16.1.13
And here we create another set of A records for the same machines, but this time in the 172.16.0.0/16 range. This range is used for management stuff, hence the -m .
And that’s the gist of it. If you add a new machine to your network, configure it with an IP address (statically or with DHCP) and add it as an A record to the forward zone. Increment the serial and tell DNS to read the updated zone with systemctl reload named . Done.
Reverse zones for 192.168.1.0/24 and 172.16.0.0/16
The reverse zone maps IP addresses to hostnames. Often called the PTR or pointer record. You have to make sure that the entries here are synced to the forward zone.
NEVER FORGET TO INCREASE THE SERIAL WITH EVERY CHANGE TO A ZONE FILE. Or risk DNS hell.
Here is the reverse zone for the 192.168.1.0/24 range.
/var/named/reverse.homelab.jhw
$TTL 3600 @ IN SOA inf01.homelab.jhw. root.homelab.jhw. ( 2025082601 ;Serial 3600 ;Refresh 1800 ;Retry 604800 ;Expire 86400 ;Minimum TTL ) @ IN NS inf01.homelab.jhw. @ IN PTR homelab.jhw. 10 IN PTR inf01.homelab.jhw. 11 IN PTR hl01.homelab.jhw. 12 IN PTR hl02.homelab.jhw. 13 IN PTR hl03.homelab.jhw.
As this is more or less the same but the other way round, I will not go through everything but instead explain the differences. It’s the reverse zone, so now we have PTR (pointer) entries that map an IPv4 address in the 192.168.1.0/24 range to hostnames. WITH A DOT AT THE END. DO NOT FORGET THE DOT!
As this is a /24 block, we only need to set the last digit of the IPv4 address.
You might wonder, where is ca here? Well, it’s CNAME is info1.homelab.jhw and that already is in this reverse zone. That is good enough. No separate entry needed.
We also need the reverse zone for the 172.16.0.0/16 range:
/var/named/reverse2.homelab.jhw
$TTL 3600 @ IN SOA inf01.homelab.jhw. root.homelab.jhw. ( 2025082901 ;Serial 3600 ;Refresh 1800 ;Retry 604800 ;Expire 86400 ;Minimum TTL ) @ IN NS inf01.homelab.jhw. @ IN PTR homelab.jhw. 10.1 IN PTR inf01-m.homelab.jhw. 11.1 IN PTR hl01-m.homelab.jhw. 12.1 IN PTR hl02-m.homelab.jhw. 13.1 IN PTR hl03-m.homelab.jhw.
Looks deceivingly similar. But there is a big difference. This is a /16 network, so we have to define the last two parts of the IPv4 address. And as it is a reverse zone file, yep, we have to reverse it. So now we need 10.1 to define the entry for 172.16.1.10 , which is the hostname inf01-m.homelab.jhw . WITH THE DOT AT THE END. AND DID YOU UPDATE THE SERIAL? :)
Phew. That’s the config done!
A final check with the named-checkconf command, which should say nothing when all files are OK. If not, it will tell you what is wrong so you get the chance to fix stuff. You did add all the . at the end of hostnames and you did update the serial of that zone file after you made changes, yes?
Start Bind
The only thing remaining is to start BIND. And persist it as a service, so it starts after every boot. It’s DNS. It must always be available.
systemctl enable named systemctl start named
You most likely will make typos in your config. So do check with named-checkconf and systemctl status named and journalctl -u named . If something breaks, read this whole entry again. Find that missing . in a zone file. Increment the serial that you forgot to do. You will get there. Don’t give up!
Result
Machines, containers etc can now be resolved in my home network. All with mow own DNS! Yay!
% nslookup jhwfritz.fritz.box Server: 192.168.1.10 Address: 192.168.1.10#53 Non-authoritative answer: Name: jhwfritz.fritz.box Address: 192.168.1.254 % nslookup ca.homelab.jhw Server: 192.168.1.10 Address: 192.168.1.10#53 ca.homelab.jhw canonical name = inf01.homelab.jhw. Name: inf01.homelab.jhw Address: 192.168.1.10
And now you should be able to ping the machines with their hostname. ssh into them. Get certificates with the CA that runs in the podman container. Life is good!
I hope you enjoyed this post and could learn something new! Feel free to comment or send corrections vie the Toot linked below that collects the comments!