This page documents the complete, final configuration of a secure homelab built with WireGuard, Raspberry Pis, UFW, AdGuard Home, Fail2Ban, n8n automation, and Uptime Kuma. It is written as a from‑top‑to‑bottom reference: design intent, implementation, validation, and final security posture.
The goal is not maximum complexity, but clear, intentional security that is easy to operate and reason about.
Design Goals
- Secure all management access using WireGuard
- Preserve full internet speed (no full‑tunnel VPN)
- Expose only explicitly intended public services
- Eliminate accidental routing, NAT, and DNS side effects
- Provide monitoring, alerting, and automated response
- Keep the system auditable and maintainable
Final Architecture Overview
- Main Server: WireGuard server and central management node
- Raspberry Pi 1 & 2: WireGuard clients and service hosts
- VPN Subnet:
10.8.0.0/24 - Tunnel Mode: Split tunnel (VPN traffic only)
- DNS: Local (AdGuard Home on Raspberry Pi)
- Firewall: UFW (IPv4 only)
- IPv6: Disabled intentionally
Only traffic destined for the VPN subnet traverses WireGuard. All normal internet traffic continues to use the local gateway.
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
Raspberry Pi Clients — /etc/wireguard/wg0.conf
[Interface]
Address = PLEASE_PUT_YOUR_PI_WG_IP
PrivateKey = PLEASE_PUT_YOUR_PI_PRIVATE_KEY
# No DNS line (AdGuard Home 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
Using /0 would unintentionally create a full‑tunnel VPN and introduce NAT dependencies. The /24 mask ensures a true split tunnel.
Routing Validation (Required)
On each Raspberry Pi:
ip route
Expected output:
- Default route → LAN gateway
10.8.0.0/24→wg0
If the default route points to wg0, stop and correct the configuration before continuing.
DNS Design (AdGuard Home)
- AdGuard Home runs locally on a Raspberry Pi
- WireGuard does not override DNS
- No
DNS=directive exists in WireGuard configs
Public DNS services are intentionally exposed:
53/udp,53/tcp— DNS853/tcp— DNS‑over‑TLS
DNS logs feed automation for abuse detection and response.
IPv6 Policy
IPv6 is intentionally disabled to reduce complexity and avoid dual‑stack routing and DNS edge cases common in small environments.
Sysctl — /etc/sysctl.d/99-disable-ipv6.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
UFW IPv6 toggle — /etc/default/ufw
IPV6=no
The environment operates entirely over IPv4.
Firewall Policy (UFW — Final)
Default Policy
ufw default deny incoming
ufw default allow outgoing
Publicly Exposed (Intentional)
22/tcp— SSH (WireGuard‑only or rate‑limited)80/tcp— HTTP443/tcp— HTTPS51820/udp— WireGuard53/udp,53/tcp— DNS853/tcp— DNS‑over‑TLS
WireGuard Internal Access
ufw allow in on wg0
ufw allow in on wg0 to any port 53
ufw allow in on wg0 to any port 853
SSH Access Model
Preferred (WireGuard‑only):
ufw allow from 10.8.0.0/24 to any port 22 proto tcp
External scans and LAN SSH attempts correctly show blocked.
SSH Hardening
- SSH key‑only authentication
- Password authentication disabled
- Root login avoided or disabled
- SSH reachable only via WireGuard IPs
Ping confirms network reachability. SSH access requires correct user and authorized key placement.
Docker Monitoring (Uptime Kuma)
- Docker API is never public
- Accessed only over WireGuard
- Exposed via read‑only docker‑socket‑proxy
Firewall rule:
ufw allow from 10.8.0.0/24 to any port 2375
This allows monitoring without exposing control capabilities.
Detection, Alerting, and Automation
- Fail2Ban blocks brute‑force attempts
- AdGuard Home logs capture DNS abuse
- n8n processes events every minute
- Automated IP blocking is applied
- Alerts are delivered to Slack
- Uptime Kuma monitors hosts and containers
This provides a full detect → alert → respond pipeline.
Validation Checklist
wg
ip route
ping 10.8.0.1
ping 8.8.8.8
ping google.com
ufw status verbose
ss -tulpen | head -n 30
External port scans for SSH should show blocked. SSH access works only from WireGuard peers using WireGuard IPs.
Common Pitfalls (Avoided)
AllowedIPs = 0.0.0.0/0(unintended full tunnel)- Overriding DNS when AdGuard runs locally
- Exposing Docker APIs publicly
- Using HTTPS proxies as Docker security
- Relying on firewall NAT side effects
Final Summary
This setup results in a secure, split‑tunnel WireGuard network between a main server and multiple Raspberry Pis, while keeping performance high and avoiding unnecessary complexity.
The main server acts as the WireGuard server, and each Raspberry Pi connects as a client on a private VPN subnet (10.8.0.0/24). Only internal VPN traffic is routed through WireGuard, while normal internet traffic continues to use each device’s local gateway. This design avoids speed degradation and removes the need for NAT or full‑tunnel routing.
DNS handling is intentionally local. Because one Raspberry Pi runs AdGuard Home, WireGuard does not override system DNS settings, preventing common resolution issues and keeping behavior predictable.
IPv6 is permanently disabled to reduce complexity and avoid dual‑stack edge cases. The firewall exposes only explicitly intended services, and SSH access is restricted to WireGuard peers using key‑only authentication.
Monitoring, alerting, and automated response are handled through Uptime Kuma, Fail2Ban, and n8n, providing real‑time visibility and protection.
The final result is a fast, secure, low‑maintenance homelab with a clearly defined attack surface, intentional access paths, and documented operating procedures — designed for reliability rather than complexity.
Leave a Reply