Fully verified36,591

Networking

AdGuard Home

AdGuard Home replaces the router's DNS resolver and filters ads, trackers, and malicious domains for every device on the network at once, including TVs and phones where no blocker can be installed. One container, a web panel with statistics and a query log, plus DNS-over-HTTPS and DNS-over-TLS upstreams.

Overview

AdGuard Home is a DNS server that sits between the devices on your network and the internet and drops requests to advertising, tracking, and malicious domains. Because filtering happens at the network level, it covers TVs, set-top boxes, smart speakers, and guest devices where a browser extension cannot be installed.

Beyond filtering it works as an ordinary resolver: it supports DNS-over-HTTPS and DNS-over-TLS upstreams, caches answers, keeps a query log, and shows per-client statistics. Individual rules and schedules can be assigned to specific devices.

The recipe runs a single container with two Docker volumes and the control panel on 127.0.0.1:3000. The built-in AdGuard Home DHCP server is not included: it requires host networking or macvlan, which is incompatible with the isolated Compose network.

Security assessment

The container runs without privileged, host networking, or the Docker socket, and with no-new-privileges. The control panel is published on 127.0.0.1 only — a DNS administrator sees the browsing history of the entire network, and the panel must never be exposed without HTTPS and authentication.

The main risk is specific to DNS: a server listening on port 53 on a public interface becomes an open resolver and gets abused in DNS amplification attacks. That is why ADGUARD_DNS_BIND defaults to 127.0.0.1, and the guide tells you to set a specific LAN interface address rather than 0.0.0.0. On a VPS, DNS access should be granted over a VPN only.

The second risk concerns the data itself: the query log stores the domains every device on the network opens. Retention and client anonymization are configured in the query log settings.

Update tracking

These are stored results of separate checks, not real-time data. An unknown status means the check has not been performed yet.

New releases

Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.

Current version v0.107.79Aug 31, 2026, 6:49 PM UTC

Critical vulnerabilities

Result of a dedicated Docker image scan, not a Compose configuration check.

No critical vulnerabilities foundAug 31, 2026, 6:49 PM UTC

Breaking updates

A new major version is found by comparing version numbers. A compatibility verdict is entered by hand after reading the release notes and the migration steps.

Not checkedNot checked

compose.yaml

Upstream Compose changes relative to the verified recipe.

Upstream Compose is not trackedNot checked

Outdated Docker images

Pinned image tags checked for newer versions.

All images are currentAug 31, 2026, 6:49 PM UTC

From observation to action

Update plan

The recipe already pins the latest known release. The steps below show the evidence retained for that version.

v0.107.79v0.107.79
  1. Open the release evidence

    Open this release on GitHub ↗ The link is evidence to review, not proof of compatibility.

  2. Review the version boundary

    The version numbers do not cross a major compatibility boundary. No compatibility verdict has been recorded.

  3. Prepare image-tag changes

    No image-tag change is currently planned.

  4. Follow the documented migration shape

    Change the tag and restart. Release-specific migration steps still come from the release notes and installation guide.

  5. Protect the rollback point

    No version change is currently planned. The recipe includes a backup procedure. Restore has been tested.

  6. Verify the updated recipe

    An exact Fully verified check for this target was recorded on 2026-08-31.

Read the update procedure in the installation guide ↓

Specifications

Security and exposure

Installation levels

The status shows how fully the published recipe meets each level's requirements.

Ready

For home

A minimal setup for a local network or access through a VPN.

  • Single server
  • Local access
  • Minimum components

The recipe covers every requirement at this level.

Partial

For a VPS

A public deployment with protected access and backups.

  • Domain
  • HTTPS via reverse proxy
  • Off-server backup

You still need to add: domain, https via reverse proxy, off-server backup.

Not supported

Reliable

A setup designed for predictable operations and observability.

  • Separate database
  • Healthcheck
  • External monitoring
  • CPU and memory limits

You still need to add: separate database, external monitoring, cpu and memory limits.

Tailored to your server

Configuration generator

Answer five questions and download a ready-to-run AdGuard Home bundle.

Runs locally
01 Do you have a domain?
02 Which reverse proxy?
03 Server architecture
04 Where should data be stored?

Use an absolute path for a separate disk, such as /srv/adguard-home.

The server's LAN interface address, for example 192.168.1.10. Never use 0.0.0.0 on a VPS: an open resolver gets abused for DNS amplification.

05 Is VPN access required?

Your bundle is ready

The archive is created in your browser. Your answers are never sent anywhere.

Screenshots

Screenshots have not been added yet.

Quick start

Read the full guide first: it explains external access, backups, and data locations.

Prepare settingscp .env.example .env
Launchdocker compose up -d
Check statusdocker compose ps

Step-by-step guide

1. Check the Ubuntu or Debian server

Minimum: 1 CPU, 256 MB RAM, and 2 GB of disk. 512 MB RAM is recommended — usage grows with the cache, the query log, and the number of blocklists. You need Ubuntu 22.04+ or Debian 12+ with Docker Engine and Docker Compose v2.24+. No separate database is required.

docker --version
docker compose version

2. Free up port 53

On Ubuntu and Debian port 53 is almost always held by systemd-resolved. Check:

sudo ss -lunp | grep ':53 '

If the output mentions systemd-resolve, disable its stub listener but keep the service running — otherwise the server itself loses name resolution:

sudo mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nDNSStubListener=no\n' | sudo tee /etc/systemd/resolved.conf.d/adguardhome.conf
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
sudo ss -lunp | grep ':53 ' || echo "port 53 is free"

Pointing at /run/systemd/resolve/resolv.conf leaves the host with working upstream servers. Do not point /etc/resolv.conf at AdGuard Home itself before it is running, or the server will be left without DNS.

3. Prepare the files and variables

Put compose.yaml, .env.example, backup.sh, restore.sh, and the proxy directory into a dedicated directory:

mkdir -p ~/services/adguard-home
cd ~/services/adguard-home
cp .env.example .env
chmod 600 .env

.env defines:

  • ADGUARD_VERSION — the pinned image tag, including the v prefix;
  • ADGUARD_DNS_BIND — the address the DNS ports are published on, 127.0.0.1 by default;
  • ADGUARD_DNS_PORT — the published DNS port, normally 53;
  • ADGUARD_WEB_PORT — the local control panel port, 3000 by default;
  • ADGUARD_CONF_VOLUME — the volume holding AdGuardHome.yaml: users, upstreams, rules;
  • ADGUARD_WORK_VOLUME — the volume holding statistics, the query log, and downloaded blocklists;
  • TZ — the time zone, which drives charts and schedules.

All user data lives in those two volumes only. Nothing is stored in the recipe directory.

4. Start the container on a VPS

docker compose pull
docker compose up -d
docker compose ps

With the defaults, no port faces the outside world: both DNS and the panel are bound to 127.0.0.1. On a VPS it should stay that way — grant resolver access over a VPN (WireGuard, Tailscale) by setting ADGUARD_DNS_BIND to the VPN interface address. Never open port 53 on a public address: the server would become an open resolver and be abused for DNS amplification.

5. Complete the setup wizard

The panel is local only, so forward the port over SSH:

ssh -L 3000:127.0.0.1:3000 user@server.example

Open http://localhost:3000. In the wizard keep the web interface on port 3000 and DNS on port 53 — those match the in-container ports from compose.yaml. Choosing different ones makes the healthcheck and the port mapping disagree with the configuration. Set an administrator name and a long password, and store it in a password manager.

After the wizard, verify the server works:

docker compose exec adguard-home nslookup example.org 127.0.0.1

Local network

For the devices on your network to use filtering, DNS must listen on the LAN interface. Set the server address in .env and recreate the container:

sed -i 's/^ADGUARD_DNS_BIND=.*/ADGUARD_DNS_BIND=192.168.1.10/' .env
docker compose up -d

Do not use 0.0.0.0: on a VPS it exposes the resolver to the whole internet. Close port 53 on the external interface in the firewall and allow it from the local subnet only:

sudo ufw allow from 192.168.1.0/24 to any port 53 proto udp
sudo ufw allow from 192.168.1.0/24 to any port 53 proto tcp

Then set the server address as the DNS server in your router, which enables filtering for every device at once. The control panel stays on 127.0.0.1.

Domain and HTTPS

A reverse proxy publishes the control panel only: DNS speaks its own protocol and does not pass through an HTTP proxy. Ready-made samples live in proxy/Caddyfile, proxy/nginx.conf, and proxy/traefik.yaml; replace dns.example.com with your domain. Caddy obtains a certificate automatically, the Nginx sample assumes a Certbot certificate, and Traefik uses the letsencrypt resolver. For Traefik in a container, replace 127.0.0.1 with a host gateway address the container can reach.

The panel exposes the query history of the whole network, so restrict the domain further by IP or with basic authentication on the proxy side. This recipe does not configure AdGuard Home’s own DNS-over-HTTPS: that needs port 443 published separately and a certificate handed to the server.

Backup

Data lives in the adguard-home-conf and adguard-home-work volumes. The script archives both at once — configuration restored without statistics ends up inconsistent:

chmod +x backup.sh restore.sh
./backup.sh

The archive appears in ./backups. It contains AdGuardHome.yaml with the administrator login and password hash, so keep a copy off the server and in a protected place.

Restore

Restoring completely replaces the contents of both volumes with the chosen archive:

./restore.sh ./backups/adguard-home-YYYYMMDDTHHMMSSZ.tar.gz
docker compose ps

Before unpacking, the script creates a safety copy of the current data.

Update

Disable the built-in auto-update in the panel — the version is set by the image tag. Create a backup, read the release notes, change ADGUARD_VERSION in .env (keep the v prefix), then run:

./backup.sh
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 adguard-home

Rollback

Restore the previous ADGUARD_VERSION in .env and run docker compose pull and docker compose up -d. AdGuard Home keeps its settings in AdGuardHome.yaml and rewrites the file into its own version’s format on start, so after a rollback the older binary may fail to read the upgraded config. In that case restore the archive made before the update:

./restore.sh ./backups/adguard-home-YYYYMMDDTHHMMSSZ.tar.gz

The application has no separate database; migrations are limited to that file.

Removal

Keep the data: docker compose down. Remove the container and all data irreversibly:

docker compose down
docker volume rm adguard-home-conf adguard-home-work
rm -rf ~/services/adguard-home

Remember to restore the previous DNS server in the router and on your devices, or the network will be left without name resolution. If you disabled the stub listener, bring it back:

sudo rm /etc/systemd/resolved.conf.d/adguardhome.conf
sudo systemctl restart systemd-resolved

Sources: Docker installation, post-install setup, and configuration.

Troubleshooting

The container will not start: address already in use on port 53

The port is held by systemd-resolved or another resolver:

sudo ss -lunp | grep ':53 '
docker compose logs --tail=100 adguard-home

Disable the stub listener as described in step 2 of the guide. If another container holds the port, find it with docker ps — two DNS servers cannot share one address.

The container never becomes healthy

The healthcheck queries the panel on port 3000 inside the container:

docker compose ps
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q adguard-home)"

The most common cause is a different web interface port chosen in the setup wizard. Set it back to 3000 in AdGuardHome.yaml (the http.address key), or adjust the healthcheck and the port mapping in compose.yaml to match.

Devices on the network cannot reach DNS

Check which address the port is published on:

docker compose port adguard-home 53/udp

An answer starting with 127.0.0.1 means ADGUARD_DNS_BIND was never changed. Set the server’s LAN interface address and run docker compose up -d. Then test from a client:

nslookup example.org 192.168.1.10

An empty answer while the server is running usually means the firewall: port 53 is needed over both UDP and TCP.

Ads still show up

Make sure the device really goes through the server: its domains should appear in the query log. Many browsers and phones use their own DNS-over-HTTPS, bypassing the system settings — turn it off in the browser or block it with a router rule. Ads served from the same domain as the content (a social media feed, for example) cannot be removed by DNS filtering at all.

A site broke after enabling a filter

Find the domain in the query log, open the entry, and click unblock — an @@||domain^ rule goes into your custom exceptions. You can also check which rule matched without making a query:

curl -u admin:password 'http://127.0.0.1:3000/control/filtering/check_host?name=example.org'

After an update the panel asks to run the wizard again

The container did not find its configuration — nearly always a different volume:

docker volume inspect adguard-home-conf
docker compose config

Do not run the wizard again before you check the data path: a fresh install overwrites AdGuardHome.yaml. If the volume is gone, restore an archive with ./restore.sh.

The administrator password is lost

There is no reset command: stop the container, open AdGuardHome.yaml in the adguard-home-conf volume, replace the hash in the users block with a new bcrypt hash, and start the container. Make a backup before editing.

Official sources

Latest release: 2026-08-18 · GitHub Stars: 36,591 · metadata checked Aug 31, 2026, 3:23 PM UTC

Disclaimer

Fossary is an independent informational catalog and is not affiliated with the developers of the listed applications. We collect information from public sources and verify published recipes, but we do not develop, distribute, or control these applications.

A listing or validation status is not a requirement, endorsement, or personal recommendation to use an application. You decide whether it is suitable and use it at your own risk. We do not warrant an application's security, availability, or fitness for purpose and, to the extent permitted by law, are not liable for resulting loss or damage. Review its license, security settings, data processing, and backup procedures before installation.