WireGuard + AdGuard Home
Why WireGuard?
WireGuard is a modern VPN protocol built into the Linux kernel. Compared to OpenVPN or IPSec it has a much smaller codebase (~4000 lines vs ~100,000), making it easier to audit and less likely to contain vulnerabilities. It’s also significantly faster and simpler to configure.
Key advantages:
- Extremely fast — minimal overhead, built into the kernel
- Simple configuration — a few lines vs hundreds for OpenVPN
- Modern cryptography — ChaCha20, Curve25519, BLAKE2
- Works great alongside other services on the same server
Prerequisites
- Ubuntu 22.04 LTS VPS with root access
- Non-root sudo user already configured
- UFW firewall active with SSH (22/tcp) allowed
- BBR congestion control enabled (recommended)
Step 1 — Install WireGuard
sudo apt update
sudo apt install -y wireguard wireguard-tools qrencode
Step 2 — Generate Server Keys
# Generate server key pair
wg genkey | \
sudo tee /etc/wireguard/server_private.key | \
wg pubkey \
| sudo tee /etc/wireguard/server_public.key
# Secure the private key
sudo chmod 600 /etc/wireguard/server_private.key
# View and save both keys
sudo cat /etc/wireguard/server_private.key # keep secret — server only
sudo cat /etc/wireguard/server_public.key # share with clients
Step 3 — Generate Client Keys
Repeat for each device. Example for two devices:
# Client 1 (laptop)
wg genkey | \
sudo tee /etc/wireguard/client1_private.key | \
wg pubkey | \
sudo tee /etc/wireguard/client1_public.key
sudo chmod 600 /etc/wireguard/client1_private.key
# Client 2 (phone)
wg genkey | \
sudo tee /etc/wireguard/client2_private.key | \
wg pubkey | \
sudo tee /etc/wireguard/client2_public.key
sudo chmod 600 /etc/wireguard/client2_private.key
Step 4 — Find Your Network Interface
Before writing the config, check your server’s network interface name:
ip route | grep default
Look for dev ethX or dev ensX in the output. Replace eth0 in all configs below with your actual interface name.
Step 5 — Create Server Configuration
sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
MTU = 1380
# IP forwarding
PostUp = sysctl -w net.ipv4.ip_forward=1
[Peer]
# Laptop
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
[Peer]
# Phone
PublicKey = <CLIENT2_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
Note: NAT and forwarding rules are handled by UFW’s
before.rules(see Step 7), not PostUp. This is cleaner and more reliable across reboots.
Step 6 — Enable IP Forwarding Permanently
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Step 7 — Configure UFW
Allow WireGuard port
sudo ufw allow 51820/udp
sudo ufw allow in on wg0 to any port 53 # AdGuard Home DNS
Edit before.rules for NAT and forwarding
sudo nano /etc/ufw/before.rules
Add this block at the very top of the file, before the *filter line:
# WireGuard NAT
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
COMMIT
Then find the *filter section and locate :ufw-before-forward - [0:0]. Add these two lines directly below it:
-A ufw-before-forward -i wg0 -j ACCEPT
-A ufw-before-forward -o wg0 -j ACCEPT
Also set the default forward policy:
sudo nano /etc/default/ufw
Change:
DEFAULT_FORWARD_POLICY="DROP"
To:
DEFAULT_FORWARD_POLICY="ACCEPT"
Reload UFW:
sudo ufw reload
Step 8 — Start WireGuard
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
# Verify
sudo wg show
You should see the wg0 interface with your server public key and peers listed.
Step 9 — Install AdGuard Home
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
Complete the setup wizard
AdGuard Home starts on port 3000 after installation. Create an SSH tunnel on your local machine to access it:
ssh -L 3000:127.0.0.1:3000 <User>@<YOUR_SERVER_IP>
Open http://localhost:3000 in your browser and complete the wizard:
| Step | Setting |
|---|---|
| Admin Web Interface | 127.0.0.1 : 3000 |
| DNS Server | 10.0.0.1 : 53 |
Setting DNS to
10.0.0.1binds AdGuard Home to the WireGuard interface only — it will never be reachable from the public internet.
After completing the wizard, set the admin password and finish.
Step 10 — Configure Encrypted Upstream DNS
Access the web UI via the SSH tunnel, then go to Settings → DNS Settings.
Set upstream DNS to:
tls://1.1.1.1 # Cloudflare DoT (fastest)
tls://1.0.0.1 # Cloudflare DoT (backup)
tls://dns.quad9.net # Quad9 DoT (malware blocking, good privacy)
Set bootstrap DNS to:
1.1.1.1
9.9.9.9
Click Test upstreams → all green → Apply.
Bind the Web UI to WireGuard Interface (Optional but Recommended)
This lets you access the UI at http://10.0.0.1:3000 whenever you’re connected to the VPN, without needing an SSH tunnel each time:
sudo systemctl stop AdGuardHome
sudo nano /opt/AdGuardHome/AdGuardHome.yaml
Find and change:
http:
address: 127.0.0.1:3000
To:
http:
address: 10.0.0.1:3000
sudo systemctl start AdGuardHome
sudo ufw allow in on wg0 to any port 3000
Step 11 — Create Client Config Files
sudo mkdir -p /etc/wireguard/clients
Client 1 — Laptop
sudo nano /etc/wireguard/clients/client1.conf
[Interface]
PrivateKey = <CLIENT1_PRIVATE_KEY>
Address = 10.0.0.2/32
DNS = 10.0.0.1
MTU = 1380
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <YOUR_SERVER_IP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Client 2 — Phone
sudo nano /etc/wireguard/clients/client2.conf
[Interface]
PrivateKey = <CLIENT2_PRIVATE_KEY>
Address = 10.0.0.3/32
DNS = 10.0.0.1
MTU = 1380
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <YOUR_SERVER_IP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
No inline comments in config files — some clients (especially iOS) fail to parse them.
Step 12 — Deploy Configs to Devices
iOS / Android — QR Code
sudo cat /etc/wireguard/clients/client2.conf | qrencode -t ansiutf8
Install the WireGuard app, tap + → Scan QR code.
Linux (SCP)
Run on your local machine:
scp <User>@<SERVER_IP>:/etc/wireguard/clients/client1.conf ~/client1.conf
sudo cp ~/client1.conf /etc/wireguard/
Step 13 — Arch Linux Client Setup
sudo pacman -S wireguard-tools
sudo cp ~/client1.conf /etc/wireguard/
sudo chmod 600 /etc/wireguard/client1.conf
Fix DNS with systemd-resolved
Arch uses systemd-resolved which intercepts DNS and ignores WireGuard’s DNS = setting by default. Add these hooks to your config:
sudo nano /etc/wireguard/client1.conf
Add to the [Interface] section:
PostUp = resolvectl dns client1 10.0.0.1; resolvectl domain client1 "~."
PostDown = resolvectl dns client1 ""; resolvectl domain client1 ""
Start the tunnel
# Start manually
sudo wg-quick up client1
# Enable on boot
sudo systemctl enable wg-quick@client1
sudo systemctl start wg-quick@client1
# Verify DNS is correct
resolvectl status client1
Expected output:
Current DNS Server: 10.0.0.1
DNS Domain: ~.
Default Route: yes
Useful commands
sudo wg show # tunnel status
sudo wg-quick up client1 # connect
sudo wg-quick down client1 # disconnect
sudo systemctl restart wg-quick@client1 # restart
Adding More Devices
Generate a new key pair on the server:
wg genkey | \
sudo tee /etc/wireguard/client3_private.key | \
wg pubkey | \
sudo tee /etc/wireguard/client3_public.key
Add a new [Peer] block to wg0.conf:
[Peer]
# New device
PublicKey = <CLIENT3_PUBLIC_KEY>
AllowedIPs = 10.0.0.4/32
Reload without dropping existing connections:
sudo wg syncconf wg0 <(sudo wg-quick strip wg0)
AdGuard Home Recommended Settings
DNS Blocklists (Filters → DNS Blocklists)
| List | Purpose |
|---|---|
| AdGuard DNS filter | Ads + trackers |
| uBlock₀ Badware risks | Malware domains |
| Phishing Army | Phishing sites |
DNS Settings (Settings → DNS Settings)
- Enable DNSSEC
- Cache size:
4194304(4MB) - Enable optimistic caching
General Settings
- Enable query log (24h retention)
- Enable statistics (7 days retention)
Per-Device Tracking (Settings → Client Settings)
Add named clients for per-device statistics:
10.0.0.2 → Laptop
10.0.0.3 → Phone
Verify Everything Works
On the server
# WireGuard status and connected peers
sudo wg show
# AdGuard Home listening on WireGuard interface only
sudo ss -tulpn | grep :53
# Must show 10.0.0.1:53 — NOT 0.0.0.0:53
# Firewall rules
sudo ufw status verbose
# All services running
sudo systemctl status wg-quick@wg0 AdGuardHome
From a connected device
- https://ifconfig.me → should show your server IP
- https://ipleak.net → DNS should show your server IP (no leaks)
- http://10.0.0.1:3000 → AdGuard Home dashboard accessible
Final Port/Service Overview
| Service | Protocol | Port | Accessible from |
|---|---|---|---|
| WireGuard | UDP | 51820 | Public internet |
| SSH | TCP | 22 | Public internet |
| AdGuard Home DNS | UDP/TCP | 53 | VPN only (10.0.0.1) |
| AdGuard Home UI | TCP | 3000 | VPN only (10.0.0.1) |
Maintenance
Update AdGuard Home
/opt/AdGuardHome/AdGuardHome --update
sudo systemctl restart AdGuardHome
Update WireGuard
sudo apt update && sudo apt upgrade wireguard-tools
Backup configs
sudo cp /etc/wireguard/wg0.conf ~/wg0-backup.conf
sudo cp /opt/AdGuardHome/AdGuardHome.yaml ~/adguard-backup.yaml
Revoke a device
Remove its [Peer] block from wg0.conf, then:
sudo wg syncconf wg0 <(sudo wg-quick strip wg0)
Key Points to Remember
- Keep
server_private.keysecure — never share it - AdGuard Home must bind to
10.0.0.1only — never0.0.0.0 - No inline comments in client config files — breaks some apps
- UFW needs both the
before.rulesNAT rules AND thewg0 port 53allow rule - Arch Linux requires
resolvectlPostUp hooks for DNS to work correctly - Use
wg syncconfto add peers without restarting the tunnel