Educational Reference

Layered Security Stack

A privacy-first, full-tunnel architecture combining WireGuard, encrypted DNS, network IDS, and SIEM — deployed on a client/server model.

WireGuard Snort IDS Wazuh SIEM dnscrypt-proxy iptables fail2ban Cloudflare Tunnel OSSEC

🗺Architecture Overview

Full-tunnel VPN exit node with layered defense — IDS, SIEM, encrypted DNS, and blocklists.

network-topology · layered-security-stack
Client Machine
🔐
WireGuardFull Tunnel · 0.0.0.0/0
📊
Wazuh AgentSIEM · FIM · Events
👁
Snort IDSPassive · local tap
🏠
OSSEC AgentHIDS · auth logs
☁️
cloudflaredQUIC · no open ports
UDP · ChaCha20-Poly1305 · port 51820 · PersistentKeepalive 25s
▼
VPS / Exit Node · 150.x.x.x
🔐
WireGuard Server:51820 · VPN endpoint
🌐
dnscrypt-proxyDoH · :53 · VPN only
👁
Snort IDSWAN + VPN taps
🧱
iptables + ipsetNAT · 1,800+ blocks
🚫
fail2banSSH guard · auto-ban
📊
Wazuh ManagerCorrelation · alerts
🔒
nginxTLS 1.2/1.3 · HSTS
NAT MASQUERADE · iptables POSTROUTING · client IP hidden
▼
Internet
🌍
Public InternetExit IP = VPS · real IP hidden
🔒
Cloudflare EdgeDDoS · WAF · TLS
🛡
Threat FeedsFeodo · ET · Tor · daily

⚙️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.

ChaCha20-Poly1305 · UDP · AllowedIPs 0/0
🌐

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.

DoH · 1.1.1.1 · 9.9.9.9 · no plain-text DNS
👁

Snort IDS

Passive packet inspection on WAN, VPN, and container interfaces. Custom rules detect SSH brute force, web shells, SQLi, path traversal, and recon.

SID 9000001–9000008 · fast alert mode
📊

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.

FIM · Vuln detection · Active response
🏠

OSSEC

Host-based IDS running alongside Wazuh. Monitors system files, auth logs, and processes. Active response can ban IPs or kill processes on detection.

HIDS · log analysis · file integrity
🧱

iptables + ipset

NAT masquerade for VPN forwarding. ipset blocklists updated daily from Feodo, Emerging Threats, and Tor exit feeds. Zero-downtime atomic swaps.

ipset hash:ip · 1,800+ blocked IPs
🚫

fail2ban

SSH brute-force protection. Monitors auth logs and inserts iptables DROP rules after N failed attempts. Automatically expires bans.

sshd jail · iptables · auto-expire
☁️

Cloudflare Tunnel

Exposes internal web services without opening any inbound ports. Traffic arrives encrypted from Cloudflare's edge — no public IP exposure of origin.

QUIC/H2 · zero open ports · DDoS shielded

🔄Network Flows

How traffic moves through each path in the stack.

FLOW 1 — Full Tunnel (all outbound)
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 ✓
FLOW 2 — DNS (encrypted, no leaks)
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 IP
FLOW 3 — Inbound Web (Cloudflare)
Browser
  └─► 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)
FLOW 4 — Attack Detection
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.

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
RESOLVED

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.

RESOLVED

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.

RESOLVED

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.

LayerThis StackAlternatives
VPNWireGuardOpenVPN, IPSec/IKEv2, Tailscale, Nebula
Network IDSSnortSuricata, Zeek, Arkime
SIEMWazuhElastic SIEM, Splunk, Graylog
Host IDSOSSECTripwire, AIDE, Falco, Samhain
DNSdnscrypt-proxyPi-hole + DoH, Unbound, AdGuard Home
Firewalliptables + ipsetnftables, pf (BSD/macOS), ufw + ipset
Brute-forcefail2banCrowdSec, DenyHosts, SSHGuard
Reverse proxynginxCaddy, HAProxy, Traefik, Envoy
Tunnel / IngressCloudflare Tunnelngrok, frp, Tailscale Funnel, bore
Threat IntelFeodo + ET + TorAbuseIPDB, Spamhaus, AlienVault OTX