Networking AWS

AdGuard Home 0.107.79 on Ubuntu 24.04 on AWS User Guide

| Product: AdGuard Home 0.107.79 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying AdGuard Home 0.107.79 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Pi-hole, AdGuard Home & DNS Server AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other DNS and time servers in the listing are described on the listing's guide hub.

AdGuard Home is a network-wide DNS server that blocks ads, trackers and malicious domains for every device that uses it as its DNS server, with no software to install on each device. Point a device, a VPC or a VPN at this instance on port 53 and its DNS lookups are filtered, logged and shown in a web dashboard.

The image installs the pinned AdGuard Home 0.107.79 release (the AdGuardHome_linux_amd64.tar.gz asset, checked against its published SHA-256) on a fully patched Ubuntu 24.04 LTS base. Unattended security upgrades stay on for your instance.

What is on the image:

  • AdGuard Home 0.107.79 (AdGuardHome.service), serving DNS on port 53 (UDP and TCP), with the self-updater turned off
  • The AdGuard admin bound to 127.0.0.1:3080 and published by nginx over HTTPS on port 443, with a certificate created on your instance's first boot; port 80 redirects to HTTPS
  • No setup wizard. First boot writes a complete configuration with an admin user whose password is unique to your instance
  • The AdGuard DNS filter enabled, so ads and trackers are blocked from the first query
  • A not an open resolver policy: DNS is answered for the local host and private address ranges only
  • A dedicated 20 GiB gp3 data volume at /var/lib/adguardhome for the configuration, query log and statistics
  • adguardhome-set-hostname, a helper to serve the admin under your own DNS name

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3 · Data volume: 20 GiB gp3

Security posture

  • Per-instance admin password. The admin password is generated on each instance's first boot and stored only as a bcrypt hash in AdGuard's configuration. The plain password is written to /root/adguardhome-credentials.txt (mode 0600, root only). No password, hash or configuration ships in the image.
  • No setup wizard, ever. AdGuard Home normally starts a first-run wizard that lets the first visitor create the admin account. On this image AdGuard and nginx refuse to start until first boot has written the configuration, so the wizard is never reachable and nothing listens on its port 3000.
  • HTTPS on first boot. nginx serves the admin on 443 with a TLS key and self-signed certificate created on your instance (TLS 1.2 and 1.3 only). Port 80 only redirects to HTTPS.
  • Fixed host name. The admin is served under a name taken from the instance metadata at first boot (or one you set), never from the request. Requests for any other host name are dropped, and cross-site form posts and WebSocket connections are refused.
  • Real client addresses. nginx passes the connecting address to AdGuard and ignores any X-Forwarded-For header a client sends.
  • Not an open resolver. AdGuard's allowed clients list ships as 127.0.0.0/8, ::1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 and fc00::/7. Queries from any other address are refused. Open resolvers are abused for DNS amplification attacks.
  • DNS stays closed until you open it. The listing security group opens SSH (22) and the admin (80, 443). Port 53 is never opened for you: you open it to your own VPC or VPN ranges.
  • No metadata access for the DNS server. AdGuard fetches blocklists and upstream answers from URLs that an administrator types in, so AdGuardHome.service and nginx.service are blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). DNS resolution and Amazon Time Sync still work.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A VPC subnet for the server, and the CIDR ranges of the clients that will use it for DNS

Recommended instance type: m5.large. A small network runs comfortably on t3.small or larger.

Connecting to your instance

Version Operating system SSH user
AdGuard Home 0.107.79 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened Pi-hole, AdGuard Home & DNS Server AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose AdGuard Home 0.107.79 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: the listing's default group allows SSH (22), HTTP (80) and HTTPS (443). Restrict all three to your own IP address. You will open DNS to your clients in Step 5.
  6. Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and the admin from your management address and DNS from your VPC, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR ranges with your own:

SG=$(aws ec2 create-security-group --group-name adguard-home \
  --description "AdGuard Home" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol udp --port 53 --cidr 10.0.0.0/16
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 53 --cidr 10.0.0.0/16
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=adguard-home}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required.

Step 2: Connect and read your admin login

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It generates the admin password, writes AdGuard's configuration, creates the HTTPS certificate and starts AdGuard and nginx. Your login is in a root-only file:

sudo cat /root/adguardhome-credentials.txt
# AdGuard Home - generated on first boot by adguardhome-firstboot.service
# These credentials are unique to this instance. Store them somewhere safe.

ADGUARD_URL=https://ec2-44-202-70-170.compute-1.amazonaws.com/
ADGUARD_USERNAME=admin
ADGUARD_PASSWORD=<ADGUARD_PASSWORD>
ADGUARD_DNS_SERVER=172.31.89.51

# Admin dashboard: open the URL above (per-instance self-signed certificate; your browser will
# warn until you install your own) and sign in with the username and password.
#
# DNS (port 53): closed in the default security group. To use this instance as a DNS server,
# open 53/udp and 53/tcp ONLY to your own VPC CIDR, peered/VPN client ranges or specific office
# IPs - never 0.0.0.0/0 - and point clients at the instance's PRIVATE IP (172.31.89.51),
# never at its public IP. AdGuard Home answers only clients in its allowed list, which ships
# restricted to loopback and the private ranges 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,
# 100.64.0.0/10 and fc00::/7. Add your own client CIDRs under
# Settings > DNS settings > Access settings.
#
# Change the hostname the admin is served as:  sudo adguardhome-set-hostname <your-dns-name>

ADGUARD_PASSWORD holds your instance's own password. ADGUARD_DNS_SERVER is the instance's private IP, the address your clients use for DNS.

Step 3: Confirm the services are running

First boot, AdGuard Home and nginx are active, the installed release is 0.107.79, and the data volume is mounted:

systemctl is-active adguardhome-firstboot.service AdGuardHome.service nginx.service
/opt/AdGuardHome/AdGuardHome --version
findmnt /var/lib/adguardhome
curl -sk https://localhost/healthz
active
active
active
AdGuard Home, version v0.107.79
TARGET               SOURCE       FSTYPE OPTIONS
/var/lib/adguardhome /dev/nvme1n1 ext4   rw,relatime
ok

/healthz answers ok without a login, so you can use it for a load balancer health check over HTTPS.

These are the only ports the instance listens on:

sudo ss -Hltnu | awk '{print $1, $5}' | sort -u
tcp *:53
tcp 0.0.0.0:22
tcp 0.0.0.0:443
tcp 0.0.0.0:80
tcp 127.0.0.1:3080
tcp [::]:22
tcp [::]:443
tcp [::]:80
udp *:53
udp 127.0.0.1:323
udp 172.31.89.51%ens5:68
udp [::1]:323

The AdGuard admin itself (3080) listens on loopback only; nginx publishes it on 443. Port 323 is chrony's local control socket and 68 is the DHCP client.

Step 4: Sign in to the dashboard

Browse to the ADGUARD_URL from Step 2, for example https://ec2-44-202-70-170.compute-1.amazonaws.com/. The certificate is self-signed, so your browser warns once; accept it, or install your own certificate (Step 8). Sign in as admin with the password from Step 2.

The AdGuard Home sign-in page served over HTTPS

The dashboard shows the number of DNS queries, how many were blocked by filters, the busiest clients and the most queried and most blocked domains.

The AdGuard Home dashboard with DNS queries, queries blocked by filters, top clients and top blocked domains

Port 80 only redirects to the HTTPS address:

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://localhost/
301 https://ec2-44-202-70-170.compute-1.amazonaws.com/

You can also check the login from the command line. A wrong password is refused by AdGuard's login handler; the instance's own password returns 200 and a session cookie:

H=$(sudo sed -n 's/^set $agh_host "\(.*\)";$/\1/p' /etc/nginx/adguardhome-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' -X POST --data '{"name":"admin","password":"not-the-password"}' "https://$H/control/login"
sudo grep '^ADGUARD_PASSWORD=' /root/adguardhome-credentials.txt | cut -d= -f2- | tr -d '\n' | python3 -c 'import json,sys; print(json.dumps({"name":"admin","password":sys.stdin.read()}))' > /tmp/agh-login.json
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' -c /tmp/agh-cookies.txt -H 'Content-Type: application/json' -X POST --data @/tmp/agh-login.json "https://$H/control/login"
grep -o agh_session /tmp/agh-cookies.txt
rm -f /tmp/agh-login.json /tmp/agh-cookies.txt
invalid username or password
200
agh_session

After five wrong passwords from one address, AdGuard locks that address out for 15 minutes.

Step 5: Open DNS to your clients

The listing's security group does not open port 53. To serve DNS, add inbound rules to the instance's security group from your own VPC or VPN CIDR ranges only, never from 0.0.0.0/0:

Protocol Port Source Purpose
UDP 53 your VPC / VPN CIDRs DNS
TCP 53 your VPC / VPN CIDRs DNS (large answers)

In the EC2 console choose the instance, open Security, choose the security group, then Edit inbound rules. Point clients at the instance's private IP (ADGUARD_DNS_SERVER in Step 2), for example in a DHCP options set for the VPC, on a VPN server, or in each client's network settings.

Test from the instance: a normal name resolves, and a known ad domain is answered with 0.0.0.0 because the AdGuard DNS filter blocks it:

dig @127.0.0.1 example.com +short
dig @127.0.0.1 ad.doubleclick.net +short
172.66.147.243
104.20.23.154
0.0.0.0

From a client inside your VPC, use the private IP instead:

dig @<private-ip> example.com

Every query appears in Query Log, with the client that asked and whether it was processed or blocked, and by which filter:

The AdGuard Home query log showing processed queries and ad domains blocked by the AdGuard DNS filter, per client

Step 6: Confirm it is not an open resolver

AdGuard answers only the clients in its allowed list. The configuration lives in /var/lib/adguardhome/AdGuardHome.yaml (root only):

sudo python3 -c 'import yaml; c = yaml.safe_load(open("/var/lib/adguardhome/AdGuardHome.yaml")); print("allowed_clients:", c["dns"]["allowed_clients"]); print("http.address:", c["http"]["address"]); print("upstream_dns:", c["dns"]["upstream_dns"])'
allowed_clients: ['127.0.0.0/8', '::1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '100.64.0.0/10', 'fc00::/7']
http.address: 127.0.0.1:3080
upstream_dns: ['https://dns10.quad9.net/dns-query']

You can prove the policy from the instance itself. This adds a temporary public test address (192.0.2.53, from the TEST-NET-1 documentation range) to the loopback interface, sends a query from it over TCP and over UDP, sends the same query from 127.0.0.1, and removes the address again. The public source is refused over TCP and gets no answer over UDP; the local one is answered:

sudo ip addr add 192.0.2.53/32 dev lo
dig +tcp -b 192.0.2.53 @192.0.2.53 example.com A +time=3 +tries=1 | grep -oE "status: [A-Z]+"
dig -b 192.0.2.53 @192.0.2.53 example.com A +time=3 +tries=1 | grep -oE "status: [A-Z]+|timed out" | head -1
dig +tcp -b 127.0.0.1 @127.0.0.1 example.com A | grep -oE "status: [A-Z]+"
sudo ip addr del 192.0.2.53/32 dev lo
status: REFUSED
timed out
status: NOERROR

To serve another network you trust, for example a VPN range that uses public addresses, add it under Settings > DNS settings > Access settings > Allowed clients and save. Never add 0.0.0.0/0. Remember to allow the same range in the security group (Step 5).

The Access settings section of AdGuard Home's DNS settings with the allowed clients list restricted to loopback and private ranges

Step 7: Choose upstream servers and blocklists

AdGuard forwards the queries it does not block to its upstream servers. The image uses Quad9's DNS-over-HTTPS service (https://dns10.quad9.net/dns-query), so queries leave the instance encrypted. Change them under Settings > DNS settings > Upstream DNS servers.

The Upstream DNS servers setting showing Quad9 DNS-over-HTTPS

Blocklists live under Filters > DNS blocklists. The AdGuard DNS filter is enabled; the AdAway Default Blocklist is listed but turned off. Add more with Add blocklist, write your own rules under Custom filtering rules, and allow specific domains under Allowlists. Blocklists update automatically.

The DNS blocklists page with the AdGuard DNS filter enabled and its rule count

Step 8: HTTPS certificate and host name

The certificate was created on your instance's first boot for the instance's public DNS name, public IP, private IP and localhost. The key is readable only by root:

sudo openssl x509 -in /etc/ssl/adguardhome/adguardhome.crt -noout -subject -dates -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/ssl/adguardhome/adguardhome.key /etc/ssl/adguardhome/adguardhome.crt /var/lib/adguardhome/AdGuardHome.yaml /root/adguardhome-credentials.txt
subject=CN = ec2-44-202-70-170.compute-1.amazonaws.com, O = cloudimg
notBefore=Oct 20 09:19:49 2026 GMT
notAfter=Jan 22 09:19:49 2029 GMT
X509v3 Subject Alternative Name: 
    DNS:ec2-44-202-70-170.compute-1.amazonaws.com, IP Address:44.202.70.170, IP Address:172.31.89.51, IP Address:127.0.0.1, DNS:localhost
600 root:root /etc/ssl/adguardhome/adguardhome.key
644 root:root /etc/ssl/adguardhome/adguardhome.crt
600 root:root /var/lib/adguardhome/AdGuardHome.yaml
600 root:root /root/adguardhome-credentials.txt

To serve the admin under your own DNS name, point the name at the instance, then run the helper. It records the name, creates a new self-signed certificate for it, updates nginx and rewrites the URL in the credentials file:

sudo adguardhome-set-hostname adguard.example.com

To use a certificate from your own certificate authority, replace /etc/ssl/adguardhome/adguardhome.key (keep it 0600 root) and /etc/ssl/adguardhome/adguardhome.crt, then run sudo systemctl reload nginx. If the instance's public address changes (for example after a stop and start without an Elastic IP), run the helper again with the new name.

Step 9: Metadata access is blocked for the DNS server

AdGuardHome.service and nginx.service carry a systemd IPAddressDeny rule for the instance metadata addresses, so nothing running inside them can reach the metadata service. Each entry is a single address, so DNS and the Amazon Time Sync Service at 169.254.169.123 keep working, as chrony shows:

systemctl show -p IPAddressDeny AdGuardHome.service nginx.service
chronyc -n sources | grep 169.254
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32

IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
^* 169.254.169.123               3   4   377     4    -40us[  -41us] +/-  522us

Maintenance

  • Admin password: the per-instance password is in /root/adguardhome-credentials.txt. AdGuard Home 0.107 has no password change page; to set a new one, stop AdGuard (sudo systemctl stop AdGuardHome), replace the password: value under users: in /var/lib/adguardhome/AdGuardHome.yaml with a bcrypt hash of your new password, start it again and update the credentials file.
  • Configuration: settings you change in the dashboard are saved to /var/lib/adguardhome/AdGuardHome.yaml. After editing the file by hand, run sudo systemctl restart AdGuardHome.
  • Storage: the configuration, query log, statistics and blocklist caches live under /var/lib/adguardhome on the dedicated data volume. Snapshot that volume to back them up, and grow it independently if you keep a long query log.
  • Upgrades: the image runs AdGuard Home with --no-check-update, so it never replaces itself. To move to a newer release, follow AdGuard's upgrade instructions for a manual install in /opt/AdGuardHome and keep a snapshot first.
  • Security patches: unattended-upgrades remains enabled so the OS keeps receiving security updates.

Security recommendations

  • Keep the security group tight. Open 53 only to the networks that use the server, and 22, 80 and 443 only to your management address.
  • Keep the allowed clients list scoped. Only add networks you trust, never 0.0.0.0/0.
  • Use your own certificate for the admin if other people sign in to it, so browsers trust it without a warning.
  • Review the query log retention under Settings > General settings if your clients' browsing history must not be kept.

Support and licensing

AdGuard Home is developed by AdGuard Software Ltd and distributed under the GNU General Public License v3.0. This image ships the unmodified upstream release binary. cloudimg provides the packaging, the hardening and not an open resolver defaults, first-boot HTTPS and per-instance credentials, and 24/7 support.

cloudimg is not affiliated with or endorsed by AdGuard Software Ltd. AdGuard is a mark of its owner and is used here only to identify the software.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.