How to Set Up AdGuard Home on OpenWrt

Set up AdGuard Home on OpenWrt while keeping dnsmasq for local DNS and routing every LAN query through encrypted, network-wide filtering.

What changed

My first version of this guide downloaded an AdGuard Home tarball into /opt, installed its own service, and made dnsmasq forward to port 5353. That worked on my old Belkin RT3200, but it is no longer how I run the network.

The current router is a GL.iNet GL-MT6000 running a recent OpenWrt snapshot. AdGuard Home now comes from OpenWrt's package repository, runs as its own unprivileged user, and is upgraded with the router's package manager. No hand-managed binary or custom init script remains.

When I audited the live router for this rewrite, it was running AdGuard Home 0.107.79. The package occupied about 32 MiB, the query data used about 20 MiB, and the process held roughly 95 MiB of RAM. That is comfortable on this router's 1 GiB of memory, but worth checking before installing it on smaller hardware.

This split matters. AdGuard Home sees the original client address, while dnsmasq remains available for .lan names and reverse lookups. It also avoids the resolver loop that can happen when dnsmasq forwards everything to AdGuard Home and AdGuard Home sends local names back to dnsmasq.

Install the OpenWrt package

OpenWrt has shipped an official AdGuard Home package since 21.02. OpenWrt 25.12 and newer use apk:

bash
apk update
apk add adguardhome

OpenWrt 21.02 through 24.10 use opkg instead:

bash
opkg update
opkg install adguardhome

Enable the service and start it:

bash
service adguardhome enable
service adguardhome start
service adguardhome status

The package owns the service and keeps its files in predictable places:

PurposePath
Binary/usr/bin/AdGuardHome
Configuration/etc/adguardhome/adguardhome.yaml
Query log and statistics/var/lib/adguardhome
OpenWrt service/etc/init.d/adguardhome

Complete the first-run wizard

Open http://<router-ip>:3000 from the LAN. The AdGuard Home wizard will ask where the web interface and DNS server should listen.

Use these values for this layout:

  • Web interface: all interfaces, port 3000
  • DNS server: all interfaces, port 54
  • Admin account: a unique username and strong password

Port 54 is intentional: dnsmasq already owns port 53. Keep both ports closed to the WAN. My router has no WAN rule for either port and its WAN input policy is REJECT; access to the dashboard comes from trusted networks only. AdGuard's security guide covers stricter bind and access rules if the service could be reached from outside the router.

Confirm that the service is listening after the wizard finishes:

bash
service adguardhome status
netstat -lntup | grep -E '(:54|:3000)'

Send client DNS to AdGuard Home

Clients already receive the router as their DNS server through DHCP, so I do not change dnsmasq's upstream server. Instead, an OpenWrt firewall redirect moves incoming DNS from port 53 to AdGuard Home on port 54.

Run this once for each client firewall zone. Start with lan:

bash
zone="lan"
rule="force_dns_${zone}"

uci -q delete "firewall.${rule}"
uci set "firewall.${rule}=redirect"
uci set "firewall.${rule}.name=Force-DNS-${zone}"
uci set "firewall.${rule}.src=${zone}"
uci set "firewall.${rule}.src_dport=53"
uci set "firewall.${rule}.dest_port=54"
uci set "firewall.${rule}.proto=tcp udp"
uci set "firewall.${rule}.target=DNAT"
uci set "firewall.${rule}.family=any"
uci set "firewall.${rule}.enabled=1"

uci commit firewall
service firewall restart

My router has a separate iot zone, so I run the same block again with zone="iot". Because that zone rejects input to the router, it also has an explicit DNS allowance:

bash
uci -q delete firewall.allow_dns_iot
uci set firewall.allow_dns_iot="rule"
uci set firewall.allow_dns_iot.name="Allow-DNS-IoT"
uci set firewall.allow_dns_iot.src="iot"
uci set firewall.allow_dns_iot.dest_port="53"
uci set firewall.allow_dns_iot.proto="tcp udp"
uci set firewall.allow_dns_iot.target="ACCEPT"

uci commit firewall
service firewall restart

Do not add 127.0.0.1#54 as a dnsmasq forwarding server in this design. AdGuard Home sends local names back to dnsmasq, so forwarding dnsmasq back to AdGuard Home would create a loop.

Configure DNS upstreams

Under Settings → DNS settings, my current default upstreams are one DNS-over-QUIC resolver and one DNS-over-TLS resolver:

text
quic://dns10.quad9.net
tls://one.one.one.one
[/lan/]127.0.0.1

The last line sends local .lan names to dnsmasq. I also set Private reverse DNS servers to 127.0.0.1, which lets AdGuard Home show names learned by dnsmasq instead of only client addresses. AdGuard Home documents the same domain-specific upstream syntax.

The rest of the settings reflect a private home network rather than a public resolver:

  • Upstream mode: parallel requests
  • DNSSEC: enabled
  • Cache: 4 MiB
  • Optimistic caching: disabled
  • Rate limit: 0
  • Query log retention: one day
  • Statistics retention: one day

I currently route a small set of Google service domains back to dnsmasq as part of a separate network policy. That exception is specific to my router, so it is deliberately absent from the reusable configuration above.

Add one blocklist

Under Filters → DNS blocklists, I use only OISD Big. One maintained list is easier to reason about than a pile of overlapping lists, produces fewer duplicate rules, and has been enough for this network.

More lists do not automatically mean better protection. Add another only when it blocks something your current list demonstrably misses, then check the query log for false positives.

Verify the path

From a device on the LAN, query the router normally:

bash
dig @<router-ip> example.org

The request should appear in AdGuard Home's query log under that device's address. A direct query to port 54 helps separate AdGuard Home problems from firewall problems:

bash
dig @<router-ip> -p 54 example.org

If the direct query works but the normal one does not, inspect the redirect. If neither works, inspect the service:

bash
service adguardhome status
logread -e AdGuardHome
netstat -lntup | grep -E '(:53|:54|:3000)'

Update and back up safely

The OpenWrt service starts AdGuard Home with its built-in updater disabled. Keep the package manager in charge of /usr/bin/AdGuardHome:

bash
apk update
apk upgrade adguardhome

Use opkg update && opkg upgrade adguardhome on older OpenWrt releases.

Upgrade this package deliberately; do not turn it into a blanket apk upgrade or opkg upgrade for the whole router. OpenWrt recommends a firmware sysupgrade when the base system needs updating so its packages stay ABI-compatible.

OpenWrt preserves /etc/adguardhome/adguardhome.yaml during a sysupgrade. The data under /var/lib/adguardhome is not part of that package keep-list, so back it up separately only if historical query logs and statistics matter to you.

The result is simpler than my old installation: OpenWrt owns the binary and service, dnsmasq keeps doing the local jobs it is good at, and the firewall sends client DNS through AdGuard Home without changing what DHCP advertises.

Let’s make the hard part feel simple.

Start a project