Set up DNS server

Fedora can run a local DNS server using either systemd-resolved for simple caching or Bind/dnsmasq for full authoritative or split-horizon setups.

The DNS dilemma on Fedora

You plug your laptop into a new network, type a domain name, and get a timeout. Or you set up a home lab, assign static IPs, and suddenly your devices can only talk by raw address. DNS is the invisible glue holding the network together. Fedora gives you three distinct paths to handle it, ranging from a simple local cache to a full authoritative zone server. Picking the wrong one causes port conflicts, broken resolution, and hours of debugging.

What DNS resolution actually looks like under the hood

DNS resolution on Linux follows a strict chain. Your application asks the resolver library for an IP. The library forwards the request to a local stub or forwarder. That forwarder either checks a local cache, queries an upstream server, or answers from an authoritative zone file. Fedora defaults to systemd-resolved because it handles the local stub, caches responses, and integrates with NetworkManager. When you add dnsmasq or BIND, you are inserting another process that wants to listen on port 53. The kernel only allows one process to bind to a specific port and protocol at a time. If two services fight for port 53, one crashes or refuses to start. Understanding this chain stops you from blindly installing packages and wondering why resolution breaks.

Think of DNS like a mail sorting facility. systemd-resolved is the local post office that remembers recent addresses and forwards unknown mail to the regional hub. dnsmasq is a neighborhood distribution center that also handles local deliveries and temporary routing. BIND is the national postal authority that defines exactly where every street and building exists. You do not need the national authority to send a letter to a neighbor. You only need it when you are defining the streets yourself.

Option 1: systemd-resolved (local caching resolver)

Fedora enables systemd-resolved out of the box. It runs a stub listener on 127.0.0.53 and caches responses to speed up repeated lookups. You rarely need to install it. You only need to point it at reliable upstream servers and verify it is caching correctly.

Here is how to check whether the resolver is active and which upstream servers it trusts.

resolvectl status
# Shows the active DNS servers, cache size, and link-specific routing
# Confirms whether systemd-resolved is actually listening on 127.0.0.53
# Reveals if NetworkManager has overridden your manual settings

To configure permanent upstream resolvers, edit the main configuration file. Always modify files in /etc/. Never edit files in /usr/lib/, because package updates will overwrite them.

[Resolve]
DNS=1.1.1.1 8.8.8.8
# Primary upstream servers queried when a local cache miss occurs
FallbackDNS=9.9.9.9
# Used only when all primary servers fail to respond within timeout
Cache=yes
# Explicitly enables response caching to reduce upstream latency
DNSSEC=allow-downgrade
# Validates signed zones but falls back to unsigned if needed

Apply the changes by restarting the service. The resolver drops its cache and reloads the configuration immediately.

sudo systemctl restart systemd-resolved
# Forces the daemon to reread /etc/systemd/resolved.conf
# Clears the in-memory cache so new upstreams take effect
# Triggers a brief resolution pause while the stub listener restarts

Run resolvectl query example.com to confirm the new upstreams are being used. The output will show DNSSEC supported: yes and list the server that answered. Restart the service before you debug resolution issues. Half the time the symptom is a stale cache entry.

Option 2: dnsmasq (lightweight forwarder and DHCP)

dnsmasq combines a DNS forwarder with a DHCP server. It is the standard choice for home labs, small office networks, and virtualization hosts where you need local host overrides and address assignment in a single lightweight process.

Install the package and pull in the default configuration skeleton.

sudo dnf install dnsmasq
# Fetches the dnsmasq binary and drops the default config into /etc/
# Installs systemd unit files for automatic service management
# Does not start the service automatically to prevent port conflicts

Configure the listener addresses, upstream servers, and local domain overrides. The configuration file uses a simple key-value format.

listen-address=127.0.0.1,192.168.1.1
# Binds dnsmasq to localhost and your LAN gateway interface
no-resolv
# Prevents dnsmasq from reading /etc/resolv.conf automatically
server=1.1.1.1
# First upstream resolver queried for unknown domains
server=8.8.8.8
# Secondary upstream used if the first server times out
local=/home.lab/
# Treats this domain as local and stops forwarding queries for it
address=/myhost.home.lab/192.168.1.50
# Returns a static IP for this specific hostname without upstream lookup

Enable the service and open the firewall. DNS traffic uses UDP and TCP on port 53. Both must be allowed for zone transfers and large responses.

sudo systemctl enable --now dnsmasq
# Starts the service immediately and registers it for boot
sudo firewall-cmd --permanent --add-service=dns
# Adds the predefined DNS service rule to the persistent firewall config
sudo firewall-cmd --reload
# Applies the permanent rules to the active runtime firewall

Verify the forwarder is answering local queries. Run dig @127.0.0.1 myhost.home.lab and check the ANSWER SECTION. If you see the correct IP, the forwarder is routing correctly. Reload the firewall after every rule change. Runtime and persistent configs diverge otherwise.

Option 3: BIND (full authoritative server)

BIND (Berkeley Internet Name Domain) is the industry standard for authoritative DNS. You use it when you need to publish zones, manage subdomains, handle zone transfers, or run a production infrastructure. It is heavier than dnsmasq and requires explicit zone definitions.

Install the server package and the diagnostic utilities.

sudo dnf install bind bind-utils
# Installs the named daemon and the dig/nslookup diagnostic tools
# Drops the base configuration into /etc/named.conf
# Sets up SELinux contexts for the named service automatically

Configure the main options and define a sample zone. BIND reads this file on startup and refuses to run if syntax is invalid.

options {
    listen-on port 53 { 127.0.0.1; 192.168.1.1; };
    # Restricts BIND to specific interfaces to avoid answering external queries
    allow-query { localhost; 192.168.1.0/24; };
    # Limits who can ask questions to your local network range
    recursion yes;
    # Enables caching and upstream forwarding for internal clients
    dnssec-validation auto;
    # Validates signed responses and falls back gracefully
};

zone "example.lab" IN {
    type master;
    # Declares this server as the primary authority for the domain
    file "/var/named/example.lab.zone";
    # Points to the zone file containing A, MX, and TXT records
    allow-update { none; };
    # Disables dynamic updates unless explicitly configured
};

Start the service and open the firewall. BIND requires explicit permission to bind to port 53.

sudo systemctl enable --now named
# Launches the named daemon and registers it for system boot
sudo firewall-cmd --permanent --add-service=dns
# Opens UDP and TCP 53 in the persistent firewall configuration
sudo firewall-cmd --reload
# Pushes the new rules into the active netfilter table

Check the journal for startup errors. BIND writes detailed diagnostics to the system journal on launch.

journalctl -xeu named
# The x flag adds explanatory context to each log line
# The e flag jumps to the end of the journal for recent events
# Confirms whether zone files loaded without syntax errors

Read the actual error before guessing. BIND will refuse to start if a zone file contains a missing trailing dot or a malformed TTL value.

Verify it worked

Verification depends on which tool you deployed. Run the appropriate command for your setup.

For systemd-resolved, query a known domain and check the cache statistics.

resolvectl statistics
# Displays cache hits, misses, and DNSSEC validation counts
# Confirms the resolver is actually caching repeated lookups
# Shows which upstream server answered the last query

For dnsmasq or BIND, use dig to test both local and upstream resolution.

dig @127.0.0.1 example.com +short
# Queries the local server directly and returns only the IP address
# Skips verbose headers to keep the output readable
# Fails immediately if the local service is not listening

If the command returns an IP, the chain is intact. If it hangs or returns connection timed out, check the service status and firewall rules. Run systemctl status <unit> before restarting. Status shows recent log lines and current state in one view.

Common pitfalls and what the error looks like

Port 53 conflicts are the most frequent failure mode. When two services try to bind to the same port, the second one fails with a clear kernel message.

dnsmasq: failed to create listening socket for port 53: Address already in use

This happens when systemd-resolved is still running its stub listener while you start dnsmasq or BIND. Disable the stub listener before starting the alternative server.

[Resolve]
DNSStubListener=no
# Stops systemd-resolved from binding to 127.0.0.53
# Prevents the port 53 conflict with dnsmasq or BIND
# Leaves upstream resolution and caching functionality intact

SELinux denials appear when BIND tries to read a zone file in an untrusted location. The denial shows up in the journal with a one-line summary.

type=AVC msg=audit(...): avc:  denied  { read } for  pid=... comm="named" name="example.lab.zone" dev="sda1" ino=... scontext=system_u:system_r:named_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

Move zone files to /var/named/ and restore the correct context. SELinux protects the daemon from reading arbitrary files. Trust the package manager. Manual file edits drift, snapshots stay.

Stale caches cause phantom resolution failures after upstream changes. Flush the cache before testing new records.

sudo resolvectl flush-caches
# Clears the in-memory DNS cache for systemd-resolved
# Forces fresh upstream queries on the next lookup
# Takes effect immediately without a service restart

If the boot menu is gone, GRUB rescue is your friend, not your enemy. DNS failures rarely break boot, but they do break package downloads and remote logins. Snapshot the system before the upgrade. Future-you will thank you.

Which tool matches your workload

Use systemd-resolved when you only need a local cache and reliable upstream forwarding on a standard Fedora desktop or server. Use dnsmasq when you are running a home lab, managing DHCP leases, and want local host overrides without heavy dependencies. Use BIND when you are publishing authoritative zones, managing subdomains, or running production infrastructure that requires zone transfers and strict access controls. Stay on the upstream systemd-resolved if you only deviate from the defaults occasionally.

Where to go next