I recently gave myself a simple networking task:
Set up a local DNS resolver and add custom domains.
Before jumping into Unbound and configuration files, it's worth understanding what we're actually building.
If you've used the internet, you've used DNS constantly — usually without realizing it.
What Is DNS?
DNS (Domain Name System) is the system that translates human-readable domain names into IP addresses.
Computers communicate over networks using IP addresses.
For example, a server might have an address like:
142.250.151.100
Humans, however, don't want to remember IP addresses for every website they visit.
We'd much rather type:
google.com
DNS provides the mapping between the two:
google.com
│
│ DNS
▼
142.250.151.100
You can think of DNS somewhat like the contacts app on your phone.
You don't memorize:
+234 801 234 5678
You save:
John
and your phone knows which number belongs to John.
DNS does something similar:
google.com → 142.250.x.x
github.com → 140.82.x.x
This means when you enter:
https://google.com
your computer first needs to answer an important question:
What IP address should I connect to for
google.com?
That's where a DNS resolver comes in.
What Is a DNS Resolver?
A DNS resolver is the component responsible for taking your DNS question and finding an answer.
For example, your browser wants to visit:
google.com
It needs an IP address, so a simplified version of what happens looks like this:
Browser
│
│ "What is the IP for google.com?"
▼
DNS Resolver
│
│ finds the answer
▼
142.250.151.100
│
▼
Browser connects to server
Usually, you don't manually choose the resolver every time you browse the internet.
Your computer receives DNS configuration from your network, often through your router.
Your ISP may provide the DNS server, or you might use a public resolver such as Google's or Cloudflare's.
For example:
8.8.8.8 → Google Public DNS
1.1.1.1 → Cloudflare DNS
So DNS is the overall naming system, while a DNS resolver is one of the components that helps you find answers within that system.
That's an important distinction:
DNS
│
└── The overall system for translating names into network information
DNS Resolver
│
└── A service that receives DNS questions and finds/returns answers
Why Run My Own DNS Resolver?
This was the part that made the project interesting.
Instead of relying entirely on whatever DNS resolver my network gives me, I can run one on my own computer.
For example:
My Computer
│
│ google.com?
▼
My DNS Resolver
│
▼
142.250.x.x
But there's another benefit.
Because I control the resolver, I can teach it names that don't exist on the public internet.
For example, I can create:
myserver.home.arpa
and decide:
myserver.home.arpa → 127.0.0.1
Or:
website.home.arpa → 127.0.0.1
Nobody needs to register these domains with a public domain registrar.
My resolver knows about them because I told it about them.
So our resolver will eventually be able to handle two types of questions:
My DNS Resolver
│
┌───────────┴───────────┐
│ │
▼ ▼
Public domain My domain
google.com website.home.arpa
│ │
▼ ▼
Ask upstream DNS Local record
│ │
▼ ▼
142.250.x.x 127.0.0.1
That's what we're going to build.
What We're Building
We'll use Unbound, a DNS resolver, and run it locally on Fedora.
By the end, we'll have this:
┌─────────────────────┐
│ Unbound │
│ 127.0.0.1:53 │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
google.com myserver.home.arpa website.home.arpa
│ │ │
▼ └───────┬────────┘
Upstream DNS ▼
│ 127.0.0.1
▼
Google's IP
Let's build it incrementally.
1. What DNS Am I Already Using?
Before installing anything, I wanted to see how DNS currently worked on my machine.
On Fedora, I ran:
resolvectl status
My Wi-Fi interface showed:
Current DNS Server: 192.168.18.1
DNS Servers: 192.168.18.1
192.168.18.1 is my router.
I also checked:
cat /etc/resolv.conf
and found:
nameserver 127.0.0.53
At first this might seem contradictory.
Why does one command say 192.168.18.1 while another says 127.0.0.53?
Because Fedora is using systemd-resolved.
The path is roughly:
Application
│
▼
127.0.0.53
systemd-resolved
│
▼
192.168.18.1
Router
│
▼
Internet DNS
127.0.0.53 is a local DNS stub provided by systemd-resolved.
The actual upstream DNS server in my case was being provided by my network.
We can see this when querying a domain:
dig google.com
Part of my output looked like:
;; ANSWER SECTION:
google.com. 214 IN A 142.251.209.110
;; SERVER: 127.0.0.53#53
So normal DNS was already working.
Now I wanted to create another DNS resolver that I controlled myself.
2. Install Unbound
I chose Unbound as the resolver.
Install it on Fedora with:
sudo dnf install unbound
Then verify the installation:
unbound -V
I was running:
Version 1.26.0
Start the service:
sudo systemctl start unbound
Then check its status:
systemctl status unbound
It should show something like:
Active: active (running)
3. Is Unbound Actually Listening for DNS Requests?
DNS normally uses port 53, so we can inspect what's listening on that port:
sudo ss -tulpn | grep ':53'
Among the output, I found:
127.0.0.1:53
being used by Unbound.
This means I now effectively had two local DNS-related services:
127.0.0.53 → systemd-resolved
127.0.0.1 → Unbound
That's an important distinction for the rest of this tutorial.
4. Query Unbound Directly
Normally I can run:
dig google.com
But that uses my system-configured DNS.
dig also lets us explicitly choose which DNS server to query.
The syntax is:
dig @DNS_SERVER DOMAIN
Therefore, to query Unbound:
dig @127.0.0.1 google.com
Initially, mine returned:
status: SERVFAIL
Unbound was running and receiving my request, but wasn't successfully resolving the domain.
So I configured upstream DNS servers.
5. Configure Unbound
Create a custom configuration file:
sudo nano /etc/unbound/conf.d/local.conf
I started with:
server:
interface: 127.0.0.1
access-control: 127.0.0.0/8 allow
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
The important part here is:
forward-zone:
name: "."
The "." represents the DNS root, so this forwarding rule catches queries that aren't answered locally.
The two forward-addr entries specify upstream DNS resolvers.
Our setup is now:
dig
│
│ google.com?
▼
Unbound
127.0.0.1
│
│ I don't have a local record
▼
Upstream DNS
│
▼
google.com IP
6. Validate Before Restarting
Instead of blindly restarting Unbound and hoping the configuration works, we can validate it first:
sudo unbound-checkconf
A valid configuration should give:
unbound-checkconf: no errors in /etc/unbound/unbound.conf
Then restart:
sudo systemctl restart unbound
Now try again:
dig @127.0.0.1 google.com
This time I got:
status: NOERROR
and:
;; ANSWER SECTION:
google.com. 150 IN A 142.250.151.138
google.com. 150 IN A 142.250.151.100
google.com. 150 IN A 142.250.151.101
...
More importantly:
;; SERVER: 127.0.0.1#53
We now have a working local DNS resolver.
7. Creating Our Own Domains
Now for the fun part.
I wanted domains that don't exist on the public internet:
myserver.home.arpa
website.home.arpa
I used home.arpa rather than something like .local.
home.arpa is reserved for naming resources within home networks, while .local has special meaning for multicast DNS (mDNS).
Edit the configuration again:
sudo nano /etc/unbound/conf.d/local.conf
My complete configuration became:
server:
interface: 127.0.0.1
access-control: 127.0.0.0/8 allow
local-zone: "home.arpa." static
local-data: "myserver.home.arpa. IN A 127.0.0.1"
local-data: "website.home.arpa. IN A 127.0.0.1"
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Check it again:
sudo unbound-checkconf
Then:
sudo systemctl restart unbound
8. Test the Custom Domains
Now let's ask our resolver:
dig @127.0.0.1 myserver.home.arpa
I got:
;; ANSWER SECTION:
myserver.home.arpa. 3600 IN A 127.0.0.1
Try the second domain:
dig @127.0.0.1 website.home.arpa
Result:
;; ANSWER SECTION:
website.home.arpa. 3600 IN A 127.0.0.1
Both queries took essentially no time:
Query time: 0 msec
That's because Unbound already knows the answers. They're defined directly in its configuration.
9. What Does the A Mean?
Consider:
website.home.arpa. IN A 127.0.0.1
This is a DNS A record.
An A record maps a hostname to an IPv4 address:
website.home.arpa
│
│ A record
▼
127.0.0.1
We could point another name at another machine on our network:
local-data: "server.home.arpa. IN A 192.168.18.50"
Now our resolver would answer:
server.home.arpa → 192.168.18.50
This is essentially the same fundamental concept used by public DNS.
The difference is that these particular records only exist inside our resolver.
10. Local Domains vs Internet Domains
We can now ask the same DNS server three questions:
dig @127.0.0.1 google.com
dig @127.0.0.1 myserver.home.arpa
dig @127.0.0.1 website.home.arpa
Conceptually:
Unbound
127.0.0.1
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
google.com myserver... website...
│ │ │
▼ ▼ ▼
Not local LOCAL LOCAL
│ │ │
▼ └──────┬───────┘
Upstream DNS ▼
│ 127.0.0.1
▼
Google IP
That's the part that made DNS click for me.
A DNS resolver isn't necessarily some mysterious server sitting somewhere on the internet.
I can run one on my own laptop.
And I can tell it:
If someone asks for this name, give them this IP address.
11. Why Didn't I Change Fedora's DNS?
There was one more decision to make.
I could configure Fedora to use:
127.0.0.1
as its normal DNS server.
Then applications would automatically use Unbound.
For this experiment, however, I decided not to change my system DNS.
My normal Fedora DNS remains:
Applications
│
▼
systemd-resolved
127.0.0.53
│
▼
Router
│
▼
Internet
And my experimental resolver remains:
dig @127.0.0.1
│
▼
Unbound
│
├── custom domains
│
└── upstream DNS
That means I can experiment with Unbound without accidentally breaking DNS for the rest of my system.
When I want to test it, I explicitly use:
dig @127.0.0.1 example.com
12. Starting and Stopping the Resolver
Because I'm using this as a local experiment, I don't necessarily need Unbound running every time my computer starts.
Start it with:
sudo systemctl start unbound
Check it:
systemctl status unbound
And stop it:
sudo systemctl stop unbound
If I eventually decide I want it permanently available, I can enable it:
sudo systemctl enable --now unbound
What I Learned
This was a small project, but it cleared up several concepts I'd previously treated as separate things.
I now have a much better mental model of:
- What DNS actually does
- What a DNS resolver is
- The difference between a DNS resolver and the overall DNS system
- The difference between my system's DNS stub and its upstream DNS server
- Why
127.0.0.53appears in/etc/resolv.conf - How DNS uses port 53
- How to query a specific DNS server with
dig - How DNS forwarding works
- What an A record does
- How custom/private DNS names can exist without being registered publicly
- How a local resolver can answer some queries itself and forward others upstream
The final setup is surprisingly small:
My Laptop
│
┌────────┴─────────┐
│ │
Normal Fedora My DNS Lab
│ │
127.0.0.53 127.0.0.1
│ │
systemd-resolved Unbound
│ │
Router ┌──────┴──────┐
│ │ │
Internet Local DNS Internet
records forwarding
And all it took to create a domain of my own was essentially:
local-data: "website.home.arpa. IN A 127.0.0.1"
Obviously, this is only the beginning of DNS.
But building a tiny working version locally made the bigger system much easier to understand.
Environment used:
- Fedora Linux
- Unbound 1.26
systemd-resolved-
dig/ BIND utilities
Top comments (0)