Setting Up a Local DNS Resolver with bind9

Set up a local DNS resolver with bind9 on Linux using a clear, step-by-step guide. Ideal for Linux and DevOps beginners—includes configuration tips, security basics, and troubleshooting.

Setting Up a Local DNS Resolver with bind9

Introduction
This tutorial walks you through setting up a local DNS resolver using bind9 on a Debian/Ubuntu-style system. It assumes a single machine acting as a caching/recursive resolver for your LAN (or for local development) and explains installation, basic configuration, testing, and hardening in a beginner-friendly way.

Requirements and overview

Before you begin, make sure you have:

  • A Linux machine (Debian, Ubuntu, or similar) with sudo/root access.
  • A stable internet connection (for forwarders and package installation).
  • Optional: a static LAN IP if other machines will query this resolver.

What we will accomplish:

  • Install bind9.
  • Configure it as a recursive, caching resolver for local clients.
  • Optionally forward queries to upstream resolvers.
  • Test, secure, and tune basic settings.

Example: check your OS and update packages

# Confirm OS
cat /etc/os-release

# Update package index
sudo apt update

Install bind9 and useful commands table

Install bind9 using your package manager, enable the service, and check its status.

Installation and basic service commands:

sudo apt install bind9 bind9utils -y
sudo systemctl enable --now bind9
sudo systemctl status bind9

Commands table

Command Purpose
sudo apt update && sudo apt install bind9 bind9utils -y Install bind9 and helper utilities on Debian/Ubuntu
sudo systemctl enable --now bind9 Enable and start bind9 immediately
sudo systemctl status bind9 Check service status
sudo journalctl -u bind9 -f Stream bind9 logs for troubleshooting
dig @127.0.0.1 example.com Query the local resolver directly
sudo ufw allow 53/tcp && sudo ufw allow 53/udp Open DNS ports in uncomplicated firewall

Verify bind9 is listening on port 53 (both UDP and TCP):

sudo ss -tunlp | grep :53
# or
sudo lsof -i :53

If bind9 is not listening, check the logs:

sudo journalctl -u bind9 --since "10 minutes ago"

Configure bind9 as a local recursive resolver

The primary configuration file for Debian/Ubuntu is /etc/bind/named.conf.options. We'll configure recursion, forwarders (optional), and restrict queries to local networks for safety.

Example named.conf.options (edit as root):

sudo nano /etc/bind/named.conf.options

Paste or modify the options section:

options {
    directory "/var/cache/bind";

    recursion yes;                // allow recursive queries
    allow-recursion { localnets; localhost; };
    allow-query { localnets; localhost; };
    listen-on { 127.0.0.1; 192.168.1.0/24; };  // adjust to your LAN
    listen-on-v6 { none; };

    // Use upstream resolvers (change as desired)
    forwarders {
        1.1.1.1;
        8.8.8.8;
    };

    dnssec-validation auto;
    auth-nxdomain no;    // conform to RFC1035
    listen-on-v6 { any; };
};

After saving, reload bind9:

sudo systemctl reload bind9
# or restart if reloading gives issues
sudo systemctl restart bind9

Make your machine (or clients) use the resolver:

  • For the local machine, edit /etc/resolv.conf or systemd-resolved configuration to point to 127.0.0.1.
# Temporary (will be overwritten by some systems)
sudo sed -i '1inameserver 127.0.0.1' /etc/resolv.conf

# To persist with Netplan / systemd-resolved, update the appropriate config files instead.

Test basic resolution using dig:

dig @127.0.0.1 example.com +short
# Expect an IPv4 or IPv6 address in response

Check cache functionality by timing queries:

dig @127.0.0.1 example.com
# Run again and note the "Query time:" value should be lower for cached responses.

Secure, tune, and troubleshoot

Bind can be tuned for performance and secured to avoid being an open resolver exposed to the internet.

Secure bind9: restrict who can query/recursion and bind to specific interfaces

# Ensure allow-query and allow-recursion include only localhost and your LAN:
allow-recursion { localhost; 192.168.1.0/24; };
allow-query { localhost; 192.168.1.0/24; };
listen-on { 127.0.0.1; 192.168.1.10; };  # replace with your server IP

Firewall example (UFW) to allow only your LAN to access DNS:

# Allow DNS from LAN only
sudo ufw allow from 192.168.1.0/24 to any port 53 proto udp
sudo ufw allow from 192.168.1.0/24 to any port 53 proto tcp

# Deny DNS from everywhere else (if default policy is allow)
sudo ufw deny 53

Rate-limiting and query limiting (basic anti-abuse):
Add to options in named.conf.options:

rate-limit {
    responses-per-second 5;
};

Troubleshooting tips and commands:

# View live logs
sudo journalctl -u bind9 -f

# Test for open resolver from a remote machine
dig @your.resolver.ip whoami.akamai.net TXT +short

# Test specific behavior (recursion)
dig @127.0.0.1 example.com +norecurse

If bind fails to start, common quick checks:

  • Syntax errors in configuration:
sudo named-checkconf
sudo named-checkzone example.com /etc/bind/db.example.com
  • Permissions on zone files or /var/cache/bind.

Testing and verifying behavior

You want to confirm the resolver is correct, caching, and not open to the internet.

Query the local resolver:

dig @127.0.0.1 example.com

Compare timings to public resolver:

dig @1.1.1.1 example.com

Check cache effectiveness by re-querying:

# First query (uncached)
dig @127.0.0.1 example.com

# Second query (should be faster)
dig @127.0.0.1 example.com

Confirm recursion is restricted (from a remote machine outside allowed nets):

dig @your.resolver.ip example.com
# If restricted, you should see a SERVFAIL or REFUSED response for recursive queries.

If you use systemd-resolved on Ubuntu, make sure it doesn't conflict. Either:

  • Configure systemd-resolved to forward to 127.0.0.1, or
  • Disable systemd-resolved and manage /etc/resolv.conf manually:
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolv.conf'

Common Pitfalls

  • Forgetting to restrict queries: leaving allow-query or allow-recursion open can make your server an open resolver and vulnerable to abuse.
  • Conflicting local resolver (systemd-resolved): Ubuntu often runs systemd-resolved by default; it may overwrite /etc/resolv.conf and block your bind9 from being used.
  • Firewall or listening interface misconfiguration: bind might be listening only on localhost while clients try connecting to the LAN IP (or vice versa); check listen-on and firewall rules.

Next Steps

  • Enable DNSSEC validation and monitoring: configure more detailed logging and integrate with monitoring tools.
  • Add local zones for development: create forward and reverse zones for local hostnames to simplify local access.
  • Consider DoT/DoH or split-horizon setups: if you need privacy or different answers for internal/external clients, explore DNS-over-TLS, DNS-over-HTTPS, or view-based zones.

End notes
This setup gives you a robust local caching resolver suitable for home labs or small networks. Always test changes carefully, monitor logs after configuration updates, and keep bind9 updated for security patches.

👉 Explore more IT books and guides at dargslan.com.