How to install AdGuard Home on a server without an open resolver
Install AdGuard Home with the official script, fix the port 53 conflict, and serve DNS only to your WireGuard clients or over DoH with ClientIDs, never openly.
- Intermediate
- 40 min read
- Updated
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13
On this page
- Prerequisites
- Step 1 — Download, read and run the official install script
- Step 2 — Free port 53 from systemd-resolved
- Step 3 — Run the setup wizard through an SSH tunnel
- Step 4 — Allow DNS only from your own clients
- Step 5 — Use AdGuard Home from your WireGuard clients
- Step 6 — Offer DNS-over-HTTPS outside the VPN (optional)
- Back up and restore
- Update AdGuard Home
- Troubleshooting
- listen udp 0.0.0.0:53: bind: address already in use
- The server itself cannot resolve names
- WireGuard clients get no DNS answers
- DoH queries through Caddy fail
- You forgot the admin password
- Next steps
AdGuard Home is a network-wide DNS server that blocks ads, trackers and malicious domains with filter lists before your devices ever connect to them. It has a web dashboard with a query log and statistics, per-client settings, and support for encrypted DNS (DNS-over-HTTPS, DNS-over-TLS and DNS-over-QUIC). At home it usually serves the local network. On a server in a data centre it can protect your phone and laptop wherever they are, but only if you set it up carefully.
This guide installs AdGuard Home with the official install script, which puts it in /opt/AdGuardHome and registers a systemd service. You free port 53 from systemd-resolved with the documented fix, run the setup wizard through an SSH tunnel so the admin interface never faces the internet, restrict DNS to your WireGuard network with ufw and AdGuard Home's allowlist, and optionally publish DNS-over-HTTPS through Caddy. Backups, updates and troubleshooting follow at the end. An official Docker image (adguard/adguardhome) also exists, but ports published by Docker bypass ufw, so the native install is the safer main path here.
Prerequisites
- A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13. The steps work on a HyperDC Linux VPS, VDS or dedicated server with root access.
- A non-root user with
sudorights and SSH key login, with ufw enabled: see Secure a new Linux server and Set up SSH keys. - A WireGuard VPN on the same server as in How to set up a WireGuard VPN server. This guide assumes its defaults: interface
wg0, VPN network10.8.0.0/24and server address10.8.0.1. If you use wg-easy, its VPN runs inside a container; follow the AdGuard Home example in the wg-easy documentation for that case. - For the optional DNS-over-HTTPS step: a domain such as
dns.example.compointing at the server, and Caddy installed as in Caddy as a reverse proxy.
| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 1 vCPU |
| RAM | Not published | 512 MB to 1 GB |
| Disk | Not published | 2 GB for the program, filter lists, statistics and query log |
AdGuard Home does not publish minimum requirements; the right-hand column is a conservative starting point for a handful of users. Large filter lists and long query log retention need more memory and disk.
Step 1 — Download, read and run the official install script
AdGuard's official automated install is a shell script. It detects your operating system and CPU, downloads the release archive from AdGuard's download server (the release channel by default), unpacks it into /opt/AdGuardHome and runs AdGuardHome -s install to register the system service. It uses sudo for the steps that need root. Download it, read it, then run it:
cd ~
curl -fsSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh -o install-adguardhome.sh
less install-adguardhome.sh
sh install-adguardhome.sh -v-v prints verbose output. The script also accepts -c to choose a channel, -r to reinstall and -u to uninstall. The project's README shows the same script as a one-liner that pipes curl into sh -s -- -v; downloading it first lets you see what runs. Check that the service is running and waiting for setup:
sudo /opt/AdGuardHome/AdGuardHome -s status
sudo ss -tlnp | grep AdGuardHomeThe status is running, and ss shows the setup wizard on 0.0.0.0:3000. ufw keeps that port closed to the internet; do not open it.
Step 2 — Free port 53 from systemd-resolved
On Ubuntu, systemd-resolved runs a local DNS stub listener on 127.0.0.53:53, which stops AdGuard Home from listening on port 53. Check whether it is active:
sudo ss -lunp | grep ':53 'If the output shows systemd-resolve, apply the fix from AdGuard's FAQ: turn off the stub listener, point the system at AdGuard Home on 127.0.0.1, and replace /etc/resolv.conf with the file that resolved maintains:
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo tee /etc/systemd/resolved.conf.d/adguardhome.conf > /dev/null <<'EOF'
[Resolve]
DNS=127.0.0.1
DNSStubListener=no
EOF
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-resolvedRun the ss command again; systemd-resolve no longer listens on port 53. The documentation notes that setting DNS=127.0.0.1 is necessary, because the stub address stops working once the stub listener is off. The server itself now uses AdGuard Home for its own lookups, so continue with Step 3 right away. If the check shows nothing on port 53 (common on Debian, which does not use systemd-resolved by default), skip this step.
Step 3 — Run the setup wizard through an SSH tunnel
On your own computer, forward local port 3000 to the wizard over SSH and leave the command running:
ssh -N -L 3000:127.0.0.1:3000 user@203.0.113.10Open http://localhost:3000 and go through the wizard:
- Admin web interface: set the listen interface to
127.0.0.1(the loopback entry) and the port to3000. The dashboard will then only be reachable through the SSH tunnel or a proxy on the server. - DNS server: keep All interfaces and port
53. The firewall and the allowlist in Step 4 decide who may use it. - Authentication: choose an admin user name and a long password from your password manager.
- Finish the wizard and open the dashboard. The tunnel keeps working, because the dashboard now listens on
127.0.0.1:3000.
Then review the upstream resolvers under Settings, DNS settings, Upstream DNS servers. AdGuard Home forwards allowed queries to them; encrypted upstreams (addresses starting with https:// or tls://) keep those queries private on the way out. Use Test upstreams before you save, and enable filter lists under Filters, DNS blocklists.
Step 4 — Allow DNS only from your own clients
Two independent layers keep the resolver private. First, ufw: allow port 53 only on the WireGuard interface, and never open it on the public interface:
sudo ufw allow in on wg0 to any port 53
sudo ufw status verboseSecond, AdGuard Home's allowlist. Open Settings, DNS settings, Access settings, and enter these lines under Allowed clients, then save:
127.0.0.1
::1
10.8.0.0/24When this list has entries, AdGuard Home only answers the clients on it. The two loopback addresses are needed because the server itself now resolves names through AdGuard Home (Step 2). AdGuard's security guidance recommends this allowlist mode for public instances. Keep the default rate limit of 20 queries per second per client in the same settings page.
Now test from the server and from a machine that is not connected to the VPN:
# on the server
sudo apt install bind9-dnsutils
dig @127.0.0.1 example.com +short
# on another machine, outside the VPN
dig @203.0.113.10 example.com +time=3 +tries=1The first query returns an IP address. The second must time out with no servers could be reached; if it gets an answer, your resolver is open, so check the ufw rules before you continue.
Step 5 — Use AdGuard Home from your WireGuard clients
Point each WireGuard client at the server's VPN address by changing the DNS line in its [Interface] section, then reconnect:
[Interface]
DNS = 10.8.0.1This works with a full tunnel and with a split tunnel (AllowedIPs = 10.8.0.0/24), because 10.8.0.1 is inside the VPN network. Open a few websites on the client; the Query Log in the dashboard shows the queries from 10.8.0.2. To see names instead of addresses, add each device under Settings, Client settings, Add client, with its VPN address as the identifier. Per-client settings, such as different blocklists for a child's phone, are set there too.
Step 6 — Offer DNS-over-HTTPS outside the VPN (optional)
For devices that should not use the VPN, publish DNS-over-HTTPS (DoH) through Caddy. Caddy terminates TLS and forwards only the /dns-query path to AdGuard Home; the admin interface stays private. Each device identifies itself with a ClientID in the URL, which works without a wildcard certificate.
AdGuard Home must accept DoH over plain HTTP from the proxy. Stop the service and open the configuration file:
sudo /opt/AdGuardHome/AdGuardHome -s stop
sudo nano /opt/AdGuardHome/AdGuardHome.yamlFind the existing http: section and its doh: subsection, and set only this value to true (leave the other keys as they are):
http:
doh:
insecure_enabled: truesudo /opt/AdGuardHome/AdGuardHome -s startAdd the DoH site to /etc/caddy/Caddyfile. Every other path returns 404:
dns.example.com {
handle /dns-query* {
reverse_proxy 127.0.0.1:3000
}
handle {
respond 404
}
}By default AdGuard Home trusts proxies on the loopback addresses, so it logs the real client address that Caddy forwards. Next, in the dashboard, add a client under Settings, Client settings, Add client with the name phone and the identifier phone (a ClientID), and add phone as a new line under Allowed clients. Then reload Caddy and test from your computer with dig, which supports DoH in BIND 9.18 and later:
sudo systemctl reload caddy
dig +https=/dns-query/phone @dns.example.com example.com +shortThe query returns an address and appears in the query log as the phone client. On the device, use the DoH address https://dns.example.com/dns-query/phone. Requests without a ClientID from the allowlist are refused.
Back up and restore
AdGuard Home keeps its state in /opt/AdGuardHome: AdGuardHome.yaml holds all settings, including users, clients, filters and the allowlist, and data/ holds statistics, the query log and cached filter lists. Stop the service briefly for a consistent copy:
sudo mkdir -p /opt/backups
sudo /opt/AdGuardHome/AdGuardHome -s stop
sudo tar -czf /opt/backups/adguardhome-$(date +%F).tar.gz -C /opt/AdGuardHome AdGuardHome.yaml data
sudo /opt/AdGuardHome/AdGuardHome -s startDNS for the server and your clients pauses for those seconds. If the query log makes the archive too large, back up AdGuardHome.yaml alone; it is enough to rebuild the setup. Copy the archives off the server.
To restore, install AdGuard Home with the script (Step 1) and apply Step 2, then stop the service, unpack the archive over the installation and start it again. The wizard is skipped because the configuration already exists:
sudo /opt/AdGuardHome/AdGuardHome -s stop
sudo tar -xzf /opt/backups/adguardhome-2026-10-09.tar.gz -C /opt/AdGuardHome
sudo /opt/AdGuardHome/AdGuardHome -s startUpdate AdGuard Home
Read the release notes and take a backup first. The dashboard shows an Update now button when a new version is available; the command-line equivalent is:
cd /opt/AdGuardHome
sudo ./AdGuardHome --update
sudo ./AdGuardHome -s statusWhen AdGuard Home updates itself, it keeps the previous executable and configuration in a backup folder inside the installation directory. Check the version in the dashboard footer afterwards.
Troubleshooting
listen udp 0.0.0.0:53: bind: address already in use
Another program holds port 53, almost always the systemd-resolved stub listener. Run sudo ss -lunp | grep ':53 ' to see which one, apply Step 2, and restart AdGuard Home with sudo /opt/AdGuardHome/AdGuardHome -s restart.
The server itself cannot resolve names
After Step 2 the server depends on AdGuard Home. Check that the service runs (sudo /opt/AdGuardHome/AdGuardHome -s status) and that 127.0.0.1 and ::1 are in Allowed clients. As a temporary way out, restore the old file with sudo rm /etc/resolv.conf and sudo mv /etc/resolv.conf.backup /etc/resolv.conf.
WireGuard clients get no DNS answers
Check the four links in the chain: the client's DNS = 10.8.0.1 line, the tunnel itself (sudo wg show shows a recent handshake), the ufw rule 53 on wg0 in sudo ufw status, and 10.8.0.0/24 in Allowed clients. The query log shows whether queries arrive and whether they were blocked or refused.
DoH queries through Caddy fail
A 404 from Caddy means the path does not start with /dns-query. An error from AdGuard Home usually means insecure_enabled is still false (stop the service before editing the file, or your change is overwritten) or the ClientID is not in Allowed clients. Check the Caddy logs with journalctl -u caddy.
You forgot the admin password
AdGuard Home stores a bcrypt hash of the password in AdGuardHome.yaml. Generate a new hash with htpasswd from the apache2-utils package, as the documentation describes:
sudo apt install apache2-utils
htpasswd -B -C 10 -n -b admin 'a-new-long-password'The output is admin: followed by the hash. Stop the service, put the hash after password: for your user under users: in AdGuardHome.yaml, and start it again. Clear your shell history afterwards, because the command contained the password.
Next steps
- Set up or extend your VPN with a WireGuard VPN server or wg-easy.
- Publish other services over HTTPS with Caddy as a reverse proxy.
- Harden the server further with Secure a new Linux server.
- Compare servers for a private DNS resolver on the AdGuard Home hosting page.
- Read the AdGuard Home knowledge base for filtering rules, DHCP and the full configuration reference.
Frequently asked questions
Why is an open DNS resolver dangerous?
A resolver that answers anyone on the internet over UDP port 53 can be abused for DNS amplification attacks: attackers send small queries with a forged source address and your server floods the victim with large answers. It can also be used by strangers at your expense. Restrict plain DNS to your own clients.
Can I use AdGuard Home on my phone without a VPN?
Yes, with encrypted DNS. This guide sets up DNS-over-HTTPS behind Caddy with a ClientID in the URL, and the allowlist only accepts the ClientIDs you create. DNS-over-TLS, which Android Private DNS uses, also needs a certificate configured in AdGuard Home and a wildcard certificate for ClientIDs.
Why not use the Docker image?
The official adguard/adguardhome image works, but ports published by Docker bypass ufw, so a published port 53 is reachable from the internet even if ufw blocks it. The native install keeps ufw in control and also sees real client addresses, which the documentation says otherwise needs host networking.
Does AdGuard Home need port 3000 open?
No. The setup wizard listens on port 3000, and this guide reaches it through an SSH tunnel. The admin interface then listens on 127.0.0.1 only, so neither port has to be opened in the firewall.
Where are the AdGuard Home settings stored?
With the install script, everything lives in /opt/AdGuardHome: the configuration file AdGuardHome.yaml and the data folder with statistics, the query log and filter caches. Those two are what you back up.
Sources
- github.com/AdguardTeam/AdGuardHome
- raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/in…
- adguard-dns.io/kb/adguard-home/getting-started
- adguard-dns.io/kb/adguard-home/faq
- adguard-dns.io/kb/adguard-home/running-securely
- adguard-dns.io/kb/adguard-home/configuration
- adguard-dns.io/kb/adguard-home/clients
- adguard-dns.io/kb/adguard-home/encryption
- adguard-dns.io/kb/adguard-home/docker
- github.com/AdguardTeam/AdGuardHome/wiki/Configuration