How to Configure a DNS Server (BIND or Unbound) on Fedora

Fedora makes it straightforward to run a local DNS resolver or authoritative name server using either Unbound (recommended for caching/forwarding) or BIND (for authoritative zones).

The local DNS problem

You just finished wiring up a homelab, or you grew tired of watching your router's DNS resolver struggle with peak-hour latency. You want a local DNS server on Fedora to cache queries, validate DNSSEC, or host authoritative zones for your internal network. The terminal is open, dnf is ready, and you know how to run commands as root. What you probably do not know is how Fedora's network stack expects DNS to behave, or why your local resolver silently fails after a reboot.

How Fedora handles resolution

Fedora does not use a static /etc/resolv.conf file. NetworkManager writes to it dynamically based on active connections. When you install a local resolver, you are inserting a middleman between your applications and the upstream internet. The resolver listens on localhost or a LAN interface, caches responses, and optionally validates cryptographic signatures. If the service crashes, your system falls back to the connection's primary DNS, or it stops resolving entirely. The goal is to make that middleman reliable, secure, and transparent to the rest of the stack.

Fedora ships two DNS packages in the official repositories. Unbound is a validating, recursive resolver. BIND is a full-featured authoritative name server. They solve different problems. Mixing them up leads to configuration drift and broken lookups. Pick one role, configure it cleanly, and let the package manager handle updates.

Unbound for caching and validation

Unbound is the recommended choice for desktop users, home networks, and small offices. It downloads the root hints, walks the DNS hierarchy, caches results, and verifies DNSSEC chains. It is lightweight and designed for exactly this role. Fedora packages it with a clean configuration split. The main config lives in /etc/unbound/unbound.conf, but you should never edit it directly. Drop your customizations into /etc/unbound/conf.d/. The package manager and update scripts will overwrite /etc/unbound/unbound.conf on upgrades. Your files in conf.d survive.

Here is how to install the package and start the service.

sudo dnf install unbound
# Install the resolver binary and default configuration files
sudo systemctl enable --now unbound
# Register the service with systemd and start it immediately
sudo systemctl status unbound
# Verify the unit is active and check for recent log lines

You need to tell Unbound which interfaces to listen on and which upstream servers to forward queries to. Creating a forwarding zone is the standard approach for home networks. It keeps validation active while offloading recursive lookups to a trusted provider. Unbound will still verify DNSSEC signatures on the responses it receives.

Here is the forwarding configuration that restricts access to localhost and your local subnet.

server:
    interface: 127.0.0.1
    # Bind only to localhost to prevent accidental exposure
    access-control: 127.0.0.0/8 allow
    # Permit queries from the loopback network
    access-control: 192.168.0.0/16 allow
    # Permit queries from your local LAN range

forward-zone:
    name: "."
    # Forward all queries to the upstream resolver
    forward-addr: 9.9.9.9
    # Primary upstream: Quad9 with DNSSEC support
    forward-addr: 149.112.112.112
    # Secondary upstream for redundancy

Save that block to /etc/unbound/conf.d/forwarding.conf. Run the configuration checker before restarting the daemon. Unbound will refuse to start if the syntax is broken or if the upstream addresses are unreachable during the initial trust anchor setup. The first run takes a few seconds while it downloads and validates the root trust anchors. This is normal. Do not interrupt it.

sudo unbound-checkconf
# Parse the configuration tree and report syntax or logic errors
sudo systemctl restart unbound
# Apply the new configuration and restart the resolver process

If you plan to serve DNS to other machines on your LAN, you must open the firewall. Fedora's default firewall policy blocks all incoming traffic except explicitly allowed services. The dns service preset covers both TCP and UDP port 53.

sudo firewall-cmd --permanent --add-service=dns
# Add the DNS service rule to the persistent firewall configuration
sudo firewall-cmd --reload
# Apply the persistent rules to the running firewall without dropping connections

Always run firewall-cmd --reload after modifying rules. The runtime configuration and the persistent configuration diverge immediately if you skip this step. Reboot before you debug network issues. Half the time the symptom is a stale firewall state.

BIND for authoritative zones

BIND serves a different purpose. It is an authoritative name server. You use it when you own a domain and need to publish records, or when you run an internal .local or .internal zone that external resolvers should never touch. The daemon is called named. Fedora ships it with strict defaults to prevent accidental open resolvers. It does not cache external queries by default. It only answers for zones you explicitly configure.

Here is how to install the authoritative server and its diagnostic utilities.

sudo dnf install bind bind-utils
# Install the named daemon and the dig/host/nslookup tools
sudo systemctl enable --now named
# Register the service with systemd and start it immediately

BIND reads its configuration from /etc/named.conf. You can append zone definitions directly, but the cleaner approach is to use the included directory. Fedora's default config already includes /etc/named.conf.d/*.conf. Create a zone file there. The directory structure keeps your custom zones separate from package-managed defaults.

Here is a minimal zone definition that tells BIND to load your internal records.

zone "example.local" IN {
    type master;
    # Declare this server as the primary source for the zone
    file "/var/named/example.local.zone";
    # Point to the zone data file on disk
};

The zone file itself follows RFC 1035 syntax. Every record needs a time-to-live value, a start-of-authority block, and at least one nameserver record. The serial number must increment every time you edit the file. Secondary servers use it to detect updates. A static serial number means your changes will never propagate.

Here is the zone data file that defines your internal hostnames.

$TTL 86400
@   IN  SOA  ns1.example.local. admin.example.local. (
            2024010101 ; Serial number: increment on every change
            3600       ; Refresh interval for secondary servers
            900        ; Retry delay after a failed transfer
            604800     ; Expire time before discarding stale data
            86400 )    ; Negative cache TTL for failed lookups
    IN  NS   ns1.example.local.
ns1 IN  A    192.168.1.10
www IN  A    192.168.1.20

Save that to /var/named/example.local.zone. BIND runs under the named user, and SELinux enforces strict file contexts for zone data. The /var/named/ directory already has the correct named_zone_t context. If you place zone files elsewhere, you will get permission denied errors in the journal. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux.

sudo named-checkconf
# Validate the main configuration and all included files
sudo named-checkzone example.local /var/named/example.local.zone
# Parse the zone file and verify record syntax and serial format
sudo systemctl reload named
# Instruct the running daemon to read the new zone without dropping connections

Run named-checkzone before every edit. A missing trailing dot on a hostname or a mismatched serial number will cause the zone to fail to load. Trust the package manager. Manual file edits drift, snapshots stay.

Pointing Fedora at your resolver

Your system needs to know where to send DNS queries. Editing /etc/resolv.conf directly is a temporary fix. NetworkManager will overwrite it on the next connection change or reboot. Use nmcli to attach the local resolver to your active connection profile. The profile persists across reboots and adapts to different network environments.

Here is how to configure your network connection to use the local DNS server.

nmcli con mod "Wired connection 1" ipv4.dns "127.0.0.1"
# Replace the default upstream DNS with your local resolver
nmcli con up "Wired connection 1"
# Reactivate the connection to apply the new DNS settings

Verify that queries are hitting your local server. The dig command from bind-utils shows exactly where the answer came from and how long it took. It bypasses the system resolver cache and talks directly to the daemon you specified.

dig @127.0.0.1 example.local
# Send a query directly to localhost and display the full response

Check the SERVER: line in the output. It should read 127.0.0.1#53(127.0.0.1). If you see your router's IP address instead, the connection profile did not apply correctly. Reboot before you debug. Half the time the symptom is a stale NetworkManager cache.

Common pitfalls and error patterns

Local DNS servers fail in predictable ways. Unbound drops to a restricted user account and refuses to bind to privileged ports without proper capabilities. BIND rejects zone files with syntax errors and logs them to the journal. SELinux blocks access to custom directories. The firewall drops packets silently.

If Unbound fails to start, the journal will show error: permission denied for control interface or error: could not bind to 0.0.0.0#53. The service requires the CAP_NET_BIND_SERVICE capability, which Fedora grants automatically via the systemd unit. Do not run the daemon as root. Check the unit file if you modified it.

If BIND refuses to load a zone, you will see zone example.local/IN: loading from master file /var/named/example.local.zone failed: syntax error in the logs. The error points to the exact line. A missing semicolon, a malformed TTL, or a serial number that did not increment will trigger it.

Run journalctl -xeu unbound or journalctl -xeu named to see the full context. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style. Read the actual error before guessing.

Which server fits your network

Use Unbound when you need a fast, local caching resolver with automatic DNSSEC validation. Use BIND when you must publish authoritative records for a domain or host internal zones that external servers should never query. Use Unbound when you are running a desktop, a home router, or a small office network. Use BIND when you are managing enterprise zones, dynamic DNS updates, or complex view configurations. Stay on Unbound if you only need to speed up local lookups and reduce upstream latency.

Where to go next