Step-by-step instructions for deploying the privacy router stack.
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 1 core | 2+ cores |
| RAM | 512 MB | 1 GB |
| Storage | 1 GB | 4+ GB |
| Network | 2 interfaces | 2 interfaces |
Supported Platforms:
- Raspberry Pi 4/5
- x86/x64 mini PC
- Virtual machine (any hypervisor)
- OpenWrt 23.05 or later (or compatible Linux)
- AmneziaWG packages (kmod-amneziawg, amneziawg-tools)
- AdGuard Home (latest)
- Internet connection via modem/ONT
- Existing WiFi router (to convert to AP mode)
- VPN provider account with WireGuard/AmneziaWG support
Choose your deployment path:
| Option | Architecture | Best For |
|---|---|---|
| A. Dedicated Hardware | Router replacement | Recommended - Network-wide protection, dedicated device |
| B. Virtual Machine | Router replacement | Homelab with existing hypervisor, 2 NICs available |
| C. Docker Container | VPN gateway add-on | Existing infrastructure, opt-in device protection |
All options provide the same core protection: AmneziaWG obfuscation, AdGuard DNS filtering, kill switch, and watchdog recovery. The choice is about deployment architecture:
Options A & B (OpenWrt): Full router replacement. OpenWrt becomes your network's router - handles routing, DHCP, firewall, VPN. Existing router becomes WiFi access point only. All devices protected automatically.
Option C (Docker): VPN gateway add-on. Runs on existing Docker host (NAS, server, VM). Existing router keeps all functions. Devices opt-in by pointing gateway/DNS at container.
A/B: Modem → [OpenWrt Privacy Router] → WiFi AP → All Devices Protected
C: Modem → [Existing Router] → Devices (some point to Docker container)
Default recommendation: OpenWrt (A or B) for most users - simpler architecture, network-wide protection. Docker (C) is appropriate when you want to add VPN capability alongside existing infrastructure without replacing your router.
For Raspberry Pi:
# Download OpenWrt image for your device
# https://openwrt.org/toh/raspberry_pi_foundation/raspberry_pi
# Write to SD card (Linux/macOS)
sudo dd if=openwrt-*.img of=/dev/sdX bs=4M status=progress
# Boot the Pi, connect via ethernet
ssh root@192.168.1.1For x86 Mini PC:
# Download x86/64 image from openwrt.org
# Write to USB/SSD and bootEdit /etc/config/network:
uci set network.lan=interface
uci set network.lan.device='br-lan'
uci set network.lan.proto='static'
uci set network.lan.ipaddr='192.168.1.1' # Your gateway IP
uci set network.lan.netmask='255.255.255.0'
uci set network.wan=interface
uci set network.wan.device='eth0' # Your WAN interface
uci set network.wan.proto='dhcp'
uci commit networkMethod 1: Pre-built packages (recommended)
# Check your OpenWrt version
cat /etc/openwrt_release
# Download packages from:
# https://github.com/amnezia-vpn/amneziawg-openwrt/releases
# Install dependencies
opkg update
opkg install kmod-crypto-lib-chacha20 kmod-crypto-lib-chacha20poly1305 \
kmod-crypto-lib-curve25519 kmod-udptunnel4 kmod-udptunnel6
# Install AmneziaWG (adjust filename for your version)
opkg install /tmp/kmod-amneziawg_*.ipk
opkg install /tmp/amneziawg-tools_*.ipkMethod 2: Build from source
See: https://github.com/amnezia-vpn/amneziawg-openwrt
Create config directory:
mkdir -p /etc/amneziawgCreate /etc/amneziawg/awg0.conf:
[Interface]
PrivateKey = YOUR_PRIVATE_KEY_HERE
# AmneziaWG obfuscation parameters
# These add CLIENT-SIDE obfuscation - servers don't need to support AmneziaWG.
# The defaults below work with ANY standard WireGuard server (Mullvad, IVPN, etc.)
# Source: wgtunnel compatibility mode (https://github.com/zaneschepke/wgtunnel)
Jc = 4
Jmin = 40
Jmax = 70
S1 = 0
S2 = 0
H1 = 1
H2 = 2
H3 = 3
H4 = 4
[Peer]
PublicKey = VPN_SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = VPN_SERVER_IP:51820
PersistentKeepalive = 25Set permissions:
chmod 600 /etc/amneziawg/awg0.confCreate VPN zone and kill switch:
# Create VPN zone
uci set firewall.vpn=zone
uci set firewall.vpn.name='vpn'
uci set firewall.vpn.device='awg0'
uci set firewall.vpn.input='REJECT'
uci set firewall.vpn.output='ACCEPT'
uci set firewall.vpn.forward='REJECT'
uci set firewall.vpn.masq='1'
uci set firewall.vpn.mtu_fix='1'
# Allow LAN to VPN forwarding ONLY (kill switch)
uci set firewall.lan_vpn=forwarding
uci set firewall.lan_vpn.src='lan'
uci set firewall.lan_vpn.dest='vpn'
# Ensure NO lan->wan forwarding exists (verify kill switch)
# This should already be the default - no lan->wan rule
uci commit firewallFor environments with deep packet inspection, install the profile library:
# Copy profile library
cp scripts/awg-profiles.sh /etc/amneziawg/awg-profiles.sh
chmod +x /etc/amneziawg/awg-profiles.shAvailable profiles:
| Profile | DPI Resistance | Use Case |
|---|---|---|
basic |
Medium | Home ISP, light censorship (default) |
quic |
High | Moderate DPI, traffic appears as HTTP/3 |
dns |
Medium | Environments where DNS traffic is allowed |
sip |
Medium | Environments where VoIP traffic is common |
stealth |
Maximum | Heavy censorship, aggressive DPI |
Set the profile in your scripts (watchdog and hotplug):
AWG_PROFILE="quic" # or dns, sip, stealthCreate server failover config:
# Create server list for watchdog failover
cat > /etc/amneziawg/servers.conf << 'EOF'
# Format: NAME ENDPOINT_IP PORT PUBLIC_KEY
# Each server has its OWN public key (Mullvad keys are unique per server, even same-city) —
# specify a real key for every server. "-" only reuses the base awg0.conf key; do NOT use it
# for failover servers (the endpoint switches but the key doesn't, so the handshake silently
# fails). Get each server's IP + key from your provider.
# Example (each server its own key; RFC 5737 doc IPs — replace with real values):
# us-lax-wg-001 198.51.100.10 51820 LAX_001_PUBLIC_KEY
# us-sjc-wg-001 198.51.100.12 51820 SJC_001_PUBLIC_KEY
# us-sea-wg-001 198.51.100.14 51820 SEA_001_PUBLIC_KEY
# Add your servers here:
EOF
# Edit with your provider's servers:
vi /etc/amneziawg/servers.confTip: Use 3-5 servers, ideally spread across a few cities for resilience (a single-city/provider outage still has a path out). The watchdog tries the same server first on failure, then cycles through the list. Give each server its own public key in
servers.conf— seeopenwrt/amneziawg/servers.conf.example.
Copy startup scripts:
# Copy watchdog script
cp openwrt/amneziawg/awg-watchdog.sh /etc/awg-watchdog.sh
chmod +x /etc/awg-watchdog.sh
# Edit watchdog with your values (REQUIRED):
vi /etc/awg-watchdog.sh
# Set: VPN_IP (must match Address in your provider's WireGuard config)
# Copy hotplug script (auto-starts VPN on WAN up)
mkdir -p /etc/hotplug.d/iface
cp openwrt/amneziawg/99-awg.hotplug /etc/hotplug.d/iface/99-awg
chmod +x /etc/hotplug.d/iface/99-awg
# Edit hotplug script with same VPN_IP:
vi /etc/hotplug.d/iface/99-awg
# Set: VPN_IPFor OpenWrt (init.d):
# Install init script for boot persistence
cp scripts/awg-watchdog.init /etc/init.d/awg-watchdog
chmod +x /etc/init.d/awg-watchdog
# Enable at boot
/etc/init.d/awg-watchdog enable
# Start now
/etc/init.d/awg-watchdog start
# Verify running
ps | grep awg-watchdogFor standard Linux (systemd):
# Copy systemd service files
sudo cp scripts/awg-watchdog.service /etc/systemd/system/
sudo cp scripts/adguardhome.service /etc/systemd/system/
# Reload systemd
sudo systemctl daemon-reload
# Enable and start services
sudo systemctl enable awg-watchdog adguardhome
sudo systemctl start awg-watchdog adguardhome
# Verify running
sudo systemctl status awg-watchdog
sudo systemctl status adguardhomeConfiguration values you need:
| Variable | Description | Where to Find |
|---|---|---|
VPN_IP |
Your VPN internal IP (e.g., 10.64.x.x) |
Provider's WireGuard config (Address field) |
servers.conf |
Server endpoints for failover | Provider's server list page |
IMPORTANT:
VPN_IPmust exactly match theAddressfrom your provider's WireGuard config. A mismatch is the #1 cause of watchdog crash-loops — the tunnel starts but connectivity checks fail, triggering infinite restarts. The WAN gateway is auto-detected.
# Create interface
ip link add dev awg0 type amneziawg
# Apply config (command is "awg" on OpenWrt, "amneziawg" if built from source)
awg setconf awg0 /etc/amneziawg/awg0.conf
# Add address (use your VPN internal IP)
ip address add 10.x.x.x/32 dev awg0
# Bring up
ip link set up dev awg0
# Add routes (use your VPN server IP and WAN gateway)
ip route add VPN_SERVER_IP via WAN_GATEWAY
ip route del default 2>/dev/null
ip route add default dev awg0
# Test
curl https://am.i.mullvad.net/ip
# Should show VPN exit IP, not your real IPOption 1: On OpenWrt (limited resources)
# Download AdGuard Home binary
cd /tmp
wget https://static.adguard.com/adguardhome/release/AdGuardHome_linux_arm64.tar.gz
tar xzf AdGuardHome_linux_arm64.tar.gz
mv AdGuardHome/AdGuardHome /usr/bin/
# Run setup
AdGuardHome -s install
# Access web UI at http://router-ip:3000Option 2: Separate device/container (recommended)
Deploy AdGuard Home on a separate LXC container, VM, or device:
# On Debian/Ubuntu
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
# Configure at http://adguard-ip:3000On OpenWrt:
# Push AdGuard as DNS server to clients
uci add_list dhcp.lan.dhcp_option='6,ADGUARD_IP'
uci commit dhcp
/etc/init.d/dnsmasq restartIn AdGuard web UI (Settings → DNS Settings → Upstream DNS):
# Use DNS-over-HTTPS to your VPN provider
# Mullvad: https://adblock.dns.mullvad.net/dns-query
# IVPN: https://dns.ivpn.net/dns-query
# Proton: https://dns.protonvpn.net/dns-query
https://YOUR_PROVIDER_DOH_URL/dns-query
# Or use VPN provider's plain DNS (if inside VPN tunnel)
# Mullvad: 100.64.0.4 | IVPN: 10.0.254.1 | Proton: 10.2.0.1
VPN_PROVIDER_DNS_IP
Enable:
- DNSSEC
- Parallel requests
- Cache enabled
# Disable IPv6 (prevents leaks)
uci set network.wan.ipv6='0'
uci set network.lan.ipv6='0'
uci delete network.wan6 2>/dev/null
uci commit network
# Disable IPv6 in kernel
echo 'net.ipv6.conf.all.disable_ipv6=1' >> /etc/sysctl.conf
echo 'net.ipv6.conf.default.disable_ipv6=1' >> /etc/sysctl.conf
sysctl -p
# Restrict SSH to LAN only
uci set dropbear.@dropbear[0].Interface='lan'
uci commit dropbear
/etc/init.d/dropbear restart
# Enable HTTPS for web UI (optional)
opkg update
opkg install luci-ssl
uci set uhttpd.main.redirect_https='1'
uci commit uhttpd
/etc/init.d/uhttpd restartIf your VPN server is geographically distant (>100ms latency), tune TCP buffers to avoid single-stream speed bottlenecks:
cat >> /etc/sysctl.conf << 'EOF'
# TCP buffer tuning for high-latency VPN links
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216
EOF
sysctl -pSkip this if your VPN server is nearby (<50ms) or WAN is <50 Mbps. See CONFIGURATION.md for details.
-
Set existing router to AP mode
- Disable DHCP
- Disable routing/NAT
- Set to bridge mode
- Assign static IP (e.g., 192.168.1.4)
-
Connect cables
- Modem → OpenWrt WAN port
- OpenWrt LAN port → WiFi AP
-
Verify
# Check VPN curl https://am.i.mullvad.net/ip # Check DNS (should return 0.0.0.0) nslookup doubleclick.net # Check kill switch (disconnect VPN, verify no internet) ip link set awg0 down curl google.com # Should fail ip link set awg0 up
For homelab users with existing hypervisors (Proxmox, ESXi, Hyper-V, etc.).
General requirements:
- 512MB+ RAM, 1-2 vCPUs
- Two virtual network interfaces (WAN and LAN)
- Download OpenWrt x86/64 image from openwrt.org
Network configuration:
- NIC 1 → Bridge to WAN network (connected to modem)
- NIC 2 → Bridge to LAN network (connected to your devices)
Steps:
- Download OpenWrt x86/64 combined image
- Create VM with 2 NICs bridged appropriately
- Import/attach the OpenWrt disk image
- Boot and continue from A2
Deploy AdGuard Home in a separate VM or container on the same hypervisor:
# On Debian/Ubuntu VM or container
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -vEnsure the AdGuard VM/container is on the LAN bridge so it can serve DNS to clients.
Boot sequencing (important): AdGuard must start AFTER OpenWrt's DHCP server is ready. Without this, AdGuard may fail to get an IP on boot, silently breaking DNS for the entire network.
Recommended: Use static IP for AdGuard (eliminates DHCP race condition):
# Inside the AdGuard VM/container
cat > /etc/systemd/network/eth0.network << 'EOF'
[Match]
Name=eth0
[Network]
Address=192.168.1.5/24
Gateway=192.168.1.1
DNS=127.0.0.1
EOF
systemctl restart systemd-networkdIf using DHCP: Add a startup delay so OpenWrt's dnsmasq is ready first:
- Proxmox:
pct set <CTID> -startup order=2,up=30(container starts 30s after OpenWrt) - Other hypervisors: Configure equivalent boot ordering with delay
Continue from A4 onwards - the configuration is identical to dedicated hardware once OpenWrt is running.
VPN gateway add-on for users with existing Docker infrastructure who want to add VPN capability without replacing their router.
When to choose Docker:
- You have an existing Docker host (NAS, server, VM)
- You want to keep your current router's functions
- You only need some devices protected (opt-in model)
- You're adding VPN to an existing infrastructure
When to choose OpenWrt instead:
- You want network-wide automatic protection
- You have dedicated hardware available (Pi, mini-PC)
- You prefer a simpler single-device architecture
AI-assisted setup: If using an AI assistant (Claude, GPT, etc.), give it access to the
docker/folder - it can guide you through macvlan networking and container configuration.
The Docker deployment provides:
- AmneziaWG VPN client with kill switch
- AdGuard Home for DNS filtering
- macvlan networking for LAN gateway mode
- Auto-recovery watchdog and health checks
- Comprehensive test suite (10 tests including kill switch verification)
┌────────────────────────────────────────────────────┐
│ Docker Host (Linux) │
│ ┌──────────────────────────────────────────────┐ │
│ │ privacy-router container │ │
│ │ AmneziaWG + AdGuard Home + Kill Switch │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ macvlan (192.168.1.250) │
└─────────────────────┼──────────────────────────────┘
│
LAN: 192.168.1.0/24
- Docker Engine 24.0+ with Compose V2
- Linux host with kernel 5.6+ (for WireGuard)
- LAN interface available for macvlan
- VPN subscription with WireGuard/AmneziaWG support
cd docker/
# Copy templates
cp .env.example .env
cp config/awg0.conf.example config/awg0.conf
# Edit with your values
nano .env # Set VPN_IP, VPN_ENDPOINT_IP, network config
nano config/awg0.conf # Set PrivateKey, PublicKey
# Deploy
docker compose up -d
# Verify (quick check)
docker exec privacy-router /opt/scripts/quick-test.sh
# Full validation (includes kill switch test)
docker exec privacy-router /opt/scripts/test-suite.shPoint devices to use the container as gateway and DNS:
| Setting | Value |
|---|---|
| Gateway | 192.168.1.250 (CONTAINER_LAN_IP) |
| DNS | 192.168.1.250 |
Or configure your router's DHCP to distribute these settings.
See docker/README.md for:
- Complete configuration reference
- Environment variables
- Troubleshooting guide
- Security notes
- VPN connected (check exit IP matches VPN provider)
- Kill switch working (no internet when VPN down)
- DNS resolving through AdGuard
- Ads blocked (
nslookup doubleclick.netreturns 0.0.0.0) - No DNS leak (
bootstrap_dnsuses VPN provider DNS, not public) - IPv6 disabled (no AAAA records returned)
- SSH restricted to LAN
- All services start on boot (power cycle and verify)
- Watchdog running and servers.conf populated
- VPN_IP in scripts matches provider config Address field
- Scripts not corrupted (
head -1shows#!/bin/sh, no\!) - Bypass rules use
option proto 'all'(if using bypass) - Existing router in AP mode
- TCP buffers tuned (if VPN latency >100ms)
- CONFIGURATION.md - Detailed config reference
- TROUBLESHOOTING.md - Common issues