This SOP is the final, stable operating procedure for a small production homelab using WireGuard, AdGuard Home, Docker monitoring, and UFW. It reflects all fixes, decisions, and lessons learned.
1) Design Goals
- Secure private management traffic
- No performance impact on internet traffic
- Public DNS via AdGuard Home (intentional)
- No public exposure of admin or Docker APIs
- Simple, predictable routing and firewall rules
2) Final Network Model
- Main Server: WireGuard server
- Raspberry Pi 1 / 2: WireGuard clients
- VPN Subnet:
10.8.0.0/24 - Tunnel Mode: Split tunnel (VPN traffic only)
- DNS: Local (AdGuard Home on Pi)
- IPv6: Disabled
- Firewall: UFW (IPv4 only)
3) WireGuard Configuration (FINAL)
Server (/etc/wireguard/wg0.conf)
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = PLEASE_PUT_YOUR_SERVER_PRIVATE_KEY
[Peer]
PublicKey = PLEASE_PUT_YOUR_PI1_PUBLIC_KEY
AllowedIPs = 10.8.0.2/32
[Peer]
PublicKey = PLEASE_PUT_YOUR_PI2_PUBLIC_KEY
AllowedIPs = 10.8.0.3/32
Clients (Pi1 / Pi2)
[Interface]
Address = PLEASE_PUT_YOUR_PI_WG_IP
PrivateKey = PLEASE_PUT_YOUR_PI_PRIVATE_KEY
# No DNS line (AdGuard runs locally)
[Peer]
PublicKey = PLEASE_PUT_YOUR_SERVER_PUBLIC_KEY
Endpoint = PLEASE_PUT_YOUR_SERVER_PUBLIC_IP_OR_DNS:51820
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25
✅ Critical rule: AllowedIPs = 10.8.0.0/24
❌ Never use /0 unless intentionally building a full-tunnel VPN.
4) Routing Verification (MANDATORY)
On each client:
ip route
Expected:
- Default route → LAN gateway
10.8.0.0/24→wg0
If default route points to wg0, stop and fix before continuing.
5) DNS Model (FINAL)
- AdGuard Home runs locally on a Raspberry Pi
- WireGuard must not override DNS
- No
DNS=entry in any WireGuard config
Public DNS services:
53/udp53/tcp853/tcp(DNS-over-TLS)
WireGuard DNS access is also allowed for internal clients.
6) IPv6 Policy (FINAL)
IPv6 is permanently disabled to reduce complexity and avoid dual-stack edge cases.
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
UFW IPv6 handling is disabled:
IPV6=no
7) Firewall Policy (UFW — FINAL)
Default Policy
ufw default deny incoming
ufw default allow outgoing
Publicly Exposed Ports (INTENTIONAL)
ufw allow 22/tcp # SSH (rate-limited or WG-only)
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw allow 51820/udp # WireGuard
ufw allow 53/udp # DNS
ufw allow 53/tcp # DNS
ufw allow 853/tcp # DNS-over-TLS
WireGuard Internal Traffic
ufw allow in on wg0
ufw allow in on wg0 to any port 53
ufw allow in on wg0 to any port 853
SSH (Choose One)
WireGuard-only (recommended):
ufw allow from 10.8.0.0/24 to any port 22 proto tcp
OR public but rate-limited:
ufw limit 22/tcp
8) Docker Monitoring Policy (FINAL)
- Docker API is never public
- Access only over WireGuard
- Prefer docker-socket-proxy
Firewall rule:
ufw allow from 10.8.0.0/24 to any port 2375
Used by Uptime Kuma for monitoring only.
9) Automation & Defense-in-Depth
- AdGuard logs feed n8n automation
- Attacking IPs are auto-blocked
- Firewall provides first-layer filtering
- Automation provides adaptive response
Always whitelist:
10.8.0.0/24- Admin IPs
- Health-check sources
10) Validation Checklist (RUN AFTER CHANGES)
wg
ip route
ping 10.8.0.1
ping 8.8.8.8
ping google.com
ufw status verbose
ss -tulpen | head -n 30
All must pass.
11) Known Pitfalls (DO NOT REPEAT)
- ❌
AllowedIPs = 0.0.0.0/0(unintended full tunnel) - ❌ Setting WireGuard DNS when AdGuard is local
- ❌ Exposing Docker ports publicly
- ❌ Using HTTPS proxies to “secure” Docker API
- ❌ Relying on UFW NAT side-effects for routing
12) Final State
- Fast internet (no tunnel overhead)
- Encrypted management traffic
- Public DNS intentionally exposed
- Minimal, auditable firewall rules
- No hidden routing or NAT dependencies
This SOP represents the final, correct configuration.
Leave a Reply