AdGuard provides centralized DNS filtering and encrypted upstream queries for infrastructure deployments. This guide covers deploying AdGuard in Docker with DNS-over-HTTPS upstreams, integrating it with Nginx Proxy Manager and WireGuard, and securing administrative access behind VPN boundaries.
Prerequisites
The following items are required:
- Docker and Docker Compose installed on the host
- Port 53 available on the host (configuration steps provided below)
- Existing Docker networks for reverse proxy routing (backend-net) and VPN access
- WireGuard VPN deployment (such as wg-easy) if remote access is required
Checking Port 53 Availability
Before deploying AdGuard Home, verify that port 53 is not already in use. Many modern Linux distributions run systemd-resolved by default, which binds to port 53 and will prevent AdGuard from starting.
Check if port 53 is currently bound:
sudo lsof -i :53Alternatively, use ss to check for listeners on port 53:
sudo ss -tulpn | grep :53If systemd-resolved or another service appears in the output, port 53 is occupied and must be freed before proceeding.
Resolving Port 53 Conflicts on Ubuntu and Debian
Ubuntu Server and recent Debian releases use systemd-resolved as the default DNS resolver. This service binds to 127.0.0.53:53 and acts as a local DNS stub resolver. There are two approaches to resolve this conflict.
Option 1: Disable systemd-resolved Completely
This approach stops systemd-resolved entirely and frees port 53 for AdGuard Home:
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolvedAfter disabling systemd-resolved, /etc/resolv.conf must be reconfigured. Remove the existing symlink and create a new resolv.conf file:
sudo rm /etc/resolv.conf
sudo nano /etc/resolv.confAdd the following content to use a temporary upstream DNS server until AdGuard is running:
nameserver 1.1.1.1
nameserver 8.8.8.8After AdGuard is running, change this to:
nameserver 127.0.0.1Option 2: Configure systemd-resolved to Not Bind Port 53
This approach keeps systemd-resolved running but prevents it from binding to port 53. Edit the resolved configuration file:
sudo nano /etc/systemd/resolved.confAdd or modify the following lines under the [Resolve] section:
[Resolve]
DNS=127.0.0.1
DNSStubListener=noThe DNSStubListener=no directive prevents systemd-resolved from binding to port 53. Remove the existing resolv.conf symlink and create a new one pointing to the systemd-resolved runtime configuration:
sudo rm /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.confRestart systemd-resolved to apply the changes:
sudo systemctl restart systemd-resolvedVerify that port 53 is now free:
sudo lsof -i :53The command should return no output if port 53 is available.
Network Architecture
AdGuard operates at three security boundaries:
Port 53 binds to the host interface to handle DNS queries from the local system and WireGuard clients. The web interface binds to internal Docker networks or 127.0.0.1 to prevent direct public exposure. External access to the admin panel occurs only through Nginx Proxy Manager with SSL and authentication enabled. WireGuard clients receive the AdGuard container IP as their DNS server, providing automatic ad-blocking and encrypted DNS lookups for all connected devices.
Directory Structure
Create persistent storage directories on the host:
mkdir -p /opt/docker/adguard/opt/adguardhome/work
mkdir -p /opt/docker/adguard/opt/adguardhome/confCreate an environment file at /opt/docker/adguard/.env:
ROOT=/opt/docker
ADGUARD_WEB_PORT=8002Docker Compose Configuration
Create /opt/docker/adguard/docker-compose.yml with the following configuration:
version: "3.8"
networks:
backend-net:
external: true
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
volumes:
- ${ROOT}/adguard/opt/adguardhome/work:/opt/adguardhome/work
- ${ROOT}/adguard/opt/adguardhome/conf:/opt/adguardhome/conf
networks:
- backend-net
ports:
- "53:53/tcp"
- "53:53/udp"
- "${ADGUARD_WEB_PORT}:80/tcp"
healthcheck:
test: ["CMD-SHELL", "netstat -pant | grep :53 || exit 1"]
interval: 15s
timeout: 30s
retries: 3
restart: unless-stoppedInitial Setup and DNS-over-HTTPS Configuration
Start the container:
cd /opt/docker/adguard && docker compose up -dVerify the container started successfully:
docker logs adguardIf the container fails with a port binding error, return to the port 53 conflict resolution steps above.
Access the setup wizard at http://127.0.0.1:8002 through an SSH tunnel or local network connection. Configure the following settings during initial setup:
- Set the admin web interface to listen on port 80 inside the container
- Configure the DNS server to listen on 0.0.0.0 port 53
- Create administrative credentials
Configure Encrypted Upstream Resolvers
After completing the initial setup, navigate to Settings then DNS Settings in the web interface. Remove any default plain-text resolvers such as 8.8.8.8 or 1.1.1.1. Under Upstream DNS servers, add encrypted DNS-over-HTTPS endpoints:
https://dns.cloudflare-dns.com/dns-query
https://dns.quad9.net/dns-query
https://doh.mullvad.net/dns-querySet the upstream mode to Parallel requests to query all upstreams simultaneously and use the fastest response. Enable DNSSEC under DNS server configuration to prevent cache poisoning attacks. Click Test Upstreams to verify connectivity, then save the configuration.
Integration with Nginx Proxy Manager
To access the AdGuard admin interface through a secure domain without exposing port 8002 publicly, configure a proxy host in Nginx Proxy Manager:
- Create a new Proxy Host
- Set Forward Hostname to adguard (the container name on backend-net)
- Set Forward Port to 80
- Enable Block Common Exploits
- Issue a Let’s Encrypt SSL certificate for the domain
- Configure Access Lists to restrict access to local subnets or VPN IP ranges
Integration with WireGuard
Configure WireGuard to distribute the AdGuard DNS server to all VPN clients. In the wg-easy environment configuration, set:
WG_DEFAULT_DNS=10.0.0.42Replace 10.0.0.42 with the IP address of the AdGuard container on the wg network or the host internal bridge IP. This setting forces all DNS queries from WireGuard clients through AdGuard Home.
Host DNS Configuration
To force the host system to use AdGuard for local DNS resolution, edit /etc/resolv.conf:
nameserver 127.0.0.1On systems where systemd-resolved is still running with DNSStubListener disabled, the resolv.conf symlink should already point to 127.0.0.1 through the configuration applied earlier. Verify the contents:
cat /etc/resolv.confVerification
Test DNS resolution and ad-blocking from the host:
nslookup doubleclick.net 127.0.0.1AdGuard should return 0.0.0.0 for blocked domains, confirming active filtering. Check the Query Log in the AdGuard web interface after performing a test lookup. The Upstream column should display the DNS-over-HTTPS URL such as https://dns.cloudflare-dns.com/dns-query, confirming that plain-text DNS queries are not leaving the host.
Verify that WireGuard clients receive the correct DNS server by checking the connection details on a connected device. All DNS queries from VPN clients should appear in the AdGuard query log.
AI assistance is used on this site for language, formatting, and turning research into a consistent template. It is not used to perform the underlying research or verify technical claims. Every command, configuration, and step in this post is tested by hand before publication.