Unbound on Ubuntu: Set Up a Private LAN DNS Resolver

Unbound on Ubuntu: Set Up a Private LAN DNS Resolver

Marcus Reid
October 11, 2026· 11 min read

Unbound on Ubuntu can provide a validating recursive DNS resolver for a small, trusted LAN without exposing a public service. This guide uses Ubuntu 24.04 LTS amd64 and its maintained Unbound package, checked as 1.19.2-1ubuntu3.10 on 11 October 2026. Package updates can change that revision; check your configured Ubuntu repositories rather than pinning an obsolete build. No complete end-to-end author test was performed.[1]

Key Takeaways

  • Begin on loopback, then bind one actual LAN address with a minimal authorized subnet.
  • Keep Ubuntu's trust anchor, service identity, and chroot arrangements intact.
  • DNSSEC validates signed data; it does not encrypt DNS or filter advertisements.
  • Test explicit queries before switching DNS on one client, and retain its original settings.

Is Unbound on Ubuntu the right task?

Use this procedure when you own the host and LAN and can maintain a recursive resolver. The overview of ongoing self-hosting duties helps assign updates, logs, access policy, and recovery. Compare DNS and VPN roles if you need an exit address or a different traffic path: a resolver answers names, not all application traffic.

Unbound is not automatically an ad-blocking service; DNS filtering has a separate policy and workflow. If the host already fails name resolution, first diagnose Ubuntu's resolver chain. The general device troubleshooting sequence separates application failures from DNS failures.

This is a small recursive resolver, not a forwarding profile, router deployment, or public DNS service. Recursive operation needs authorized outbound UDP and TCP port 53 to authoritative servers. Networks that prohibit that traffic need an approved alternative; do not disable their controls. Reserve a stable LAN address through the network owner's existing address-management process.

Record current packages, Unbound configuration and includes, unit and drop-ins, enablement, firewall rules, addresses, routes, and resolver ownership. Keep private backups outside active include directories. Retain an independent management connection. If an existing Unbound deployment or automated manager owns these paths, stop and agree a change with that owner rather than pasting a second conflicting server block.

Step 1: Inspect port 53 and the Ubuntu package

Check bindings and system DNS before installing. Ubuntu's systemd-resolved may listen on a loopback stub such as 127.0.0.53; that is different from 127.0.0.1. A wildcard listener can conflict with either. Do not disable the system resolver or rewrite /etc/resolv.conf to free a port.[6]

sudo ss -lntup 'sport = :53'
resolvectl status
ip -4 address
ip -6 address
sudo apt-get update
sudo apt-get install unbound dnsutils
apt-cache policy unbound
unbound -V
sudo unbound-checkconf /etc/unbound/unbound.conf
sudo systemctl status unbound.service --no-pager

Installing the package may start its service: inspect the resulting listener and service state immediately. Stop and investigate an unexpected broad listener. Preserve the baseline if the package was already installed. apt-cache policy shows installed and candidate versions; unbound -V shows the program build, not necessarily the full Ubuntu package revision.

Read /etc/unbound/unbound.conf, its include directives, and /etc/unbound/unbound.conf.d/ before adding a file. Verify the package-provided root trust-anchor arrangement and its accessible state directory; leave it in place. Do not duplicate auto-trust-anchor-file, initialize a second anchor blindly, set username: "", or remove chroot settings to silence a permissions error. Correct permissions and paths within the package's design.[2][6]

The current upstream handbook can explain concepts, but its examples and version may differ from Noble's package. Configuration fields and defaults here follow the Ubuntu manual. The package owns hardening and trust-anchor maintenance unless the existing administrator has deliberately configured otherwise.[2][3]

Step 2: Start a private DNS resolver on loopback

After reconciling existing interface and ACL directives, use a privileged editor to create the previously absent /etc/unbound/unbound.conf.d/reader-lan.conf, root-owned mode 644. Confirm the main file actually includes that directory. Add only this scoped block; keep the package's other files unchanged:

server:
    interface: 127.0.0.1
    port: 53
    access-control: 127.0.0.0/8 allow
    access-control: 0.0.0.0/0 refuse
    access-control: ::/0 refuse
    hide-identity: yes
    hide-version: yes
    verbosity: 1
    log-queries: no

The broad IPv4 and IPv6 ACL entries refuse clients; they are not listener addresses. The more-specific loopback allowance takes precedence. With only an explicit IPv4 loopback interface, this addition requests no incoming IPv6 listener. Other included configuration can still add interfaces or ACLs, which is why checking the merged configuration and actual sockets matters.[2]

Validate the effective file before restarting. unbound-checkconf should exit zero; it does not prove answers, network access, DNSSEC, or firewall safety.[4]

sudo unbound-checkconf /etc/unbound/unbound.conf
sudo systemctl restart unbound.service
sudo systemctl status unbound.service --no-pager
sudo ss -lntup 'sport = :53'
dig @127.0.0.1 example.com A

Read bounded diagnostics with sudo journalctl -u unbound.service -n 30 --no-pager. The explicit loopback query should receive a DNS response from the intended process. Inspect status and answer separately; NOERROR with no requested record can be valid. On a bind failure, resolve the actual address conflict; on a permissions failure, inspect package-owned paths. Do not add 0.0.0.0 as a shortcut.

Step 3: Restrict Unbound access control to one LAN

Replace the exercise file with this block only after loopback works. Substitute the host's real stable LAN IPv4 address for every use of 192.168.50.2 below, including the firewall and query commands. Replace 192.168.50.0/24 with the smallest actual authorized subnet; do not assume every private address belongs to your LAN.

server:
    interface: 127.0.0.1
    interface: 192.168.50.2
    port: 53
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.50.0/24 allow
    access-control: 0.0.0.0/0 refuse
    access-control: ::/0 refuse
    hide-identity: yes
    hide-version: yes
    verbosity: 1
    log-queries: no

Only assigned addresses can bind. Avoid 0.0.0.0, ::, broad private-range allowances, and router port forwarding. The most-specific matching ACL governs access. These examples do not add an IPv6 interface or client allowance; if IPv6 access is required, design and validate its actual interface, minimal prefix, and policy separately. Do not confuse incoming listeners with outbound IPv6 queries or AAAA answers.[2]

Re-run the effective configuration check, restart the service, and inspect both UDP and TCP listeners. Interface changes require a restart; do not assume a reload creates a new socket. Keep the current firewall manager and permit only UDP and TCP port 53 from the authorized subnet to this host's LAN address, if policy requires an allowance. Record exact additions, preserve management access, and never flush or disable a shared firewall. Unbound ACL and firewall restrictions serve different layers.

For an already-active UFW-owned policy, the narrow additions are:

sudo ufw allow proto udp from 192.168.50.0/24 to 192.168.50.2 port 53 comment 'unbound-lan'
sudo ufw allow proto tcp from 192.168.50.0/24 to 192.168.50.2 port 53 comment 'unbound-lan'

Replace the sample subnet and address before use. Do not enable UFW solely to run these commands. If another owner manages the firewall, use that owner's equivalent procedure.

Step 4: Verify answers, DNSSEC, cache, and refusal

On one authorized LAN test client with dig, issue explicit queries before changing system DNS:

dig @192.168.50.2 example.com A
dig @192.168.50.2 example.com A +tcp
dig @192.168.50.2 example.com AAAA
dig @192.168.50.2 com. SOA +dnssec
dig @192.168.50.2 dnssec-failed.org A +dnssec
dig @192.168.50.2 dnssec-failed.org A +dnssec +cdflag

Read the server address, status, answer type, and flags. A signed positive query such as com. SOA should return validated data with ad when validation succeeds. The deliberately broken dnssec-failed.org example should return SERVFAIL while it remains bogus. With +cdflag, a different result can help distinguish validation failure from reachability; use that flag only for this diagnostic, never as the normal client policy. These are expected observations, not live results asserted by this article.[2][5]

Reader checkExpected observationIf it differs
Explicit UDP and TCP queries from the allowed clientResponses come from the selected LAN addressInspect each transport's listener and firewall rule
Signed positive answerSuccessful validation and ad for authenticated dataInspect time, trust anchor, logs, and whether the selected name remains signed
Known-bogus answer without CDSERVFAIL when the test remains bogusConfirm the effective validator and test-domain condition; do not disable validation
Repeat a cacheable positive query before TTL expiryA cached answer may show a decreasing TTLPrefetch, expiry, and different cache paths can alter observations
Controlled source outside the allowed subnet, reaching the listenerREFUSED under this ACLA firewall drop may instead time out; verify the actual source and policy
IPv4 and IPv6 socket inventoryOnly intended listeners existCheck all includes and firewall families before declaring the boundary correct
Service restart followed by a new queryAuthorized resolution works againRestore the safe configuration if the baseline is not recovered

Use a genuinely separate, authorized source for the refusal check. A timeout from one network is not evidence that every unauthorized network is excluded. Do not create a public exposure or broad allow rule just to obtain a refusal response. Mark the check unperformed if no controlled source is available.

For cache observation, repeat the same name, type, and class shortly after a successful query and compare TTL and reported server. Do not treat lower query time as a benchmark: scheduling, network caches, prefetch, and upstream conditions can change it. DNSSEC protects signed-data authenticity, not the confidentiality of DNS queries. Query logs remain disabled in this sample; diagnostics can still contain addresses, so retain them privately.

Step 5: Switch one client and practice recovery

Only after explicit checks, save one client's existing DNS values and their owner: DHCP, NetworkManager, networkd, VPN, or another supported manager. Change that client's DNS through its supported interface to the resolver's actual LAN address. Leave the router and other clients unchanged. Check the effective resolver state and ordinary application resolution. Applications using their own DoH can bypass system DNS; a working browser alone does not prove the system uses Unbound.

To recover, first restore that client's exact previous DNS settings through the same owner and verify ordinary resolution. Remove only the recorded exercise firewall additions, then restore the saved Unbound configuration. If this exercise created reader-lan.conf and it was previously absent, remove only that file; keep package trust-anchor and other include files. Validate the effective baseline, restart or stop the service according to its former state, and restore prior enablement. Verify listeners, system resolver, DNS answers, and management access.

If a unit or drop-in was changed by the existing deployment procedure, restore it and run sudo systemctl daemon-reload before restarting. Remove the package only if the baseline proves this exercise introduced it and nothing else needs it. Do not remove trust-anchor state from an existing installation or blindly rewrite /etc/resolv.conf. If recovery is uncertain, stop further changes and use the independent management path.

To add filtering in front of this resolver, AdGuard Home with split upstreams can forward selected domains to Unbound.

Summary

  • Establish loopback resolution before exposing one deliberate LAN listener.
  • Check both transports, validation, refusal, and effective client DNS separately.
  • Restore the client first, then narrowly restore the host's original service and policy.

FAQ

Does this create an open public resolver?

The intended configuration binds specific private interfaces and permits one authorized subnet. Confirm merged includes, sockets, firewall families, and upstream NAT; the snippet alone is not proof of the entire deployment boundary.

Must I disable systemd-resolved?

No. Inspect the actual bindings and resolver chain. Different explicit loopback addresses can coexist; a real conflict needs an ownership-aware correction.[6]

Does DNSSEC encrypt queries?

No. It validates signed DNS data. This recursive UDP/TCP configuration does not add encrypted client transport or hide queries from the network.

Why test TCP as well as UDP?

DNS uses both. A UDP answer does not establish that TCP works when required for a larger response or another supported exchange.

Does an AAAA answer prove an IPv6 listener exists?

No. Record type, inbound transport family, and outbound resolver connectivity are different properties; inspect sockets explicitly.

Does a fast second lookup prove performance gains?

No. Observe cache behavior under controlled conditions, but one timing difference is not a reliable benchmark or comparison.

Should I change the router's DNS immediately?

No. Check one client and its recovery first. Router-wide distribution is outside this scoped procedure and needs its own maintenance and rollback plan.

Disclaimer: Use only authorized hosts and networks and follow applicable law and policy. These are documentation-based steps and expected reader checks, not an author deployment record, compatibility certification, or performance guarantee.

Sources:

  1. Ubuntu noble-updates Unbound package — https://packages.ubuntu.com/noble-updates/unbound
  2. Ubuntu noble unbound.conf manual — https://manpages.ubuntu.com/manpages/noble/man5/unbound.conf.5.html
  3. NLnet Labs home resolver guide — https://unbound.docs.nlnetlabs.nl/en/latest/use-cases/home-resolver.html
  4. NLnet Labs configuration checker — https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound-checkconf.html
  5. Google Public DNS: DNSSEC failure example — https://developers.google.com/speed/public-dns/docs/troubleshooting/domains
  6. NLnet Labs resolver configuration — https://unbound.docs.nlnetlabs.nl/en/latest/getting-started/configuration.html

Sources checked 11 October 2026.

Related Articles:

Start your 3-day free trial

Sign up to experience all premium features at no cost.

*Available only to new users. Each user is limited to one trial.

Unbound on Ubuntu: Set Up a Private LAN DNS Resolver | AethoVPN