AdGuard Home as an optional resolver alongside dnsmasq

This page describes an arrangement for running AdGuard Home (AGH) where dnsmasq keeps operating independently as the primary resolver with AGH as an optional, opt-in resolver that individual VLANs or clients can be pointed at as desired.

You should also read AdGuard Home for more details on AGH itself, as well as the traditional installation approach. In summary, that approach is to install AGH as the 'primary' resolver, in turn pointing to dnsmasq for local names. (Slightly more detail: move dnsmasq aside to a port other than :53, run AGH on :53, then configure AGH to forward local names to dnsmasq at its new home, and finally configure dnsmasq to forcibly advertise the non-dnsmasq dns-server via DHCP.)

This works fine right up until you upgrade, when the AGH package is not installed yet. It also is a little inflexible (for example you may not want/need adblocking from all devices on the network or the openwrt host itself).

Switching things around, with dnsmasq up front and AGH following on, is also fragile in that DNS will still break when AGH's not installed (and you won't be able to install it, as you won't be able to resolve the openwrt package servers! Yes, trivially fixable but annoying nonetheless). And you also lose the AGH per-client config/reporting as everything to AGH is coming via dnsmasq/localhost.

Whilst we're here, there are even more ways of using AGH (each with drawbacks):

  • configuring dnsmasq with AGH as an upstream and additional servers; this can certainly “work” but it can also be the source of tearing your hair out. Amongst other issues, it'll only work when strict-order is set, otherwise you'll get random behaviour; you'll also suffer timeouts when the “primary” upstream is down and that's unavoidable. Sane DNS configuration entails every upstream server being consistent in that they should all give functionally the same answer to the same requests; that's clearly (intentionally) not the case for AGH and any public DNS server.
  • configure AGH on any unused port and use firewall rules to direct clients to it; i.e. client thinks it's asking the DNS server on :53 but it's redirected to AGH on :54

DNS via IPv6: if you run a dual IPv4/v6 local network, you'll be saving yourself a bit of grief if you only run AGH via IPv4. Your local clients, and AGH, will happily resolve IPv6 addresses (AAAA DNS records) via DNS over IPv4. Put simply: none of the Pv4-only config below hampers a perfect 10/10 IPv6 support score. The complexity with running an IPv6 DNS server comes down to inflexibility: IPv4 DHCP, especially on OpenWRT using dnsmasq, allows all sorts of options; vs. IPv6 which is far less flexible even with DHCPv6 (odhcp6c) and if you're using SLAAC only, you flat out can't do much at all re. selectively advertising anything. This inflexibility is a-ok if you're dedicating a whole subnet / VLAN to AGH, problems arise when you want a mixed bag of clients (as with the dhcp tagging option below).

One more thing: at least some Android devices (incl. Samsung OneUI 8) require two DNS servers to be configured. If the DHCP server only delivers one the phone will add 8.8.8.8 as the second. This will cause some of your DNS traffic to be sent to Google instead of AGH. The implementation below adds an alias for the second DNS server IP.

Below is an arrangement that essentially makes AGH “optional”, leaving the dnsmasq config operating independently of AGH for localhost. In my case I have AGH enabled for one VLAN with the others unfiltered.

The general approach is to:

  1. create a dummy interface (akin to loopback), excluded from dnsmasq
  2. run AGH on that
  3. configure dnsmasq to deliver the dummy's IP as the DHCP DNS server, for the VLANs you want to use AGH on by default
  4. clients can easily opt-out by using the router's regular interface address
  • pick any unused IP address in your network, I've used a tiny /30 with 192.168.254.254 and .253
apk add kmod-dummy
echo 'options dummy numdummies=1' >> /etc/modules.conf
echo '/etc/modules.conf' >> /etc/sysupgrade.conf

uci set network.dummy=interface
uci set network.dummy.proto='static'
uci set network.dummy.device='dummy0'
uci set network.dummy.ipaddr='192.168.254.254/30'

uci set network.@alias[-1].interface='dummy'
uci set network.@alias[-1].proto='static'
uci set network.@alias[-1].ipaddr='192.168.254.253/30'


uci commit network
/etc/init.d/network restart

optional, give it a name (note, can't create both v4+v6 via LuCI, UI only supports one address?):

cat <<EOF >> /etc/hosts
192.168.254.254   agh
EOF
  • tell dnsmasq to ignore the dummy interface:
uci add_list dhcp.@dnsmasq[0].notinterface='dummy'
uci commit dhcp
/etc/init.d/dnsmasq restart
  • in /etc/adguardhome/adguardhome.yaml, set bind_hosts to be only the above dummy IP address(es), e.g.
dns:
  bind_hosts:
    - 192.168.254.254
    - 192.168.254.253
  port: 53
  • configure AGH's upstream servers to whatever you want to use for Internet names, and localhost (default :53, dnsmasq) for your local domain and unqualified names:
tls://dns.google
[/home/]127.0.0.1
[//]127.0.0.1
  • (re)start it: service adguardhome restart
  • verify you have both dnsmasq and AGH running, each to their own interfaces, and AGH's working:
# netstat -plan | grep ':53.*LISTEN'
tcp        0      0 192.168.1.1:53          0.0.0.0:*               LISTEN      5195/dnsmasq
tcp        0      0 192.168.254.254:53      0.0.0.0:*               LISTEN      5160/AdGuardHome
tcp        0      0 192.168.254.253:53      0.0.0.0:*               LISTEN      5160/AdGuardHome
...
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN      5195/dnsmasq
...
tcp        0      0 ::1:53                  :::*                    LISTEN      5195/dnsmasq
tcp        0      0 fd00::254:254:53        :::*                    LISTEN      5160/AdGuardHome
...

# nslookup google.com agh
Server:		agh
Address:	192.168.254.254:53

Non-authoritative answer:
Name:	google.com
Address: 172.217.25.174

Non-authoritative answer:
Name:	google.com
Address: 2404:6800:4006:806::200e
uci add_list dhcp.lan.dhcp_option="6,192.168.254.254,192.168.254.253"
uci commit dhcp
/etc/init.d/dnsmasq restart

Or, instead of configuring AGH for all DHCP hosts in the VLAN, do it a client-by-client, in /etc/dnsmasq.conf:

dhcp-option=tag:agh,option:dns-server,192.168.254.254,192.168.254.253

# mac addresses of clients to use AGH:
dhcp-host=cc:08:fa:11:11:11,set:agh
dhcp-host=28:9f:04:22:22:22,set:agh

on macos, check the DNS server provided via DHCP:

% ipconfig getpacket en0 | grep domain_name_server
domain_name_server (ip_mult): {192.168.254.254, 192.168.254.253}

% nslookup google.com agh
Server:		agh
Address:	192.168.254.254#53

Non-authoritative answer:
Name:	google.com
Address: 172.217.25.174
  • aside: two DNS name lookups happen in the above: the first for agh against the system resolver (in turn, AGH and then dnsmasq) (A and AAAA; if you don't have an IPv6 configured for the dummy, you'll get a delay whilst macos runs through the search list to try and resolve it); the second is for google.com to AGH
  • Last modified: 2026/08/29 12:25
  • by benl