Self-Hosting a WireGuard VPN on AWS: What Nobody Tells You Until You've Run One
I have spent a long career watching engineers build tunnels — IPsec in the days when a Phase 2 mismatch could eat an afternoon, SSL VPN appliances that were themselves the vulnerability, OpenVPN configs photocopied from forum posts. WireGuard is the first VPN protocol in decades that a single engineer can hold entirely in their head. So I built one properly: WireGuard via wg-easy on an Arm-based EC2 instance, tuned it until it was faster than my raw ISP path on some routes, ran it as my daily driver — and then decommissioned it deliberately, which is a phase most blog posts pretend doesn't exist.
This write-up is the whole arc, written to take you from "I followed a tutorial" to "I understand the protocol": the Layer-3 architecture, the handshake and cryptokey-routing internals, the exact configurations, the tuning that actually moved the needle, benchmark-backed comparisons against OpenVPN and IPsec, and — the part that matters most — a comprehensive, data-driven accounting of the pros, the cons, and the security concerns that the "5-minute VPN" tutorials skip. If you read only one section, read §10.
1. Why Build One at All
Three legitimate reasons, and one bad one:
Legitimate:
1. Untrusted network protection. Hotel, airport, and café Wi-Fi are hostile territory. A tunnel to infrastructure you control defeats local snooping, rogue DHCP/ARP games, and captive-portal middleboxes.
2. A stable, known egress IP. Useful for locking down admin panels, allow-listing yourself into security groups, and consistent behavior from anti-fraud systems while traveling.
3. Education. Nothing teaches routing, NAT, MTU, DNS, and kernel networking like operating a VPN people actually use. This alone justified the project.
Bad: "I want to be anonymous." A self-hosted VPN is close to the opposite of anonymity — see §10.1. Be clear about which problem you're solving before you type terraform apply, because the architecture that follows serves the first three goals and actively undermines the fourth.
2. L3 Network Architecture
The complete Layer-3 picture: clients tunnel over UDP 51820 to the EC2 host, land on the wg0 interface in 10.8.0.0/24, are forwarded and source-NAT'd out of ens5, and egress via the Elastic IP. The container runs in host network mode — one of the tuning decisions that mattered (§6). Throughout this article the public IP is shown as 203.0.113.10, a documentation address — a security write-up shouldn't publish real ones, even released ones.
CLIENTS (roaming) AWS us-west-2
┌──────────────────┐ ┌─────────────────────────────────────────┐
│ Laptop │ │ VPC 100.64.0.0/16 (RFC 6598 space) │
│ wg0: 10.8.0.2/32 │ │ ┌───────────────────────────────────┐ │
│ MTU 1420 │ Encrypted UDP │ │ EC2 t4g.micro (Arm64, AL2023) │ │
├──────────────────┤ :51820 │ │ ens5: 100.64.1.76 │ │
│ Phone │ ═══════════════════►│ │ EIP : 203.0.113.10 (1:1 NAT by │ │
│ wg0: 10.8.0.3/32 │ │ │ AWS Internet Gateway) │ │
└────────┬─────────┘ │ │ │ │
│ │ │ ┌─────────────────────────────┐ │ │
│ any network: │ │ │ wg-easy container │ │ │
│ café / hotel / LTE │ │ │ network_mode: host │ │ │
│ (sees only UDP noise) │ │ │ cap: NET_ADMIN, SYS_MODULE │ │ │
▼ │ │ │ ├─ wg0 10.8.0.1/24 :51820 │ │ │
┌──────────────┐ │ │ │ └─ web UI :51821 (locked │ │ │
│ INTERNET │ │ │ │ to trusted IP ONLY) │ │ │
└──────┬───────┘ │ │ └──────────┬──────────────────┘ │ │
│ │ │ │ decrypt │ │
│ │ │ ▼ │ │
│ │ │ Linux kernel forwarding │ │
│ RETURN / EGRESS PATH │ │ net.ipv4.ip_forward=1 │ │
│ ◄════════════════════════════│ │ FORWARD: allow 10.8.0.0/24 │ │
│ src rewritten to │ │ POSTROUTING: MASQUERADE → ens5 │ │
│ 203.0.113.10 (MASQUERADE) │ └───────────────────────────────────┘ │
▼ │ ENI: Source/Dest Check DISABLED │
destination sites └─────────────────────────────────────────┘
see ONLY the EIP
SECURITY GROUP (stateful)
├─ UDP 51820 ← 0.0.0.0/0 WireGuard (safe: silent protocol)
├─ TCP 51821 ← trusted IP/32 wg-easy admin UI
└─ TCP 22 ← trusted IP/32 SSH
Three details in that diagram carry most of the operational weight:
| Decision | Why it exists | What breaks without it |
|---|---|---|
ip_forward=1 + FORWARD rules | The instance is now a router, not just a host | Tunnel connects, handshake completes, no traffic flows — the classic WireGuard "it's up but dead" symptom |
MASQUERADE on ens5 | 10.8.0.0/24 is not routable on the internet; source NAT rewrites it to the instance IP | Packets leave, replies never come back |
| Source/Dest check disabled on the ENI | AWS drops packets whose source isn't the instance's own IP — which is every forwarded VPN packet | Everything works in tcpdump on wg0 and silently dies at the hypervisor |
3. WireGuard Protocol Internals — What Makes It Different
This is the section that separates operating a tunnel from understanding one. Everything WireGuard does well — and every one of its sharp edges in §9 — follows directly from four design decisions: a fixed cryptographic suite, a one-round-trip handshake, a single data structure called cryptokey routing, and radical silence on the wire. Internalize these and the rest of the article is corollary.
3.1 The Crypto Suite Is Fixed — and That's the Feature
Every VPN protocol before WireGuard treated cryptography as a menu: IKE negotiates proposals, TLS negotiates cipher suites, and thirty years of downgrade attacks (EXPORT ciphers, FREAK, Logjam, SSLv3 fallbacks) came from exactly that flexibility. WireGuard ships one suite, no negotiation, no version rollback — if the primitives are ever broken, you update the protocol version on both ends, not a config knob:
| Primitive | Role | Why this one |
|---|---|---|
| Curve25519 (X25519) | Elliptic-curve Diffie-Hellman key agreement | Fast, constant-time by construction, no weak points to validate, 32-byte keys you can paste in a config |
| ChaCha20-Poly1305 | AEAD cipher for all transport data | Constant-time in pure software — no AES timing side channels, and fast even on CPUs without AES-NI (phones, small Arm instances like this build's t4g) |
| BLAKE2s | Hashing, keyed MACs | Faster than SHA-2 in software with a comparable security margin |
| HKDF | Key derivation at every handshake step | Standard, well-analyzed expansion of DH results into session keys |
| XChaCha20-Poly1305 | Cookie encryption (DoS defense, §3.2) | Extended nonce allows stateless random nonces |
| TAI64N timestamps | Handshake replay protection | Monotonic ordering without server-side session state |
The protocol's handshake is an instance of the Noise IK pattern, and it has something almost no deployed VPN protocol has: machine-checked formal analyses — a symbolic model in Tamarin and a computational proof in CryptoVerif — plus an independent pen-and-paper cryptographic analysis (Dowling & Paterson). "Formally analyzed" is not marketing here; it's published, reproducible work on the actual protocol.
3.2 The 1-RTT Handshake, and How It Stays Invisible
INITIATOR (client) RESPONDER (server)
│
│ 1. Handshake Initiation ─ type 0x01, 148 bytes fixed ─────►
│ • fresh ephemeral public key (Curve25519, per handshake)
│ • initiator's static public key — AEAD-ENCRYPTED
│ (identity hiding: a sniffer can't learn who connects)
│ • TAI64N timestamp — AEAD-encrypted (replay defense)
│ • mac1 — keyed with BLAKE2s(server's static public key):
│ ★ you cannot even ELICIT a reply without already
│ knowing the server's public key. This is the
│ "invisible to scanners" property, mechanically.
│ • mac2 — cookie echo (all zeros unless server is loaded)
│
│ ◄──── 2. Handshake Response ─ type 0x02, 92 bytes fixed ───
│ • responder ephemeral + AEAD over an empty payload
│ (proves possession of static AND ephemeral keys)
│ [ under load: type 0x03 Cookie Reply instead — client
│ must echo it in mac2, proving IP ownership. DoS
│ defense with ZERO stored state on the server. ]
│
│ 3. Transport Data ─ type 0x04 ─────────────────────────────►
│ ChaCha20-Poly1305 AEAD · 64-bit counter nonce ·
│ sliding-window replay check · padded to 16-byte multiple
▼
TOTAL: 1 round trip and the first data packet is already flying.
(IKEv2 needs 2+ round trips; OpenVPN runs a full TLS handshake
inside its own framing. On a 150 ms hotel link you feel this.)
Three consequences worth spelling out. First, the silence is cryptographic, not cosmetic: because mac1 is keyed with a hash of the responder's public key, an internet-wide scanner that doesn't hold your public key receives nothing — not a rejection, not an ICMP error, nothing. Your VPN port does not exist for anyone but your peers, which is why UDP 51820 open to 0.0.0.0/0 in the security group is a defensible rule. Second, DoS resistance is stateless: an initiation is 148 bytes but forces the server to do a DH computation, so under load the server answers with an encrypted cookie instead and only spends CPU on initiators who can prove they own their source address. Compare that with TLS servers allocating session state on every ClientHello. Third, the encrypted static key means identity hiding: a passive observer of the handshake learns that some WireGuard peer contacted the server — not which one.
3.3 Session Lifetime and Forward Secrecy — the Timer Table
WireGuard's timers are protocol constants, not tunables, and knowing them turns debugging from guesswork into arithmetic:
| Constant | Value | What it means operationally |
|---|---|---|
| REKEY_AFTER_TIME | 120 s | Session keys are renegotiated at most every 2 minutes — a fresh ephemeral DH each time |
| REJECT_AFTER_TIME | 180 s | Keys hard-expire; a healthy peer's latest handshake in wg show is never older than ~2–3 min while passing traffic |
| REKEY_AFTER_MESSAGES | 260 | Counter-based rekey bound; unreachable in practice, exists for cryptographic hygiene |
| REKEY_TIMEOUT | 5 s | Handshake retransmission interval — why a dead server manifests as retries every 5 s in client logs |
| KEEPALIVE_TIMEOUT | 10 s | Passive keepalive after receiving data, so both sides confirm liveness without traffic |
| persistent-keepalive | 25 s (configured) | The one you set — see §3.5 for why 25 |
The 120-second rekey is WireGuard's forward secrecy mechanism: session keys derive from per-handshake ephemerals, so an adversary who records your ciphertext today and steals your static private key next year can decrypt at most nothing of the recorded traffic — the ephemerals are long gone. Two honest caveats: forward secrecy protects past sessions only (an attacker holding your current keys can join or impersonate right now — §10.3), and the DH itself is classical Curve25519, which is where the quantum caveat in §10.6 comes in.
3.4 Cryptokey Routing: One Table That Is Both Route and Firewall
This is WireGuard's central data structure and the single most misunderstood thing about it. Each peer entry maps a public key ↔ set of AllowedIPs, and the mapping is enforced in both directions:
Egress: when a packet enters wg0, its destination IP is matched — longest prefix wins — against every peer's AllowedIPs to decide which public key to encrypt to. AllowedIPs is the routing table.
Ingress: when a packet is decrypted, its inner source IP must fall inside the sending peer's AllowedIPs, or the kernel drops it silently. AllowedIPs is also an anti-spoofing ACL: peer B cannot inject packets claiming to be 10.8.0.2, because only peer A's key is bound to that address.
# Server side — one entry per peer (wg-easy generates these)
[Peer]
PublicKey = lqx4Yk…clientA…UGc=
PresharedKey = xxxxxxxx… # §10.6 — quantum hedge, free to add
AllowedIPs = 10.8.0.2/32 # "route 10.8.0.2 to this key" AND
# "this key may only source 10.8.0.2"
# Client side — FULL tunnel (this build)
[Peer]
PublicKey = SRVe9m…server…Qz0=
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0 # default route through the tunnel
PersistentKeepalive = 25
# Client side — SPLIT tunnel variant: only VPN + VPC traffic tunneled,
# everything else uses the local network directly
AllowedIPs = 10.8.0.0/24, 100.64.0.0/16
Full tunnel vs split tunnel is therefore not a client "mode" — it's just this one line. Full tunnel (0.0.0.0/0) buys the hostile-Wi-Fi protection this project exists for, at the price of every byte riding through AWS egress billing (§12). Split tunnel is the right answer when the VPN's job is reaching private services, not protecting general browsing. And note what the ingress rule gives you for free in a multi-peer setup: tenant isolation without a single firewall rule — your phone and your friend's laptop cannot spoof each other even though they share a subnet and a server.
3.5 Roaming and NAT Traversal — Why Wi-Fi → LTE Doesn't Drop
WireGuard has no concept of a "connection," so there is nothing to drop. The server updates a peer's endpoint (IP:port) from the outer source address of any packet that passes cryptographic authentication. Walk out of the café mid-download: your phone's outer address changes from the café NAT to the LTE carrier's CGNAT, the first authenticated packet from the new address updates the server's endpoint table, and the transfer continues. No re-handshake, no re-auth, no dropped TCP sessions inside the tunnel. Because the update requires a valid AEAD tag, an attacker can't spoof source addresses to redirect your session — endpoint mobility is authenticated, unlike, say, a bare UDP application.
The 25 in PersistentKeepalive = 25 is not folklore, it's NAT arithmetic: consumer and carrier NATs expire idle UDP mappings aggressively — Linux netfilter's default for unreplied UDP is 30 seconds, and RFC 4787 only mandates a 2-minute minimum that plenty of gear ignores. A 25-second keepalive keeps the pinhole open just under the worst common timeout, so the server can still reach a silent client behind NAT. Cost: one 32-byte packet every 25 s (~110 bytes/min on the wire) — negligible on power and bandwidth, essential for pushing traffic to idle peers.
3.6 What WireGuard Deliberately Refuses to Do
Reading the protocol's non-goals is as instructive as its features, because each one becomes an operational fact you must plan around (they resurface as cons in §9):
| Deliberate omission | Rationale | What it costs you |
|---|---|---|
| No TCP transport | TCP-over-TCP is pathological: two stacked retransmission timers amplify every loss into a stall cascade ("TCP meltdown") | UDP-hostile hotel/corporate networks break it, with no fallback |
| No traffic obfuscation | Obfuscation is an arms race the kernel shouldn't fight; layer it externally if needed | DPI can fingerprint the fixed 148/92-byte handshakes — censors do (§10.5) |
| No dynamic address assignment | No DHCP-like exchange = less state, less parsing, less attack surface | Every peer is a hand-assigned /32; wg-easy exists to paper over this |
| No user management, no 2FA, no PKI | Possession of a private key is identity; the protocol stays 4k lines | Key distribution and rotation are entirely your problem (§5.6, §10.3) |
| No logging, at all | Nothing to seize, nothing to leak — privacy by construction | No audit trail either: "who connected when" requires external tooling (§9.6) |
4. Packet Walk: One HTTPS Request Through the Tunnel
If you can narrate this diagram from memory, you can debug any VPN. The inner packet is your real traffic; WireGuard wraps it in ChaCha20-Poly1305 and ships it as boring UDP. Note the MTU arithmetic — the single most common cause of "VPN connects but some sites hang."
ON THE WIRE (café Wi-Fi sees this) INSIDE THE TUNNEL (after decrypt)
┌────────────────────────────────────┐
│ IP src: 192.168.7.42 (café DHCP) │ ┌─────────────────────────────┐
│ dst: 203.0.113.10 │ │ IP src: 10.8.0.2 │
│ UDP dst: 51820 │ │ dst: 142.250.x.x │
│ ┌────────────────────────────────┐ │ ──► │ TCP dst: 443 │
│ │ WireGuard: ChaCha20-Poly1305 │ │ │ TLS (your actual request) │
│ │ ████████████████████████████ │ │ └─────────────────────────────┘
│ └────────────────────────────────┘ │
└────────────────────────────────────┘ kernel FORWARD → POSTROUTING:
src 10.8.0.2 ⇒ 100.64.1.76 (MASQUERADE)
MTU BUDGET (why WG_MTU=1420) IGW: 100.64.1.76 ⇒ 203.0.113.10 (EIP 1:1)
1500 ethernet │
− 20 outer IPv4 ┐ ▼
− 8 UDP │ 60 bytes of destination server
− 32 WireGuard ┘ overhead sees src = 203.0.113.10
= 1440 theoretical max
= 1420 in practice (AWS + safety margin)
Set it wrong → fragmentation, or PMTUD
blackholes → "some sites just hang"
The advanced move that closes out MTU pathology for good is TCP MSS clamping. Path-MTU discovery depends on ICMP "fragmentation needed" messages, and half the internet's middleboxes drop ICMP — so a server that never learns your 1420-byte path keeps sending 1460-byte segments into a black hole. Clamping rewrites the MSS option in every forwarded TCP SYN so both ends agree on segments that fit (1420 − 40 bytes of IP+TCP headers = 1380):
# On the EC2 host — rewrite MSS in SYNs crossing the tunnel, both directions
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# Verify what an inner flow actually negotiated:
sudo tcpdump -ni wg0 'tcp[tcpflags] = tcp-syn' -v | head # look for "mss 1380"
With correct MTU and MSS clamping, the "some sites hang, most don't" class of bug — the one that generates the longest, saddest forum threads — is structurally impossible rather than merely unlikely.
5. The Build: EC2, wg-easy, the NAT Layer, and Operations
5.1 Right-Sizing the Instance (a Confession)
I started this project on a c8gn.xlarge — a network-optimized brute — because "VPNs need network performance," and then watched CloudWatch report the instance napping at 2% utilization. WireGuard runs in the kernel; for a personal VPN the crypto is nearly free. I stepped down to t4g.small, then t4g.micro (2 vCPU, 1 GB, Arm64) at roughly $6/month, and throughput for real-world use didn't measurably change. Fifty years of infrastructure teaches one lesson on repeat: measure before you size, and size for the workload you have, not the one you imagine.
5.2 wg-easy in Host Network Mode
wg-easy wraps WireGuard with peer management, QR-code provisioning, and per-client stats. The important deviation from the standard tutorial: network_mode: host. Docker's default bridge adds a userland NAT hop (and its own conntrack table) in front of a protocol whose whole point is kernel-speed forwarding — and container-level sysctls fight with runc. Host mode deletes the entire problem class:
# /opt/wg-easy/docker-compose.yml
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: wg-easy
restart: unless-stopped
network_mode: host # no docker bridge, no double NAT
cap_add:
- NET_ADMIN # manage wg0 + routes
- SYS_MODULE # load the wireguard kernel module
environment:
- WG_HOST=203.0.113.10 # your Elastic IP or DNS name
- PASSWORD_HASH=$$2y$$10$$... # bcrypt — NEVER the plaintext PASSWORD var
- WG_PORT=51820
- WG_DEFAULT_DNS=1.1.1.3 # see §6.1 — this line cost me a week
- WG_MTU=1420 # see Fig 3 for the arithmetic
- WG_PERSISTENT_KEEPALIVE=25 # keeps NAT pinholes open — §3.5 for the math
volumes:
- /opt/wg-easy:/etc/wireguard # peer configs + keys — this dir IS the crown jewels
# modern wg-easy also writes a per-peer PresharedKey — keep it (§10.6)
5.3 Kernel Tuning
# /etc/sysctl.d/99-wireguard-perf.conf
net.core.rmem_max = 16777216 # UDP receive buffer ceiling
net.core.wmem_max = 16777216 # UDP send buffer ceiling
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 10000 # deeper queue for packet bursts
net.core.default_qdisc = fq # fair queuing — required for BBR
net.ipv4.tcp_congestion_control = bbr # loss ≠ congestion on long paths
net.ipv4.ip_forward = 1 # the "this box is a router" bit
# apply: sudo sysctl --system
BBR deserves a sentence of respect: on long, slightly lossy paths (exactly what roaming VPN clients traverse), CUBIC interprets every lost packet as congestion and collapses its window; BBR models bandwidth and RTT instead and keeps the pipe full. It benefits the TCP flows inside your tunnel that terminate on this box and, more importantly, reflects the correct mental model for tunnel paths. Note that fq is not decoration — BBR depends on packet pacing, and fq is the qdisc that provides it.
5.4 The NAT Layer (Where Every Tutorial Hand-Waves)
# Forwarding: established flows back in, VPN subnet out
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 10.8.0.0/24 -j ACCEPT
# Source NAT: rewrite 10.8.0.x to the instance IP on egress
iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
# MSS clamping — see §4; belongs in the same persistent ruleset
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# Persist across reboots (Amazon Linux 2023)
dnf install -y iptables-services
service iptables save && systemctl enable iptables
Two mechanics worth understanding rather than pasting. MASQUERADE vs SNAT: MASQUERADE re-resolves the outgoing interface's address on every connection, which is what you want when the instance's private IP could change (stop/start); SNAT with a hardcoded address is marginally faster but breaks silently after an IP change. Conntrack is now load-bearing: every flow through the box occupies an entry in the connection-tracking table (nf_conntrack_max auto-scales with RAM — check /proc/sys/net/netfilter/nf_conntrack_count under load). One user won't dent it; twenty peers running torrents will, and the failure mode is dropped new connections while existing ones work — a genuinely confusing symptom if you've never seen it.
And the AWS-specific step that generates more "WireGuard doesn't work on EC2" forum threads than everything else combined:
# The hypervisor drops forwarded packets unless you tell it not to
aws ec2 modify-instance-attribute \
--instance-id i-0123456789abcdef0 \
--no-source-dest-check
5.5 Verify Before You Trust
sudo wg show # handshake within last ~2 min per peer? (§3.3)
ping -c 3 10.8.0.1 # client → server inside the tunnel
curl ifconfig.me # from client: MUST return the EIP
sudo tcpdump -ni wg0 # decrypted inner traffic (debugging gold)
sudo tcpdump -ni ens5 udp port 51820 # outer: ciphertext only
# DNS leak check — resolver must be the tunnel DNS, not the café's:
dig +short whoami.ds.akahelp.net TXT @ns1-1.akamaitech.net
5.6 Operating It: Monitoring, Alerting, and the Key Lifecycle
A VPN you don't monitor is a VPN you'll discover is down from a hotel lobby. WireGuard exposes everything you need through one machine-readable command — wg show all dump emits one line per peer: public key, preshared-key presence, endpoint, AllowedIPs, latest-handshake epoch, RX/TX bytes, keepalive. Given §3.3's timer table, the alert rule writes itself: a peer that should be online whose last handshake is older than 180 seconds is down, period.
# Handshake-staleness check — cron this and alert on output
now=$(date +%s)
sudo wg show wg0 latest-handshakes | while read -r peer ts; do
if [ "$ts" -gt 0 ] && [ $((now - ts)) -gt 180 ]; then
echo "STALE PEER: $peer — last handshake $((now - ts))s ago"
fi
done
# Per-peer transfer counters — feed CloudWatch or Prometheus
# (prometheus_wireguard_exporter parses the same dump format)
sudo wg show wg0 transfer
The key lifecycle is the part WireGuard's minimalism hands entirely to you, so make it a written runbook, not a vibe:
- Issue: one keypair per device, never shared across devices — revocation granularity is exactly key granularity.
- Revoke: device lost, sold, or retired → delete the peer in wg-easy that day. There is no CRL, no expiry, no second chance; a key is valid until removed.
- Rotate: server keypair on any suspicion of compromise and on a calendar (quarterly is honest). Understand the cost before you need it: rotation means touching every client config — this is the price of having no PKI, and it's why peer count quietly matters (§9.5).
- Audit: monthly, read the peer list and justify each entry aloud. An entry you can't attribute to a device you own is an incident, not a mystery.
- Contain: keys live in
/opt/wg-easyand nowhere else — not in AMIs, not in snapshots, not in a "backup" tarball on your laptop (§10.3).
6. Performance Tuning That Actually Mattered
6.1 The Bottleneck Was Never the Crypto — It Was DNS
The VPN "felt slow" for a week. Throughput tests were fine; browsing was molasses. The culprit: every new domain a page touches (and a modern page touches dozens) paid a slow round-trip to a distant filtered resolver before a single byte of content moved. Switching WG_DEFAULT_DNS from my NextDNS endpoint to Cloudflare's family resolver (1.1.1.3) transformed perceived speed more than every kernel knob combined.
The endgame I'd recommend (and prototyped): run AdGuard Home on the same instance in host mode, point WG_DEFAULT_DNS at the wg0 gateway IP, and use an encrypted upstream. Ad-blocking at zero added network latency, cache hits answered on-box — the same chain-the-resolvers pattern from my Raspberry Pi homelab build, relocated to the cloud edge.
6.2 Where the CPU and the Ceiling Actually Are
For the advanced reader who wants the real bottleneck map on a small instance:
The crypto path is kernel-resident and cheap. Packets never cross into userspace — no context switches, no copies to a daemon, which is precisely the tax OpenVPN pays per packet. ChaCha20-Poly1305 was chosen partly because it's fast without hardware AES support, which is why a 1 GB Arm instance saturates its useful bandwidth with single-digit CPU. Encryption work is spread across cores per-peer via the kernel's parallel crypto infrastructure, but note the corollary: a single flow's throughput is effectively bounded by one core — more vCPUs help more peers, not one big download.
The real ceiling on a t4g.micro is the network baseline, not the crypto. Burstable instances advertise "up to 5 Gbps" but sustain only a small baseline (commonly measured in the tens of megabits for the micro sizes) once burst allowances drain. For a personal VPN whose bottleneck is your café Wi-Fi anyway, irrelevant; for a shared exit node with three streaming housemates, this — not WireGuard — is what you'd hit first. That asymmetry is exactly why the c8gn.xlarge (§5.1) was comedy: I had provisioned a firehose for a garden hose workload.
Buffers exist for bursts, not bandwidth. The rmem_max/netdev_max_backlog increases in §5.3 don't raise throughput; they stop packet drops during micro-bursts (a page load fanning out to 30 connections at once) on a box with little RAM headroom. Measure drops, don't guess: ethtool -S ens5 | grep -i drop and netstat -su | grep -i errors before and after.
6.3 The Ranked List
| # | Change | Impact |
|---|---|---|
| 1 | Fast, close DNS resolver | Massive perceived improvement — pages render immediately |
| 2 | Correct MTU (1420) + MSS clamping | Eliminated hanging sites and mid-transfer stalls — structurally (§4) |
| 3 | Host networking (no docker bridge) | Removed double-NAT and conntrack pressure; simpler debugging |
| 4 | UDP buffers + netdev backlog | Fewer drops during bursts on the 1 GB instance |
| 5 | BBR + fq | Steadier long-haul TCP; the right default everywhere |
| 6 | Instance class upgrades | Statistically indistinguishable from zero for one user. Save the money. |
7. WireGuard vs OpenVPN vs IPsec — the Data
Opinions about VPN protocols are abundant; numbers are rarer. Here is the honest comparison table I wish someone had handed me, combining the WireGuard project's own published gigabit benchmark (old now, but directionally reproduced many times since), the codebase measurements from the whitepaper, and thirty years of operating the other two:
| Dimension | WireGuard | IPsec (IKEv2) | OpenVPN |
|---|---|---|---|
| Tunnel codebase (order of magnitude) | ~4,000 LOC, one kernel module | Hundreds of thousands (kernel XFRM + strongSwan/IKE daemon) | Hundreds of thousands (daemon + full TLS library) |
| Where packets are processed | Kernel, end to end | Kernel data path, userspace IKE | Userspace via tun device — two kernel crossings per packet |
| Handshake | 1 round trip (Noise IK) | 2+ round trips (IKE_SA_INIT + IKE_AUTH) | Full TLS handshake inside custom framing — slowest to connect |
| Cryptography | Fixed suite, no negotiation | Negotiated proposals — misconfiguration surface | Negotiated TLS suites — misconfiguration surface |
| Reference benchmark (same gigabit hardware) | ~1,011 Mbps · 0.40 ms ping | ~825–880 Mbps · ~0.50 ms | ~258 Mbps · 1.54 ms |
| Roaming / mobility | Native, stateless (§3.5) | MOBIKE extension, if both ends support it | Reconnect and renegotiate |
| TCP/443 fallback for hostile networks | None, by design | None (ESP / UDP 500+4500) | Yes — its enduring superpower |
| Response to unauthenticated probes | Silence — port is invisible | IKE responds — scannable | TLS responds — scannable |
| Formal analysis of protocol | Tamarin + CryptoVerif, published | RFC sprawl; analyses per-fragment | Inherits TLS analyses; implementation audits |
| Failure behavior | Silent — no error, just no handshake | Verbose negotiation errors (once you learn to read them) | Verbose logs |
| Still the right choice when… | You control both ends and UDP works | Site-to-site with vendor gear, compliance regimes | You must traverse UDP-hostile networks on TCP/443 |
Read the last three rows together and the mature conclusion emerges: WireGuard didn't make the others obsolete — it made them specialists. OpenVPN survives precisely where WireGuard's §3.6 refusals bite; IPsec remains the lingua franca of appliance-to-appliance tunnels. For a personal cloud VPN where you control both endpoints, WireGuard wins on every dimension that matters.
8. The Pros — Comprehensive, With Evidence
The at-a-glance version, then the argument for each:
| Pro | The evidence |
|---|---|
| Total configuration authority | Logging policy, DNS, peers, keys — all yours; no vendor claim to audit |
| Minimal, verified attack surface | ~4k LOC · formal analyses (§3.1) · cryptographically silent port (§3.2) |
| Performance economics | ~4× OpenVPN throughput, ¼ its latency (§7) — near-free crypto on a $6 instance |
| Real hostile-network protection | Entire local segment reduced to observing UDP noise (Fig 3) |
| Single-tenant exit | No shared-IP abuse blowback, no congestion, no device caps |
| Compounding education | Routing, NAT, MTU, DNS, kernel, AWS — one project, whole stack |
8.1 Total configuration authority. Your ciphers, your DNS, your logging policy (including none), your peer list. No vendor "no-logs" pinky-promise to evaluate — you wrote the logging config, and §3.6's no-logging default means the honest answer to "what do you retain?" is nothing, verifiably, because there is nothing to configure. For anyone who has ever read a commercial VPN's 40-page privacy policy looking for the sentence that matters, this is the entire pitch.
8.2 An attack surface you can actually reason about. The numbers from §3 and §7 deserve restating as a security argument: ~4,000 lines of kernel code with machine-checked protocol analyses, versus codebases two orders of magnitude larger for the alternatives. Fewer lines is not aesthetics — vulnerability density correlates with code size, and WireGuard's CVE history since kernel mainlining (Linux 5.6, 2020) is remarkably quiet compared to the drumbeat of critical advisories against enterprise SSL-VPN appliances, which have been among the most-exploited perimeter devices of the last half-decade. Add the mac1 silence property — your VPN port is cryptographically invisible to scanners, not merely filtered — and no negotiation to downgrade, and you have the smallest honest perimeter I've ever operated.
8.3 Performance economics that favor small. Kernel-resident crypto (§6.2) means the protocol adds ~0.4 ms and negligible CPU — so the $6 instance is not a compromise, it's the correctly-sized machine. The 1-RTT handshake (§3.2) makes reconnection imperceptible, and stateless roaming (§3.5) means your SSH sessions survive the walk from café Wi-Fi to LTE. On battery: an idle WireGuard tunnel transmits nothing but a 32-byte keepalive every 25 s — OpenVPN's TLS layer keeps considerably more machinery warm.
8.4 Real protection on hostile networks. The entire local segment — snoopers, rogue APs, ARP spoofers, TLS-stripping middleboxes, captive-portal injectors — sees uniform UDP noise (Fig 3). This isn't partial mitigation; the local network is removed from your threat model for confidentiality and integrity, which is the use case where a personal VPN earns its keep, full stop.
8.5 A single-user tunnel has no noisy neighbors. No congested shared exit at 8 p.m., no IP reputation ruined by whoever else bought the $3 plan this month, no simultaneous-device limits, no support ticket to add a peer. And the cryptokey-routing ingress check (§3.4) means even your own multi-device setup has built-in spoofing isolation without writing a single firewall rule.
8.6 The education compounds. Routing, NAT, conntrack, MTU pathology, DNS architecture, kernel tuning, congestion control, AWS networking internals — this project touches more of the networking stack per dollar than anything else you can build. It is the cloud-era equivalent of building your own amplifier, and unlike the amplifier, employers can tell when you've done it: the §5–6 debugging vocabulary is interview material.
9. The Cons — Comprehensive, With Evidence
Same format — the table, then the honest detail:
| Con | The evidence |
|---|---|
| Permanent unpaid ops role | Patching, monitoring, key lifecycle — §5.6 is a real runbook you now own |
| Availability math of one box | Single-instance SLA is 99.5% — up to ~3.6 hrs/month, no failover, one geography |
| Egress pricing ambush | $0.09/GB retail — heavy months cost 5–10× the instance (§12) |
| Datacenter IP treatment | Cloud ASN → blocked streams, CAPTCHAs, anti-fraud friction |
| Protocol sharp edges | No TCP fallback, DPI-fingerprintable, static /32s, keyless rotation pain (§3.6) |
| No audit trail | Privacy-by-design cuts both ways: no record of who connected when |
9.1 You are now a VPN operator, unpaid, forever. Kernel updates, Docker image updates, the key lifecycle runbook of §5.6, monitoring, and 3 a.m. curiosity about that CloudWatch spike. A commercial provider amortizes an ops team across millions of users; you amortize yourself across one. Budget it honestly: an hour or two a month when nothing is wrong, and the tail risk of a weekend when something is. If §5.6 read like overhead rather than craft, that is the signal to buy instead of build.
9.2 The availability math of one box is unkind. One instance, one AZ, one region. AWS's SLA for a single EC2 instance is 99.5% monthly — that's a tolerance of roughly 3.6 hours of downtime per month before credits apply, with no failover unless you build it (second instance, EIP re-association automation, health checks — congratulations, your $6 VPN is now a distributed system). Instance retires or the AZ hiccups → no VPN until you fix it, probably while traveling, probably on the exact network you don't trust. One region also means one exit geography: commercial providers give you ninety countries; you have us-west-2.
9.3 Egress pricing is the ambush. The instance is $6/month; the bandwidth is $0.09/GB after the free tier — see §12 for the full table. The structural point: a full-tunnel VPN (§3.4) converts every byte of your internet life into billable cloud egress. Commercial VPNs bundle unlimited transfer because they buy transit wholesale by the gigabit; AWS retail egress is some of the most expensive bandwidth money can buy. Split tunneling is the engineering mitigation; discipline is the behavioral one; neither is free.
9.4 Datacenter IP, datacenter treatment. Your Elastic IP sits in address space that every IP-intelligence feed labels "hosting/AWS," and the internet treats that label as a prior: streaming services block it wholesale, retailers add checkout friction, some sites CAPTCHA you into oblivion, and a few security teams' geo-anomaly rules will flag your logins. Geo-unblocking — the reason half the internet buys VPNs — mostly doesn't work from cloud IPs, and no amount of tuning changes what ASN you egress from.
9.5 WireGuard's refusals become your workload. Every §3.6 non-goal lands on your desk. No dynamic addressing: every peer is a hand-assigned /32, fine at 5 peers, a spreadsheet at 25. No user management, 2FA, or SSO: possession of the private key is identity, so a stolen laptop with an unencrypted disk is a valid VPN credential until you notice. No PKI: rotating the server key means reprovisioning every client — which is why it doesn't happen, which is its own finding. No TCP fallback and no obfuscation: on UDP-hostile hotel networks and DPI-filtered national networks, your VPN simply doesn't work, and the protocol's silence (§3.2) means "doesn't work" presents as nothing at all — no error, no log, just a handshake that never lands. Elegant to run; unforgiving to half-understand.
9.6 No audit trail, by design. The same no-logging property celebrated in §8.1 means you cannot answer "which device connected last Tuesday" after an incident — WireGuard keeps a current endpoint and byte counters, nothing historical. If a peer key leaks, forensics amounts to whatever external polling (§5.6) you thought to build beforehand. Privacy tools and accountability tools are different tools; know which one you deployed.
10. The Bad Part — Security Concerns Nobody Covers
Everything above is engineering. This section is the judgment — the concerns that "deploy a VPN in 5 minutes!" content never mentions, ordered by how expensive the misunderstanding is.
10.1 A VPN Moves Trust; It Does Not Remove It
WITHOUT VPN
┌────────┐ everything: SNI, DNS, ┌─────┐ ┌──────────────┐
│ Device ├──────────────────────────►│ ISP ├───────►│ Destinations │
└────────┘ timing, volume, dst IP └─────┘ └──────────────┘
ISP sees: ALL metadata site sees: your home IP
WITH SELF-HOSTED VPN
┌────────┐ UDP noise ┌─────┐ tunnel ┌─────────┐ ┌──────────────┐
│ Device ├─────────────►│ ISP ├───────────►│ AWS EC2 ├───────►│ Destinations │
└────────┘ └─────┘ └─────────┘ └──────────────┘
ISP sees: "talks to one AWS IP, AWS sees: all egress site sees: your
volume, timing" (still metadata!) flow metadata (VPC EIP — which is
Flow Logs), billing YOURS ALONE and
Device still leaks: browser identity = your name, tied to a paid
fingerprint, cookies, logins your credit card AWS account
═══════════════════════════════════════════════════════════════════════════════
The lesson: you didn't erase the observer. You chose a different one.
Your ISP no longer sees destinations — but it still sees that you talk to one AWS IP, when, and how much. Traffic-volume and timing analysis remain possible. Meanwhile AWS can observe every egress flow, and unlike a commercial VPN's shared exit where you're one of ten thousand users on that IP, your Elastic IP has a population of exactly one. Any site, ad network, or investigator who learns that IP has learned you — it's associated with an account carrying your legal name and payment method, discoverable by legal process. For privacy-from-your-ISP this architecture is fine. For anonymity it's a monogrammed getaway car.
10.2 The Admin Interface Is the Real Attack Surface
WireGuard itself is beautifully unattackable from the outside — silent, keyed, minimal (§3.2). The thing bolted next to it is not: wg-easy's web UI on TCP 51821 holds every peer's private key behind a single password. Expose that to 0.0.0.0/0 and your VPN's security has been reduced from Curve25519 to "how good is my password and is there a CVE in this Node.js app this month." The pattern is eternal: the management plane, not the data plane, is where tunnels die — true for 2000s IPsec appliances, true for enterprise SSL-VPN gateways (whose exploitation has been a headline generator for years), true for your $6 hobby box.
- TCP 51821 restricted to a single trusted
/32— or no public rule at all: reach the UI only through the tunnel or an SSH port-forward. PASSWORD_HASH(bcrypt), never the plaintextPASSWORDenv var that lands indocker inspectand shell history.- SSH: key-only auth, no root login, same trusted-IP restriction.
- Patch cadence: the wg-easy image and the kernel, monthly, on a calendar — not "when I remember."
10.3 Key Hygiene: /opt/wg-easy Is the Whole Kingdom
That volume directory contains the server private key and every peer config. Whoever reads it is the VPN: they can impersonate the server, decrypt future sessions they MITM, or quietly join your network as a trusted peer. It ends up in more places than you think — EBS snapshots, AMIs you baked for "backup," a tarball you scp'd home, the laptop of the friend you provisioned. Treat peer revocation as routine (device lost or retired → delete the peer that day), rotate the server keypair on any suspicion, and if a snapshot ever contained the directory, assume the keys are wherever the snapshot went. Remember the precise shape of WireGuard's guarantee from §3.3: forward secrecy protects past sessions; it does nothing about an attacker who holds current keys.
10.4 The Leak Trio: DNS, Kill Switch, IPv6
Three quiet failure modes defeat the entire exercise while the client cheerfully reports "Connected":
DNS leaks. If the OS keeps using the local resolver, the café still reads every domain you visit off the wire — encrypted tunnel notwithstanding. Force tunnel DNS in the client config and verify with a leak test (§5.5); never assume.
No kill switch. WireGuard ships without one. Tunnel drops → the OS silently reverts to the default route → traffic egresses in cleartext with your real IP, mid-session. If "never unencrypted" is a requirement, enforce it at the client firewall (only allow egress via wg0 + the endpoint IP) and test by killing the server during a download.
IPv6. This build tunnels IPv4 only (AllowedIPs = 0.0.0.0/0). On an IPv6-enabled café network, every v6-capable destination is reached outside the tunnel. Add ::/0 to AllowedIPs and handle v6 properly, or disable v6 on the client. Ignoring it means running a VPN with a second, invisible, unencrypted default route.
10.5 DPI Fingerprintability: Your VPN Is Invisible to Scanners, Obvious to Censors
Here's a subtlety that trips up even experienced engineers: WireGuard is simultaneously the stealthiest and the most conspicuous VPN protocol, depending on who's looking. To an active scanner probing your server, it doesn't exist (§3.2). To a passive observer of your traffic, it's unmistakable: four fixed message types in the first byte, handshakes of exactly 148 and 92 bytes, a recognizable rekey cadence. The whitepaper declares obfuscation an explicit non-goal, and national firewalls in censorship-heavy countries exploit exactly this — they fingerprint and drop WireGuard flows wholesale, as do some corporate networks and the occasional hotel middlebox that simply blocks all UDP. If you operate in those environments, the answer is an obfuscating outer layer (udp2raw, wstunnel, an HTTPS-shaped tunnel) with its own performance tax — including the TCP-over-TCP meltdown §3.6 warned about — or keeping an OpenVPN TCP/443 profile as the compatibility fallback. Know which network environments your VPN must survive before you standardize on the protocol.
10.6 Harvest Now, Decrypt Later — and the One-Line Quantum Hedge
Curve25519 is classical elliptic-curve cryptography, and a cryptographically-relevant quantum computer running Shor's algorithm would break it. The reason this matters today rather than someday: an adversary in a position to record your ciphertext can store it and decrypt retroactively when the capability arrives — harvest now, decrypt later. Whether that's in your threat model is a personal judgment; what's not debatable is that the mitigation costs one line. WireGuard supports an optional 256-bit PresharedKey per peer, mixed into the handshake's key derivation alongside the DH results. Because it's symmetric material, quantum computers gain no meaningful shortcut against it — so long as the PSK stays secret, recorded traffic stays sealed even if Curve25519 falls:
# Generate one PSK per peer; goes in BOTH ends' [Peer] sections
wg genpsk
# → modern wg-easy versions generate and manage per-peer PSKs automatically —
# one more reason not to hand-roll configs. Note the PSK lives in the same
# /opt/wg-easy directory, so §10.3's hygiene rules apply to it equally.
This is a hedge, not a full post-quantum handshake — identity hiding and some handshake properties still rest on classical assumptions — but for the price of a config line, sealed recorded traffic is the cheapest insurance in this article.
10.7 The AWS Side of the House
The VPN's security posture includes the cloud account it lives in, and three controls belong in any honest build. IMDSv2, enforced: a server that forwards arbitrary client traffic is a textbook SSRF pivot toward the instance metadata service and its IAM credentials; requiring the session-token flow (--metadata-options HttpTokens=required) closes the classic 169.254.169.254 grab. SSM Session Manager over SSH: port 22 open to even a trusted /32 is a standing invitation that IAM-authenticated, CloudTrail-audited shell access simply isn't — on a box whose whole job is holding keys, the audit trail argument wins. VPC Flow Logs, understood both ways: they are how AWS could observe your egress metadata (Fig 4's honest caveat) and also how you detect anomalies — a peer exfiltrating gigabytes at 4 a.m. shows up in flow logs or nowhere. Decide deliberately whether to enable them; both choices are defensible, but only one of them is a choice you made on purpose.
10.8 Concerns That Apply to Every VPN — Including the One You'd Buy
For balance, the commercial side of the ledger: "no-logs" is a marketing claim, occasionally validated by an audit-of-a-point-in-time, occasionally falsified in court records; jurisdictions and their disclosure obligations apply; free VPNs are data-collection businesses wearing a privacy costume; browser "VPN" extensions are usually proxies that see plaintext; and several household-name providers have shipped actual vulnerabilities in their own clients. Self-hosting exchanges those risks for the ones in §10.1–10.7. There is no configuration of any VPN, bought or built, that removes the need to decide whom you trust with what.
11. The Threat-Model Table
Print this. It's the whole article in one table — what a personal cloud VPN actually does:
| Threat | Protected? | Reality |
|---|---|---|
| Snooping on public Wi-Fi (sniffing, ARP/DHCP spoofing, evil twin) | Yes | The core use case. Local segment sees only encrypted UDP. |
| ISP reading your destinations / DNS | Yes | ISP sees one AWS endpoint. (AWS now sees the flows instead — trust moved.) |
| Captive portals / middlebox TLS meddling | Yes | Nothing on-path can inject or strip inside the tunnel. |
| Stable trusted egress IP for allow-listing | Yes | Genuinely useful; lock your admin panels to the EIP. |
| Recorded-today, decrypted-by-quantum-later traffic | Partially | With per-peer PresharedKeys (§10.6): yes for confidentiality. Without: no. |
| Website tracking, fingerprinting, cookies | No | IP is one signal of dozens. Your browser identifies you fine. |
| Anonymity from platforms, advertisers, investigators | No | Single-tenant IP tied to your named, billed AWS account. Worse than shared exits. |
| Geo-unblocking streaming catalogs | Mostly no | Cloud IP ranges are fingerprinted and blocked wholesale. |
| Networks that block UDP or fingerprint WireGuard (DPI) | No | No TCP fallback, no obfuscation — by design (§10.5). Carry a backup transport. |
| Malware, phishing, endpoint compromise | No | A tunnel transports packets; it doesn't judge them. (DNS filtering helps at the margin.) |
| Traffic-volume / timing analysis by a global observer | No | Beyond any single-hop VPN by design. That's Tor's problem domain. |
12. Real Costs (the Line Item Tutorials Omit)
| Item | Monthly (us-west-2, on-demand) | Notes |
|---|---|---|
| t4g.micro instance | ~$6.10 | 2 vCPU Arm, 1 GB — ample; see §5.1 |
| Elastic IP | ~$3.65 | Billed hourly whether attached or not since 2024 |
| EBS gp3 (8 GB) | ~$0.65 | OS + Docker + configs |
| Egress @ 50 GB (light browsing) | ~$4.00 | First 100 GB/mo free tier offsets some |
| Light-use total | ≈ $10–14 | Comparable to a premium commercial VPN |
| Egress @ 500 GB (streaming month) | ~$40–45 | The ambush. Every tunneled byte exits AWS at retail |
| Egress @ 1 TB (household full-tunnel) | ~$83+ | At this point a commercial plan is 15–25× cheaper |
The honest framing: self-hosting is price-competitive for light, deliberate use and ruinous as a full-time streaming pipe. Full tunnel vs split tunnel (§3.4) is therefore a billing decision as much as a security one. You are not buying cheaper VPN service; you are buying control and an education, at commercial-VPN prices.
13. Decommissioning Cleanly — the Skill Nobody Practices
I shut this project down on purpose: the learning was extracted, and an unpatched, unmonitored VPN server is not an asset — it is a liability with a public IP. Infrastructure you've stopped caring for doesn't gracefully retire itself; it waits for a CVE. The teardown, in dependency order:
# 0. Revoke first — no orphaned credentials outliving the server
# (delete all peers in wg-easy; securely remove client configs from devices)
# 1. Snapshot nothing containing /opt/wg-easy unless you accept
# that the snapshot now IS a key store. I chose: no snapshot.
# 2. Terminate the instance
aws ec2 terminate-instances --instance-ids i-0123456789abcdef0
# 3. Release the Elastic IP — it bills while parked, and "my old IP"
# has no sentimental value
aws ec2 disassociate-address --association-id eipassoc-xxxx
aws ec2 release-address --allocation-id eipalloc-xxxx
# 4. Delete the VPC and its dependency chain (IGW, subnets, SGs, routes)
aws ec2 delete-vpc --vpc-id vpc-xxxx # after detaching/deleting dependents
# 5. Verify $0: billing console next cycle. "Terminated" with a
# parked EIP or orphaned EBS volume still costs money.
14. FAQ
Is self-hosted more private than a commercial VPN?
Differently private. You remove the vendor but become the sole user of an IP tied to your named AWS account, and AWS can see egress flow metadata. Pick based on threat model, not vibes.
Does a VPN make me anonymous?
No. It changes your apparent IP and encrypts the path — fingerprinting, cookies, and logins identify you regardless. A single-tenant exit IP is arguably worse for anonymity. That's Tor's job.
Why WireGuard over OpenVPN/IPsec?
~4k lines of formally-analyzed kernel code, fixed modern crypto, a 1-RTT handshake, no negotiation to misconfigure, near-line-rate on tiny instances, seamless roaming. Trade-offs: static peer addressing, no built-in user management, no TCP fallback, and silent-by-design failure modes. See §7 for the data.
How does AllowedIPs actually work?
It's one table doing two jobs (§3.4): outbound, destination IP selects which peer key to encrypt to (it's the routing table); inbound, the decrypted packet's source IP must be inside the sender's AllowedIPs or it's dropped (it's an anti-spoofing ACL). 0.0.0.0/0 = full tunnel; listed subnets = split tunnel.
Is the wg-easy UI safe to expose?
Not to the internet — it holds every private key behind one password. Trusted-IP-only on 51821, or reach it exclusively through the tunnel. Only UDP 51820 should ever face the world.
What does it really cost?
~$10–14/month light use; egress at $0.09/GB makes heavy streaming cost multiples of the instance, and a 1 TB month runs past $80. See §12.
Why is my WireGuard slow?
In order of likelihood: DNS latency, MTU/fragmentation (add MSS clamping — §4), Docker bridge double-NAT, missing UDP buffer/backlog tuning, burstable-instance baseline throttling. The crypto is never the answer (§6.2).
Why doesn't it work on some hotel/corporate networks?
WireGuard is UDP-only, unobfuscated, and DPI-fingerprintable by design (§10.5). UDP-blocking networks and national firewalls stop it, and there's no TCP/443 fallback. Wrap it (udp2raw/wstunnel) at a performance cost, or carry an OpenVPN TCP/443 backup profile.
Does WireGuard protect against quantum computers?
Partially, if you add per-peer PresharedKeys (§10.6): the symmetric PSK seals recorded traffic against a future quantum break of Curve25519 — the harvest-now-decrypt-later hedge. One line of config; wg-easy does it automatically.
Can I stream geo-blocked content through it?
Mostly no — cloud IP ranges are fingerprinted and blocked by every major service. Buy the commercial cat-and-mouse if that's the goal.
Do I need a kill switch?
If "never unencrypted" matters, yes — WireGuard doesn't ship one. Enforce at the client firewall, and test by killing the server mid-download.
15. Conclusion
Would I tell you to build this? Yes — once, deliberately, with your eyes open. You will learn more real networking in two weekends than in a semester, you'll get genuine protection on hostile Wi-Fi, and you'll never again read a VPN marketing page the same way. But go in knowing what you're signing: you are not purchasing invisibility — you are relocating your trust from an ISP to a cloud provider and appointing yourself the security team. The tunnel is the easy 10%. The protocol understanding of §3, the threat-model honesty, the management-plane discipline, the key hygiene, the leak testing, and the clean decommission are the 90% that separate an engineer from a tutorial-follower. Fifty years of tunnels distilled: encryption is cheap; knowing exactly whom you trust, and being able to name the day you'll turn it off, is the expensive part — and the part worth paying for.
Further Study — Going Past This Article
- The WireGuard whitepaper (Jason A. Donenfeld) — 20 pages, entirely readable; the source for §3's handshake, timers, and non-goals.
- WireGuard formal verification — the Tamarin model and the computational analyses behind the "formally analyzed" claim.
- WireGuard performance benchmarks — the throughput/latency numbers in §7's comparison table, with methodology.
- RFC 4787 — NAT behavioral requirements for UDP — why
PersistentKeepalive = 25is arithmetic, not folklore (§3.5). - "BBR: Congestion-Based Congestion Control" (Cardwell et al., ACM Queue) — the model behind §5.3's most interesting sysctl.