User Tools

Site Tools



Unbound is a validating, recursive, and caching DNS resolver. The C implementation of Unbound is developed and maintained by NLnet Labs.

OpenWrt uses Dnsmasq for DNS forwarding (and DHCP serving). This works well for many cases. Dependence on the upstream resolver can be cause for concern. It is often provided by the ISP, and some users have switched to public DNS providers. Either way can result in problems due to performance, hijacking, trustworthiness, or several other reasons. Running a recursive resolver is a solution.

Releases LEDE 17.01 and OpenWrt 18.06 rc1 have UCI and LuCI for Unbound with features that may be familiar. “How To” are available for integration with either dnsmasq or odhcpd. “How To” are available to configure Unbound as forwarding client of DNS over TLS. These can be found on GitHub within openwrt/packages in Unbound pacakge README.

Unbound with Dnsmasq on Chaos Calmer

The remainder of this page describes how to setup Unbound combined with Dnsmasq for local name and address resolution on OpenWrt Chaos Calmer (15.05.1). There are two examples. Example 1 puts dnsmasq and Unbound in serial configuration, so dnsmasq responds to all queries on #53 and operates almost in default configuration but treats Unbound as the upstream resolver. Example 1 will be preferred by most due to its simplicity. Example 2 puts Unbound and dnsmasq in parallel, so Unbound responds to all queries on #53, but hands off local domain to dnsmasq. Example 2 may reduce lookup delays in heavy use systems, but is more complex to configure.


The following steps assume that OpenWrt has been installed on a device and configured as desired, including the network configuration.

The later steps require accessing the device using a terminal.

Note in examples that the choice of local host port 53535 is arbitrary. Similar tutorials often use 5353 or 5355 (which can conflict with MDNS). Adjust as desired.

Example 1 - Serial, dnsmasq first and Unbound second

This should be the preferred configuration for MOST people for several reasons: 1) it's simpler to troubleshoot, 2) the configuration is easier, 3) It's faster for local resolution, 4) better for adblocking (dnsmasq has simpler syntax). It puts dnsmasq at port 53, and unbound at 53535. As before, you will need to install unbound by first updating the package list via luci or opkg update, then opkg install unbound, and if you want, opkg install luci-app-unbound


You can use the default package install configuration for Unbound (small memory), and you only need to change port: 53535 option. In such configuration Unbound will recurs the global DNS system, but with a lot of users, it could stress a small router. The remaining Unbound example here uses ssl for upstream servers. If you want to use or other non-tls server, then you need to comment out the ssl-upstream: yes line. Adjust as needed.

  port: 53535
  access-control: allow
  access-control: allow
  access-control: allow
  cache-max-ttl: 14400
  cache-min-ttl: 900
  do-tcp: yes
  hide-identity: yes
  hide-version: yes
  minimal-responses: yes
  prefetch: yes
  qname-minimisation: yes
  rrset-roundrobin: yes
  ssl-upstream: yes
  use-caps-for-id: yes
  verbosity: 1
  do-ip4: yes
  do-ip6: no
  outgoing-port-permit: "10240-65335"
  outgoing-range: 60
  num-queries-per-thread: 30
  msg-buffer-size: 8192
  infra-cache-numhosts: 200
  msg-cache-size: 100k
  rrset-cache-size: 100k
  key-cache-size: 100k
  neg-cache-size: 10k
  target-fetch-policy: "2 1 0 0 0 0"
  harden-large-queries: yes
  harden-short-bufsize: yes
  name: "."
  forward-addr:         # primary
  forward-addr: # secondary


There are TWO changes being made to dnsmasq: 1) add a server option to point to unbound at, and to ignore whatever's in /etc/resolv.conf using option noresolv 1:

      option domainneeded '1'
      option boguspriv '1'
      option localise_queries '1'
      option rebind_protection '1'
      option rebind_localhost '1'
      option local '/lan/'
      option domain 'lan'
      option expandhosts '1'
      option authoritative '1'
      option readethers '1'
      option leasefile '/tmp/dhcp.leases'
      option resolvfile '/tmp/'
      option localservice '1'
      # The following redirects dns to unbound
      option noresolv '1'
      list server ''

OPTIONAL for adblocking:/etc/dnsmasq.conf

If you want adblocking, dnsmasq accepts wildcards which is better than hosts files because you can cover an entire domain with one line. So: here we point dnsmasq to get our blacklists from the adblock folder. Thus, unbound NEVER has to resolve ad servers. Replace with the LAN address of your router if different. See the dnsmasq for an explanation of these options if unsure.


You will need to add your blacklist files to the /etc/adblock folder. These files would contain, for example, lines like address=/ Many adblock list sites will create these dnsmasq-specific blacklist files for you, and in the proper format to be placed in this folder. If the adlist ops to use 'server=' instead of address=, then replace with address=. server= redirects address lookups, whereas address= simply serves up the address.

Example 2 - Parallel, Unbound primary and dnsmasq only local

This example directs dns queries to unbound first, then to dnsmasq if local using 'stub zones'. The installation and configuration instructions below are written in the form of a shell script for precision and clarity to a technical audience. The script can be saved and executed, although it is recommended to run commands and make edits individually both for better understanding and because the script is written to favor readability and clarity of instruction at the cost of thorough error handling and robustness.

# Steps to configure unbound on OpenWrt with dnsmasq for dynamic DNS
# Note:  Clarity of instruction is favored over script speed or robustness.
#        It is not idempotent.

# Show commands as executed, error out on failure or undefined variables
set -eux

# Note the local domain (Network -> DHCP & DNS -> General Settings)
lan_domain=$(uci get 'dhcp.@dnsmasq[0].domain')

# Note the LAN network address (Network -> Interfaces -> LAN -> IPv4 address)
lan_address=$(uci get network.lan.ipaddr)

# Update the package list (System -> Software -> Update lists)
opkg update

# Install unbound (System -> Software -> Find package: unbound -> Install)
opkg install unbound # Ignore error that it can't listen on port 53

# Move dnsmasq to port 53535 where it will still serve local DNS from DHCP
# Network -> DHCP & DNS -> Advanced Settings -> DNS server port to 53535
uci set 'dhcp.@dnsmasq[0].port=53535'

# Configure dnsmasq to send a DNS Server DHCP option with its LAN IP
# since it does not do this by default when port is configured.
uci add_list "dhcp.lan.dhcp_option=option:dns-server,$lan_address"

# Save & Apply (will restart dnsmasq, DNS unreachable until unbound is up)
uci commit

# Allow unbound to query dnsmasq on the loopback address
# by adding 'do-not-query-localhost: no' to server section
sed -i '/^server:/a\	do-not-query-localhost: no' /etc/unbound/unbound.conf

# Convert the network address to a Reverse DNS domain
case $(uci get network.lan.netmask) in ip_to_rdns='\3.\2.\' ;; ip_to_rdns='\2.\' ;; ip_to_rdns='\' ;;
    *) echo 'More complex rDNS configuration required.' >&2 ; exit 1 ;;
lan_rdns_domain=$(echo "$lan_address" | \
    sed -E "s/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/$ip_to_rdns/")

# Check if the local addresses are in a private address range (very common)
case "$lan_address" in
    0.*) ip_to_priv_rdns='' ;;
    10.*) ip_to_priv_rdns='' ;;
    169.254.*) ip_to_priv_rdns='' ;;
    172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) ip_to_priv_rdns='\' ;;
    192.0.2.*) ip_to_priv_rdns='' ;;
    192.168.*) ip_to_priv_rdns='' ;;
    198.51.100.*) ip_to_priv_rdns='' ;;
    203.0.113.*) ip_to_priv_rdns='' ;;
if [ -n "${ip_to_priv_rdns-}" ] ; then
    # Disable default "does not exist" reply for private address ranges
    # by adding 'local-zone "$lan_domain" nodefault' to server section
    # Note that this must be on RFC 1918/5735/5737 boundary,
    # this is only equal to $lan_rdns_domain when netmask covers whole range.
    lan_priv_rdns_domain=$(echo "$lan_address" | \
        sed -E "s/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/$ip_to_priv_rdns/")
    sed -i "/^server:/a\	local-zone: \"$lan_priv_rdns_domain\" nodefault"  \

# Ignore DNSSEC chain of trust for the local domain
# by adding 'domain-insecure: "$lan_domain"' to server section
sed -i "/^server:/a\	domain-insecure: \"$lan_domain\"" /etc/unbound/unbound.conf

# Ignore DNSSEC chain of trust for the local reverse domain
# by adding 'domain-insecure: "$lan_rdns_domain"' to server section
sed -i "/^server:/a\	domain-insecure: \"$lan_rdns_domain\"" /etc/unbound/unbound.conf

# Add a stub zone to dnsmasq for the local domain to the unbound configuration
cat >> /etc/unbound/unbound.conf <<DNS_STUB_ZONE
	name: "$lan_domain"

# Add a stub zone to dnsmasq for the local reverse domain to unbound.conf
cat >> /etc/unbound/unbound.conf <<RDNS_STUB_ZONE
	name: "$lan_rdns_domain"

# Optionally enable DNS Rebinding protection by uncommenting private-address
# configuration and adding 'private-domain: "$lan_domain"' to server section
sed -E -i \
    -e 's/(# )?private-address:/private-address:/' \
    -e "/^server:/a\	private-domain: \"$lan_domain\"" \

# Restart (or start) unbound (System -> Startup -> unbound -> Restart)
/etc/init.d/unbound restart

The resulting configuration (with defaults and comments removed) should look something like:

	do-not-query-localhost: no
	domain-insecure: ""
	domain-insecure: "example.local"
	local-zone: "" nodefault
	private-address: fd00::/8
	private-address: fe80::/10
	private-domain: "example.local"

	name: "example.local"

	name: ""


After completing the above steps, DNS should be working for both local and global addresses. If not, here are some suggested troubleshooting steps:

Resolution can be attempted from the OpenWrt system by running nslookup and nslookup Unfortunately, the nslookup output does not distinguish between no response and a negative response, which significantly reduces its usefulness for debugging. A much more powerful lookup tool is DiG from the bind-dig package. To use it run dig @, add -p 53535 to query the Dnsmasq port, or add -x with an IP in place of the domain to do a reverse lookup.

No Response

If Unbound is not responding to any request, try restarting the service with /etc/init.d/unbound restart and checking the system log for errors logread | tail.

Negative Response for Local Only

If the local domain or addresses result in negative responses, check that they are resolved correctly by Dnsmasq on port 53535. If so, check that the domain appears in domain-insecure, local-zone (which may be a suffix and must match a predefined zone), and as a name in stub-zone.

Failures for DNSSEC-Secured Domains

If domains which use DNSSEC fail to resolve while other domains work, check that the system time is correct. Time skew can cause validation failures. If the time is incorrect, check the NTP client configuration.

Further Additions


It is relatively straightforward to extend the above configuration for IPv6. Forward resolution (from local domain to IPv6 address) does not require any additional changes to Unbound, although it may require configuration changes to Dnsmasq. See IPv6 DNS.

To configure reverse DNS for IPv6: Determine the rDNS domain from the IPv6 address prefix by reversing the nibbles and appending “”, add domain-insecure: $lan6_rdns_domain, local-zone: $lan6_rdns_domain nodefault if it is in a private range (be sure to use a preconfigured range), and add stub-zone with name: “$lan6_rdns_domain” in the same way as $lan_rdns_domain above.

docs/guide-user/services/dns/unbound.txt · Last modified: 2018/07/09 06:03 by ericluehrsen1