AdGuard Home with Docker: Block Ads and Trackers for Your Whole Home Network (Linux, Windows, Mac)
Before you start
- 19 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
AdGuard Home is a free, open-source DNS server that blocks ads and trackers for every device on your network, with nothing to install on the phones, TVs and laptops themselves.
Logo: the official AdGuard Home project (github.com/AdguardTeam/AdGuardHome)
Every website, app and smart TV first asks DNS "where is this name?". AdGuard Home answers that question. If the name is on a blocklist, it says "blocked" and the ad or tracker never loads. If not, it asks a real DNS server for you. You point your router at it once and the whole house is covered.
At a glance#
- What you get: network-wide ad and tracker blocking, a query log, per-device rules, encrypted upstream DNS (DoH/DoT) and your own home names like
nas.home.arpa. - Time needed: about 20 minutes, plus a few minutes in your router.
- Difficulty: easy on a Linux box, medium if port 53 is busy (Ubuntu) or you use Windows or Mac.
- You need: Docker with Compose, a computer that stays on, and access to your router's DHCP/DNS settings.
| What | Value |
|---|---|
| Current version | v0.107.79, released 18 Aug 2026 (GitHub releases) |
| Pinned image (official, Docker Hub) | adguard/adguardhome:v0.107.79 (about 28 MiB compressed on amd64) |
| Ports | 53 TCP and UDP (DNS), 3000 (first-run wizard only), 80 (web page) |
| Official docs | adguard-dns.io/kb/adguard-home |
Why this version? We checked the GitHub issue tracker for reports about v0.107.79. We found no install failures and no data-loss reports. A few narrow bugs are open (listed under Known bugs). So this guide uses the latest stable release. Your rollback tag is v0.107.78. Do not use the beta or edge tags for DNS your family depends on.
How it fits together#
Your router tells every device to use AdGuard Home for DNS. AdGuard Home checks each lookup against its blocklists, answers "blocked" or forwards it to an encrypted upstream DNS server, and keeps a log.
Two rules to remember:
- Port 53 is the star. DNS lives on port 53, so the machine that runs AdGuard Home must be able to own it.
- It must stay on. If this machine is off, nothing on your network can look up names.
Where should it run?#
Docker Desktop on Windows or Mac is fine for trying AdGuard Home. A DNS server for the whole house belongs on a Linux machine that is always on, or on the native program.
| Where | Good for | Why |
|---|---|---|
| Linux (mini PC, Raspberry Pi, NAS, small server) | The real thing | Always on, and host networking works, so AdGuard Home sees each device's real IP |
| Windows or Mac with Docker Desktop | A test run | The docs say host networking "is not supported on Docker Desktop for Mac, Docker Desktop for Windows". Without it, all devices look like one Docker address, so per-device rules do not work. Laptops also sleep |
| Windows or Mac, native program | A home DNS server on a PC that stays on | The official release archive installs as a service (see "The native option" below) |
The first two rows come from the official Docker page. The "laptops sleep" point is our advice.
Before you start#
You need Docker, a fixed address for the machine and permission to change your router's DNS.
| System | What you need |
|---|---|
| Linux | Docker Engine and the Compose plugin. On Ubuntu, a short fix to free port 53 (Step 1) |
| Windows | Docker Desktop (WSL2 backend), 64-bit Windows 10 or 11. Test use only, see above |
| Mac | Docker Desktop (Apple silicon or Intel). Test use only, see above |
Check your tools:
docker --version docker compose version
Checklist:
- [ ] Port 53 (TCP and UDP) is free on the machine (Step 1 shows how to check)
- [ ] The machine has a fixed IP (a DHCP reservation in your router is enough)
- [ ] You know how to log in to your router
- [ ] You backed up any DNS settings you are about to change
Tested on 2026-10-26: not run. Commands checked against the official AdGuard Home knowledge base (read 2026-10-26).
Step 1: Make sure port 53 is free#
Only one program can own port 53. On Ubuntu, systemd-resolved already does, so you have to switch its small built-in listener off first.
Linux#
First, see who uses port 53:
sudo ss -lntup | grep ':53 '
If you see systemd-resolve on 127.0.0.53:53, free the port. This is the official method from the AdGuard Home Docker page:
sudo mkdir -p /etc/systemd/resolved.conf.d sudo tee /etc/systemd/resolved.conf.d/adguardhome.conf <<'EOT' [Resolve] DNS=127.0.0.1 DNSStubListener=no EOT sudo mv /etc/resolv.conf /etc/resolv.conf.backup sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf sudo systemctl reload-or-restart systemd-resolved
Why DNS=127.0.0.1? Without the stub listener, the old address 127.0.0.53 no longer works, so the machine itself must be told to ask AdGuard Home on 127.0.0.1.
One side effect (our note, not from the docs): while the AdGuard Home container is stopped, this machine cannot resolve names either, so docker compose pull fails. Start the container first, or comment out DNS=127.0.0.1 for the update.
Check it worked:
sudo ss -lntup | grep ':53 '
Expected: no output at all. Nothing is listening on port 53 yet.
Windows (PowerShell)#
Docker Desktop publishes the port on Windows. Check that nothing else uses it:
netstat -ano | findstr ":53 "
Check it worked: no line with 0.0.0.0:53 or 127.0.0.1:53 in LISTENING state. If there is one, note the last number (the process id) and look it up with Get-Process -Id <PID>.
Mac (Terminal)#
sudo lsof -nP -iUDP:53 -iTCP:53 -sTCP:LISTEN
Check it worked: no output. If a program is listed, stop it or use the native option below.
Step 2: Start AdGuard Home#
You create a folder, save one Compose file and start it. Data lives in ~/docker_volumes/adguardhome.
The container uses two folders, as the official page says: /opt/adguardhome/work (data: logs, statistics) and /opt/adguardhome/conf (settings, in AdGuardHome.yaml).
Linux#
mkdir -p ~/docker_volumes/adguardhome/{work,conf}
cd ~/docker_volumes/adguardhome
nano docker-compose.yml
Paste this, save, and start:
services:
adguardhome:
image: adguard/adguardhome:v0.107.79
container_name: adguardhome
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "3000:3000/tcp" # first-run wizard; remove after setup
- "80:80/tcp" # web page after setup
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
docker compose up -d
Want AdGuard Home to see every device's real IP? On Linux you can replace the ports: block with network_mode: host. The official page says --network host is how to make it "see the original client IPs". It is also needed for the built-in DHCP server (ports 67 and 68), which this guide does not use. With host mode, the web page uses whatever port you pick in the wizard.
Windows (PowerShell)#
$d = "$HOME\docker_volumes\adguardhome" New-Item -ItemType Directory -Force "$d\work", "$d\conf" | Out-Null Set-Location $d notepad docker-compose.yml
Paste the same Compose file as in the Linux tab, save, and start:
docker compose up -d
Mac (Terminal)#
Same commands as Linux, with the same Compose file:
mkdir -p ~/docker_volumes/adguardhome/{work,conf}
cd ~/docker_volumes/adguardhome
nano docker-compose.yml # paste the file from the Linux tab, save
docker compose up -d
Check it worked#
docker compose ps
Expected: adguardhome is running with ports 53, 3000 and 80 listed. Then open http://<machine-ip>:3000 (or http://127.0.0.1:3000 on the same machine). You should see the AdGuard Home setup wizard.
Potential issue: address already in use on port 53. Go back to Step 1.
First run: the setup wizard#
The wizard asks where to listen and which admin account to create. Take the defaults for the ports and set a long password.
- Open
http://<machine-ip>:3000and click Get Started. - Admin Web Interface: keep all interfaces and port 80 (that is the container's port, which the Compose file maps).
- DNS server: keep all interfaces and port 53. For a home server, "all interfaces" is what lets other devices reach it. Keep it off the internet (see Security).
- Create the admin user and a long password.
- Finish, then open
http://<machine-ip>and log in.
After this, the wizard is gone. Edit docker-compose.yml, delete the 3000:3000 line and run:
docker compose up -d
Check it worked#
Ask your new DNS server about a known ad domain (use 127.0.0.1 if you are on the same machine, otherwise the machine's IP):
nslookup doubleclick.net 127.0.0.1
Expected: an answer of 0.0.0.0 (or an empty or refused answer, depending on the blocking mode in Settings > DNS settings). A normal site such as example.org should answer with real addresses. The Query Log page in the web UI shows both lookups. The same nslookup command works in PowerShell and the Mac Terminal.
Step 3: Point your router at it#
Set AdGuard Home's address as the DNS server in your router's DHCP settings, and every device that joins or renews its lease will use it.
The official guide describes it like this: open the router's settings in your browser (the address is usually printed on the router), find the DHCP/DNS settings and enter the AdGuard Home server address. Exact menus differ by router.
- Give the AdGuard Home machine a fixed IP (a DHCP reservation in the router).
- In the router's LAN or DHCP settings, set the primary DNS to that IP.
- Leave the secondary DNS empty, or use a second AdGuard Home. Our advice: a public secondary DNS lets some devices skip your blocking.
- Reconnect a device (or turn Wi-Fi off and on) so it gets a new lease.
Some routers do not allow a custom DNS server. The official guide says that then the built-in DHCP server of AdGuard Home may help. That needs host networking on Linux, so it is a job for a second day.
The router step is the same whatever computer runs AdGuard Home. What differs is how you set a single device by hand and how you check it.
Linux#
To try one device first, set its DNS by hand (for example in your network manager) to the AdGuard Home address. Check it worked:
nslookup example.org
The Server: line must show your AdGuard Home machine's IP. Then watch the Query Log in the web UI fill up with that device's lookups.
Windows (PowerShell)#
The official guide sets it by hand like this: Control Panel, Network and Sharing Center, Change adapter settings, your connection, Properties, Internet Protocol Version 4 (TCP/IPv4), "Use the following DNS server addresses". Check it worked:
nslookup example.org
The Server: line must show your AdGuard Home machine's IP. Renewing the lease with ipconfig /renew helps after a router change.
Mac (Terminal)#
The official guide sets it by hand in System Settings, Network, your connection, Advanced, DNS tab. Check it worked:
nslookup example.org
The Server: line must show your AdGuard Home machine's IP. Phones: the official guide covers Android and iOS (Wi-Fi network settings, DNS field); mobile data cannot use a custom DNS.
Make it yours: blocklists, upstream DNS, clients and home names#
Four settings turn it from "running" into "useful".
| You want | Where in the web UI | What to do |
|---|---|---|
| More blocking | Filters > DNS blocklists | Keep the default AdGuard DNS filter and add lists slowly. Big lists use more memory |
| Private lookups | Settings > DNS settings > Upstream DNS servers | Use encrypted servers, for example https://dns.cloudflare.com/dns-query (DoH) and tls://dns.quad9.net (DoT). Put plain IP addresses in Bootstrap DNS servers so it can find them |
| Different rules per device | Settings > Client settings | Add a device by IP, CIDR, MAC or ClientID, then give it its own blocked services, safe search or upstreams |
| Home names | Filters > DNS rewrites | Map nas.home.arpa to your NAS address. home.arpa is the name reserved for home networks |
| Allow a blocked site | Filters > Custom filtering rules | Add `@@ |
Per-device rules need AdGuard Home to see each device's real IP. Behind Docker Desktop all devices share one address, so this feature does not work there.
Encrypted upstream is for the leg from AdGuard Home to the internet. The leg from your devices to AdGuard Home stays plain DNS on port 53 at home.
AdGuard Home or Pi-hole?#
Both do the same job. AdGuard Home is one small program with encrypted DNS built in; Pi-hole has the bigger community.
| AdGuard Home | Pi-hole | |
|---|---|---|
| Install | One program or one small Docker image | Docker image or install script |
| Encrypted upstream (DoH/DoT) | Built in | Usually via an extra helper such as cloudflared |
| Answers DoH/DoT to your devices | Yes (needs a certificate) | No |
| Per-device rules | Built in (Client settings) | Groups |
| Own DHCP server | Yes | Yes |
| Home names | DNS rewrites, wildcards allowed | Local DNS records |
The AdGuard Home column follows its official docs. The Pi-hole column is our general summary, not an official claim: check Pi-hole's own docs for the version you use. We have older Pi-hole posts on the site too.
The native option#
If you would rather not use Docker, the developer's release archive runs as a service on Linux, Windows and Mac.
From the official Getting started page: download the archive for your system from the latest stable release page, extract it, enter the AdGuardHome folder and install the service.
Linux and Mac:
sudo ./AdGuardHome -s install
Windows (PowerShell as administrator):
.\AdGuardHome.exe -s install
Official notes: on macOS Catalina and newer, keep the working folder inside /Applications. On Fedora, install into /usr/local/bin. Control the service with -s start, -s stop, -s restart, -s status and -s uninstall. The first start listens on port 3000 for the same wizard as above. Docker installs cannot use the built-in update button: pull a new image instead.
Put it behind HTTPS (with or without Traefik)#
This is about the admin web page only: it keeps your password off the network in plain text. DNS stays on port 53. Running only at home on one machine? You can skip this section.
Important: both routes below want ports 80 and 443 on the same machine. So AdGuard Home must stop publishing port 80 (and you do not publish 443 for it at all). DNS on port 53 stays published directly.
With Traefik#
Prerequisites: a domain, DNS pointing to the server, ports 80 and 443 open, and Traefik on the external proxy network.
docker network create proxy
Change the service like this. The labels are our example, not from the AdGuard docs (adjust names to your Traefik setup):
services:
adguardhome:
image: adguard/adguardhome:v0.107.79
container_name: adguardhome
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
networks:
- proxy
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.adguard.rule=Host(`<your-domain>`)
- traefik.http.routers.adguard.entrypoints=https
- traefik.http.routers.adguard.tls=true
- traefik.http.services.adguard.loadbalancer.server.port=80
networks:
proxy:
external: true
Without Traefik: Caddy#
Choose Caddy if you have no Traefik yet and run one small server.
| Case | Result | You do |
|---|---|---|
| Public domain | Automatic Let's Encrypt | DNS A record to the server, ports 80 and 443 open |
| Home only | tls internal (local certificate) | Trust Caddy's root certificate on each device |
If Caddy runs on the machine itself (not in Docker), publish the AdGuard Home web page on a private local port. Change the 80:80 line in your Compose file to:
- "127.0.0.1:8080:80"
Caddyfile for a public domain:
adguard.example.com {
reverse_proxy localhost:8080
}
Caddyfile for home only (add a DNS rewrite in AdGuard Home that points the name at the Caddy machine):
adguard.home.arpa {
tls internal
reverse_proxy localhost:8080
}
Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):
# Linux: on the Caddy machine sudo caddy trust # other Linux devices, with root.crt copied over sudo cp root.crt /usr/local/share/ca-certificates/caddy-root.crt && sudo update-ca-certificates # Windows (PowerShell as administrator) certutil -addstore -f ROOT root.crt # Mac sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt
Run Caddy (Linux: install from the official apt repository on caddyserver.com/docs/install first):
# Linux sudo systemctl reload caddy # Windows (PowerShell) winget install CaddyServer.Caddy caddy run # Mac brew install caddy && caddy run
Or use Docker on the same network. Remove the 80:80 line from AdGuard Home, add the proxy network to it, use reverse_proxy adguardhome:80 in the Caddyfile and save this in ~/docker_volumes/caddy (create the network once: docker network create proxy):
services:
caddy:
image: caddy:2.11.7
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy_data:/data
- ./caddy_config:/config
networks:
- proxy
networks:
proxy:
external: true
Check it worked: curl -I https://adguard.example.com answers HTTP/2 200 (or a redirect to the login page).
AdGuard Home can also serve DNS-over-HTTPS and DNS-over-TLS itself (Settings > Encryption settings, with a certificate). That is for devices outside your home. It is not needed on your own network and is not covered here.
Security and testing#
Keep it off the internet, lock the settings folder and check the basics.
- Never forward port 53, 80 or 443 on your router to this machine. An open DNS server on the internet gets abused. To use it away from home, go through a VPN such as WireGuard or Tailscale.
- Secrets stay in
conf/. The admin password hash lives inAdGuardHome.yaml. Lock the folder and never put it in git:
chmod 700 ~/docker_volumes/adguardhome/conf
- Listen only where you need to. The official security guide says that on a home network you should pick the local interface, and for a public server restrict access with the Access settings (allowed clients) and keep the default plain-DNS rate limit of 20.
- Update when security fixes appear. Several recent releases fixed security problems (see the release notes), so do not stay on one tag forever.
Tests:
docker compose ps docker compose logs --tail 100 adguardhome nslookup doubleclick.net 127.0.0.1 curl -I https://<your-domain>
Expected: container running, no errors in the log, a blocked answer for the ad domain and HTTP/2 200 (or a redirect to login) from the last command (only if you set up HTTPS).
Troubleshooting#
| Problem | Solution |
|---|---|
address already in use on port 53 when starting | Another program owns port 53. On Ubuntu follow Step 1. Find the program with sudo ss -lntup | grep ':53 ' |
| Setup page does not open on port 3000 | docker compose ps must show 3000->3000. Open the port in the host firewall |
| Devices do not use it | Renew the lease or reconnect Wi-Fi. nslookup example.org must show your server as Server: |
| A site is not blocked | Check Settings > General settings (blocking on), Filters > DNS blocklists, and that no allowlist, rewrite or custom rule overrides it (official FAQ) |
| A site is blocked by mistake | Add an allow rule such as `@@ |
| Every device shows one IP | You are behind Docker Desktop or NAT. Use a Linux host with network_mode: host, or the native program |
| "Blocked by CNAME or IP" in the log | Normal: AdGuard Home blocks trackers hidden behind another name (CNAME cloaking) |
Names stop resolving on the host after DNSStubListener=no | The container is stopped. Start it, or temporarily remove DNS=127.0.0.1 |
| Container will not start after editing settings | Edit AdGuardHome.yaml only while the container is stopped, or the running program overwrites it. Restore the file from backup if the YAML is broken |
Typical error: port 53 in use#
Error response from daemon: ... failed to bind host port ... :53/tcp: address already in use
The wording changes between Docker versions; the official page only says Docker "will fail to bind on port 53". Cause: the systemd-resolved stub listener on 127.0.0.53:53. Fix: Step 1.
Known bugs#
Open on GitHub at the time of writing (read 2026-10-26):
- #8569: the DNSCrypt server is reported broken in v0.107.79. Only matters if you serve DNSCrypt.
- #8596: optimistic caching serves records for hours after they expire. Leave optimistic cache off if you see TLS errors.
- #8658: the DNS server can deadlock on shutdown in one report.
- #8662: a request for a release on a newer Go version because of a security advisory. Watch the releases page.
- If v0.107.79 misbehaves for you, roll back by setting the tag to
v0.107.78. Back up first: settings formats can change between releases.
Updates and backups#
Docker installs do not update themselves: change the tag and recreate the container. Back up by copying the folder.
Update (Linux/Mac; on Windows use the same docker compose lines in PowerShell):
cd ~/docker_volumes/adguardhome # read the release notes, edit the image tag, then: docker compose pull && docker compose up -d
The official page's own update path is the same idea: pull the new image, stop and remove the old container, start a new one from the same command.
Backup script (Linux/Mac). It stops the container briefly so the files are consistent:
cd ~/docker_volumes/adguardhome mkdir -p ~/adguard-backups docker compose down tar czf ~/adguard-backups/adguardhome-$(date +%F).tgz -C ~/docker_volumes adguardhome docker compose up -d
Windows (PowerShell):
Set-Location "$HOME\docker_volumes\adguardhome" New-Item -ItemType Directory -Force "$HOME\adguard-backups" | Out-Null docker compose down Compress-Archive -Path "$HOME\docker_volumes\adguardhome" -DestinationPath "$HOME\adguard-backups\adguardhome-$(Get-Date -Format yyyy-MM-dd).zip" -Force docker compose up -d
Schedule: Linux/Mac cron at 03:00 (0 3 * * * <path-to-script>); Windows Task Scheduler running the same steps. A short DNS gap at 03:00 is normal.
Restore test#
Do this once, before you need it. It starts the backup on other ports so it does not clash with the real one (Linux/Mac):
mkdir -p /tmp/restore && tar xzf ~/adguard-backups/adguardhome-*.tgz -C /tmp/restore docker run --rm -d --name adguard-restore -p 5353:53/udp -p 3001:80/tcp \ -v /tmp/restore/adguardhome/work:/opt/adguardhome/work \ -v /tmp/restore/adguardhome/conf:/opt/adguardhome/conf \ adguard/adguardhome:v0.107.79 nslookup -port=5353 doubleclick.net 127.0.0.1
Open http://localhost:3001, log in and check that your settings, blocklists and query log are there. Then clean up:
docker stop adguard-restore && rm -rf /tmp/restore
Performance and resources#
AdGuard Home is light, but it is also the thing every device waits for. The official docs give no RAM or CPU numbers and we did not measure them, so measure on your own machine:
docker stats --no-stream adguardhome
Tips:
- Blocklists cost memory. Add lists one at a time and watch
docker stats. - The query log and statistics cost disk. Set the retention in Settings > General settings to what you need.
- Speed. Use more than one upstream server and the parallel-request mode in Settings > DNS settings, and enable the cache.
- Hardware. A Raspberry Pi or a small mini PC is enough for a home.
FAQ#
Can I use the latest tag? Yes, the official page uses it. We pin v0.107.79 so updates and rollbacks are your choice.
Should I use beta or edge? No. The official page calls them unstable development builds. Do not put them under the DNS of your whole family.
Will it break websites? Sometimes a login or shop page needs a domain that is on a list. Add an allow rule in Custom filtering rules.
Does it block YouTube ads? Mostly no. DNS blocking works per name, and many video ads come from the same names as the video.
Can it replace my router's DHCP server? Yes, with host networking on Linux. Start with DNS only and add DHCP later.
Can I run it on my Windows PC or Mac? For a test, yes (Docker Desktop). For the whole house, use an always-on Linux machine or the native program, because devices lose DNS whenever the PC sleeps.
Sources#
- AdGuard Home: Getting started, Docker, Setting up AdGuard Home securely, FAQ (read 2026-10-26)
- AdGuardHome on GitHub: releases (v0.107.79, 18 Aug 2026) and issues
- Docker Hub: adguard/adguardhome
- Caddy docs: automatic HTTPS, tls internal, reverse_proxy, install (Caddy 2.11.7, read 2026-10-26)
Checked against the official docs on 2026-10-26. Not run by DIY Lab Hub. Error text marked "typical" is not copied from the official docs.