# 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.

Difficulty: Intermediate\
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

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.

> **Warning**
>
> Never run AdGuard Home on an internet server as an **open resolver** that answers everyone on port 53. Open resolvers are abused for DNS amplification attacks and by strangers. In this guide plain DNS is only reachable from your own WireGuard clients, and devices outside the VPN use DNS-over-HTTPS with an allowlist of ClientIDs.

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 `sudo` rights and SSH key login, with ufw enabled: see [Secure a new Linux server](/guides/secure-a-new-linux-server) and [Set up SSH keys](/guides/ssh-keys).
- A WireGuard VPN on the same server as in [How to set up a WireGuard VPN server](/guides/wireguard-vpn-server). This guide assumes its defaults: interface `wg0`, VPN network `10.8.0.0/24` and server address `10.8.0.1`. If you use [wg-easy](/guides/install-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.com` pointing at the server, and Caddy installed as in [Caddy as a reverse proxy](/guides/caddy-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:

```bash
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:

```bash
sudo /opt/AdGuardHome/AdGuardHome -s status
sudo ss -tlnp | grep AdGuardHome
```

The 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:

```bash
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:

```bash
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-resolved
```

Run 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:

```bash
ssh -N -L 3000:127.0.0.1:3000 user@203.0.113.10
```

Open `http://localhost:3000` and go through the wizard:

1. **Admin web interface**: set the listen interface to `127.0.0.1` (the loopback entry) and the port to `3000`. The dashboard will then only be reachable through the SSH tunnel or a proxy on the server.
2. **DNS server**: keep **All interfaces** and port `53`. The firewall and the allowlist in Step 4 decide who may use it.
3. **Authentication**: choose an admin user name and a long password from your password manager.
4. 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:

```bash
sudo ufw allow in on wg0 to any port 53
sudo ufw status verbose
```

Second, AdGuard Home's allowlist. Open **Settings**, **DNS settings**, **Access settings**, and enter these lines under **Allowed clients**, then save:

```text
127.0.0.1
::1
10.8.0.0/24
```

When 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:

```bash
# 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=1
```

The 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:

```ini
[Interface]
DNS = 10.8.0.1
```

This 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:

```bash
sudo /opt/AdGuardHome/AdGuardHome -s stop
sudo nano /opt/AdGuardHome/AdGuardHome.yaml
```

Find the existing `http:` section and its `doh:` subsection, and set only this value to `true` (leave the other keys as they are):

```yaml
http:
  doh:
    insecure_enabled: true
```

```bash
sudo /opt/AdGuardHome/AdGuardHome -s start
```

Add the DoH site to `/etc/caddy/Caddyfile`. Every other path returns 404:

```caddyfile
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:

```bash
sudo systemctl reload caddy
dig +https=/dns-query/phone @dns.example.com example.com +short
```

The 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.

> **Note**
>
> DNS-over-TLS (port 853/tcp, used by Android Private DNS) and DNS-over-QUIC (853/udp) are handled by AdGuard Home itself. They need a certificate configured under **Settings**, **Encryption settings**, and ClientIDs in host names such as `phone.dns.example.com` need a wildcard certificate. Follow AdGuard's encryption and clients documentation if you need them, and open only those ports.

## 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:

```bash
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 start
```

DNS 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:

```bash
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 start
```

## Update AdGuard Home

Read the [release notes](https://github.com/AdguardTeam/AdGuardHome/releases) and take a backup first. The dashboard shows an **Update now** button when a new version is available; the command-line equivalent is:

```bash
cd /opt/AdGuardHome
sudo ./AdGuardHome --update
sudo ./AdGuardHome -s status
```

When 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:

```bash
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](/guides/wireguard-vpn-server) or [wg-easy](/guides/install-wg-easy).
- Publish other services over HTTPS with [Caddy as a reverse proxy](/guides/caddy-reverse-proxy).
- Harden the server further with [Secure a new Linux server](/guides/secure-a-new-linux-server).
- Compare servers for a private DNS resolver on the [AdGuard Home hosting](/adguard-home-hosting) page.
- Read the [AdGuard Home knowledge base](https://adguard-dns.io/kb/adguard-home/getting-started/) 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.

---

Source: <https://hyperdc.com/guides/tutorials/install-adguard-home>\
Updated: 2026-10-09
