Layered Security Stack
A privacy-first, full-tunnel architecture combining WireGuard, encrypted DNS, network IDS, and SIEM โ deployed on a client/server model.
๐บArchitecture Overview
Full-tunnel VPN exit node with layered defense โ IDS, SIEM, encrypted DNS, and blocklists.
โ๏ธStack Components
Each layer has a specific responsibility. Defense in depth โ no single point of failure.
WireGuard VPN
Full-tunnel client/server. All traffic โ IPv4 and IPv6 โ encrypted and routed through the VPS. Client real IP never reaches the destination.
dnscrypt-proxy
DNS-over-HTTPS server running on the VPS, served only to VPN clients. All DNS queries are encrypted inside the tunnel before leaving the VPS.
Snort IDS
Passive packet inspection on WAN, VPN, and container interfaces. Custom rules detect SSH brute force, web shells, SQLi, path traversal, and recon.
Wazuh SIEM
Centralized log and event collection. Agent on client ships FIM alerts, vulnerability data, and auth events to the manager for correlation and alerting.
OSSEC
Host-based IDS running alongside Wazuh. Monitors system files, auth logs, and processes. Active response can ban IPs or kill processes on detection.
iptables + ipset
NAT masquerade for VPN forwarding. ipset blocklists updated daily from Feodo, Emerging Threats, and Tor exit feeds. Zero-downtime atomic swaps.
fail2ban
SSH brute-force protection. Monitors auth logs and inserts iptables DROP rules after N failed attempts. Automatically expires bans.
Cloudflare Tunnel
Exposes internal web services without opening any inbound ports. Traffic arrives encrypted from Cloudflare's edge โ no public IP exposure of origin.
๐Network Flows
How traffic moves through each path in the stack.
CLIENT
โ AllowedIPs = 0.0.0.0/0, ::/0
โ ALL traffic enters tunnel
โ
โโโโบ UDP encrypted โโโโโโโโโโโบ VPS:51820
โ ChaCha20-Poly1305 โ
โ PersistentKeepalive=25s โ
โ โผ
โ iptables FORWARD (pos 1)
โ NAT POSTROUTING MASQUERADE
โ โ
โโโโ return traffic โโโโโโโโโโโ
โ โผ
โ INTERNET
โ
โโโ Your visible IP = VPS IP โ
Real IP = hidden โClient app queries DNS
โโโบ OS stub resolver
โโโบ VPN_SERVER:53
(inside encrypted tunnel)
โโโบ dnscrypt-proxy
โ
โโโบ 1.1.1.1 over HTTPS
โโโบ 9.9.9.9 over HTTPS
DNS never leaves tunnel unencrypted.
DNS server only reachable via VPN.
Verify:
dig google.com | grep SERVER
โ should show VPN server IPBrowser
โโโบ Cloudflare Edge
โ TLS termination
โ DDoS / bot filtering
โ
โ Cloudflare Tunnel (QUIC)
โ No inbound ports open
โผ
cloudflared daemon (client)
โ
โผ
Security Proxy (localhost)
โ Blocks: SQLi, SSRF,
โ path traversal, scanners
โผ
Backend (localhost only)Network traffic
โโโบ Snort IDS (passive tap)
โ WAN + VPN + docker
โ Custom rules SID 9000001โ8
โโโบ snort.alert.fast
โโโบ Wazuh / OSSEC
โโโบ Correlation
โโโบ Active response
iptables blocklists (daily update):
โโโบ Feodo C2 IPs
โโโบ ET compromised hosts
โโโบ Tor exit nodes
fail2ban: auth.log โ >5 fails โ DROP๐Key Design Decisions
Non-obvious choices and the reasoning behind them.
WireGuard: INSERT not APPEND to FORWARD chain
Docker adds a REJECT rule to the iptables FORWARD chain. Using -A (append) places your WireGuard ACCEPT rule after Docker's REJECT โ VPN traffic is silently dropped.
# Wrong โ sits after Docker's REJECT iptables -A FORWARD -i wg0 -j ACCEPT # Correct โ inserted at position 1, before Docker iptables -I FORWARD 1 -i wg0 -j ACCEPT
IPv6 ULA prefix prevents ISP address leaks
A full tunnel with only AllowedIPs = 0.0.0.0/0 still leaves IPv6 exposed. Assign a ULA prefix (fd00::/8) to the VPN interface and include ::/0 in AllowedIPs.
Multiple tunnels: specific host route wins over subnet route
When two WireGuard tunnels claim overlapping subnets, the secondary tunnel's route blackholes traffic to the VPN server's internal IP.
IFACE=$(route -n get 8.8.8.8 | awk '/interface:/{print $2}')
route add -host VPN_INTERNAL_IP -interface "$IFACE"TLS: enforce 1.2/1.3, remove legacy from default config
nginx ships a global ssl_protocols directive that includes TLS 1.0/1.1. Remove it at the global level.
ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:..."; ssl_prefer_server_ciphers off;
SSH: always maintain a tunnel-based alias
Cloud firewall rules may block port 22 from unknown source IPs. The VPN server's internal IP is only reachable through the tunnel โ bypassing external firewall rules entirely.
๐งOperational Fixes
Real issues encountered in production and how they were resolved.
Full tunnel + unreachable VPN server = total internet loss
AllowedIPs 0.0.0.0/0 routes all traffic into the tunnel. If the VPN server goes down, all traffic disappears. Fix: bring down the tunnel to restore the default gateway.
sudo wg-quick down /path/to/client.conf
Two active tunnels blackhole traffic to VPN server internal IP
Secondary tunnel claims the VPN subnet. SSH and DNS to the VPN server stop working. Fix: add a specific /32 host route through the primary tunnel interface.
DNS leaking to public fallback due to routing conflict
Same routing bug causes DNS queries to VPN server to be blackholed. OS falls back to secondary public DNS โ queries are unencrypted and outside the tunnel.
nginx serving TLS 1.0/1.1 โ SSLv2 exploit attempt detected
Snort detected an SSLv2 Client_Hello overflow attempt. Removed legacy protocols from global nginx config, enforced TLS 1.2/1.3 only, reloaded zero-downtime.
๐Technology Alternatives
This architecture is implementation-agnostic. Swap components to suit your environment.
| Layer | This Stack | Alternatives |
|---|---|---|
| VPN | WireGuard | OpenVPN, IPSec/IKEv2, Tailscale, Nebula |
| Network IDS | Snort | Suricata, Zeek, Arkime |
| SIEM | Wazuh | Elastic SIEM, Splunk, Graylog |
| Host IDS | OSSEC | Tripwire, AIDE, Falco, Samhain |
| DNS | dnscrypt-proxy | Pi-hole + DoH, Unbound, AdGuard Home |
| Firewall | iptables + ipset | nftables, pf (BSD/macOS), ufw + ipset |
| Brute-force | fail2ban | CrowdSec, DenyHosts, SSHGuard |
| Reverse proxy | nginx | Caddy, HAProxy, Traefik, Envoy |
| Tunnel / Ingress | Cloudflare Tunnel | ngrok, frp, Tailscale Funnel, bore |
| Threat Intel | Feodo + ET + Tor | AbuseIPDB, Spamhaus, AlienVault OTX |