Portal | Level: L2: Operations | Topics: VPN & Tunneling, Firewalls, Linux Networking Tools | Domain: Networking
VPN & Tunneling - Primer¶
Why This Matters¶
Networks have boundaries. VPNs dissolve them. Whether you are connecting remote offices, giving developers access to staging environments, linking cloud VPCs, or just tunneling past a restrictive firewall — you need tunnels. Get them wrong and you either have no connectivity or, worse, a false sense of security with traffic flowing in the clear.
This primer covers WireGuard (the modern standard), OpenVPN (the incumbent), IPSec (the enterprise standard), and SSH tunnels (the pragmatic quick-fix). You will understand when to use each, how they work, and how to configure them without shooting yourself in the foot.
Tunnel Types: L2 vs L3¶
Layer 2 (TAP / Bridge):
┌──────────────────────────────────────────┐
│ Ethernet frames traverse the tunnel │
│ Both sides appear to be on the same LAN │
│ Broadcasts work, ARP works │
│ Used for: bridging sites, VXLAN │
└──────────────────────────────────────────┘
Layer 3 (TUN / Routed):
┌──────────────────────────────────────────┐
│ IP packets traverse the tunnel │
│ Each side has its own subnet │
│ No broadcasts across the tunnel │
│ Used for: most VPNs, site-to-site │
└──────────────────────────────────────────┘
Most VPNs are Layer 3. Layer 2 tunnels are used when you need broadcast traffic (legacy protocols, DHCP across sites) or full ethernet bridging.
WireGuard¶
WireGuard is the modern VPN protocol: ~4000 lines of kernel code, simple configuration, excellent performance. It is now built into the Linux kernel (5.6+).
Who made it: WireGuard was created by Jason Donenfeld and merged into the Linux kernel in March 2020 (kernel 5.6). Linus Torvalds himself praised it: "Can I just once again state my love for it and hope it gets merged soon? Maybe the code isn't perfect, but I've skimmed it, and compared to the horrors that are OpenVPN and IPSec, it's a work of art." The ~4000 lines of code compares to ~100,000 for OpenVPN and ~400,000 for IPSec (strongSwan).
How WireGuard Works¶
┌───────────┐ ┌───────────┐
│ Peer A │ UDP (port 51820) │ Peer B │
│ │◄────────────────────────────▶│ │
│ Public Key │ Noise Protocol Framework │ Public Key │
│ Private Key│ ChaCha20 + Poly1305 │ Private Key│
│ wg0: 10.0.0.1 │ wg0: 10.0.0.2
└───────────┘ └───────────┘
Key concepts: - Peers identify each other by public keys (not certificates) - All traffic is UDP (single port) - Cryptokey routing: "packets from this IP go to this peer" - Silent by design: does not respond to unauthenticated packets
WireGuard Configuration¶
Server (Peer A):
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = SERVER_PRIVATE_KEY_HERE
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Client B
PublicKey = CLIENT_B_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.2/32
[Peer]
# Client C
PublicKey = CLIENT_C_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.3/32
Client (Peer B):
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = CLIENT_B_PRIVATE_KEY_HERE
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0 # route ALL traffic through VPN
# AllowedIPs = 10.0.0.0/24 # route only VPN subnet (split tunnel)
PersistentKeepalive = 25
Key Generation¶
# Generate key pair
wg genkey | tee privatekey | wg pubkey > publickey
# Or one-liner
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
chmod 600 /etc/wireguard/privatekey
# Generate preshared key (additional layer of security)
wg genpsk > preshared.key
Managing WireGuard¶
# Bring interface up/down
wg-quick up wg0
wg-quick down wg0
# Enable at boot
systemctl enable wg-quick@wg0
# Show status
wg show
wg show wg0
# Add a peer dynamically (without restarting)
wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 10.0.0.4/32
# Remove a peer
wg set wg0 peer CLIENT_PUBLIC_KEY remove
AllowedIPs = Routing Table¶
Gotcha: WireGuard has no concept of "connection state." It is stateless by design. A peer is considered "connected" if it has sent a valid handshake recently. If a peer goes offline, WireGuard does not detect this -- it simply stops receiving packets.
PersistentKeepalive = 25sends a keepalive packet every 25 seconds, which is needed for peers behind NAT (to keep the NAT mapping alive) and for detecting dead peers.
This is the concept that trips people up. AllowedIPs serves two purposes:
- Outgoing: which destination IPs get routed through this peer
- Incoming: which source IPs are accepted from this peer
AllowedIPs = 0.0.0.0/0 # full tunnel (all traffic through VPN)
AllowedIPs = 10.0.0.0/24 # only VPN subnet
AllowedIPs = 10.0.0.2/32 # only this specific peer IP
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24 # VPN + remote LAN
OpenVPN¶
OpenVPN is the battle-tested workhorse. More complex than WireGuard but extremely flexible and well-understood.
OpenVPN Architecture¶
┌───────────┐ ┌───────────┐
│ Client │ TLS (TCP/UDP 1194) │ Server │
│ │◄────────────────────────────▶│ │
│ Client cert│ OpenSSL / TLS channel │ Server cert│
│ + CA cert │ + data channel (AES-GCM) │ + CA cert │
│ tun0: 10.8.0.2 │ tun0: 10.8.0.1
└───────────┘ └───────────┘
Basic Server Config¶
# /etc/openvpn/server.conf
port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh2048.pem
server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
keepalive 10 120
cipher AES-256-GCM
auth SHA256
persist-key
persist-tun
status /var/log/openvpn-status.log
log-append /var/log/openvpn.log
verb 3
UDP vs TCP¶
UDP (default, recommended):
+ Lower latency
+ No TCP-over-TCP meltdown
+ Better performance
- May be blocked by firewalls
TCP (fallback):
+ Passes through most firewalls
+ Can run on port 443 (looks like HTTPS)
- Higher latency
- TCP-over-TCP causes retransmission storms
Always try UDP first. Use TCP only when UDP is blocked.
Under the hood: "TCP-over-TCP meltdown" is a real performance problem. When you encapsulate TCP traffic inside a TCP tunnel, both layers independently retry lost packets. A single packet loss triggers retransmission at both levels, causing exponential backoff storms. This is why WireGuard uses UDP exclusively and why OpenVPN defaults to UDP. If you must use TCP (restrictive firewalls), expect 20-40% throughput degradation under any packet loss.
IPSec¶
IPSec is the enterprise/carrier-grade VPN protocol. It is complex but deeply embedded in networking hardware (every router and firewall speaks IPSec).
IPSec Phases¶
Phase 1 (IKE SA):
Peers authenticate each other
Negotiate encryption algorithms
Establish a secure management channel
→ Result: IKE Security Association
Phase 2 (IPSec SA):
Negotiate data encryption parameters
Define what traffic to protect (selectors)
→ Result: IPSec Security Association (the actual tunnel)
┌──────────────────────────────────────────────────────────┐
│ Phase 1 (IKE) │
│ ┌─────────┐ IKE_SA_INIT ┌─────────┐ │
│ │ Peer A │◄──────────────▶│ Peer B │ │
│ │ │ IKE_AUTH │ │ │
│ └─────────┘ └─────────┘ │
│ │
│ Phase 2 (Child SA) │
│ ┌─────────┐ CREATE_CHILD ┌─────────┐ │
│ │ Peer A │◄──────────────▶│ Peer B │ │
│ │ │ ESP traffic │ │ │
│ └─────────┘ └─────────┘ │
└──────────────────────────────────────────────────────────┘
IPSec Modes¶
Transport Mode:
Original IP header preserved
Only payload encrypted
Used for: host-to-host communication
Tunnel Mode:
Entire original packet encrypted
New IP header added
Used for: site-to-site VPN (most common)
strongSwan (Linux IPSec)¶
# /etc/ipsec.conf
config setup
charondebug="ike 2, knl 2, net 2"
conn site-to-site
authby=secret
left=203.0.113.1
leftsubnet=10.1.0.0/24
right=198.51.100.1
rightsubnet=10.2.0.0/24
ike=aes256-sha256-modp2048
esp=aes256-sha256
keyexchange=ikev2
auto=start
SSH Tunnels¶
SSH tunnels are the duct tape of networking. Not meant for production VPNs, but invaluable for quick access to services behind firewalls.
Local Port Forwarding¶
Forward a local port to a remote service through the SSH server:
# Access remote database as if it were local
ssh -L 5432:db.internal:5432 bastion.example.com
# Now connect to localhost:5432 to reach db.internal:5432
# Syntax: ssh -L local_port:remote_host:remote_port ssh_server
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Laptop │──SSH────▶│ Bastion │────────▶│ Database │
│ :5432 │ tunnel │ │ direct │ :5432 │
└──────────┘ └──────────┘ └──────────┘
Remote Port Forwarding¶
Expose a local service to the remote network:
# Make local web server accessible on the bastion
ssh -R 8080:localhost:3000 bastion.example.com
# Now bastion:8080 reaches your localhost:3000
# Syntax: ssh -R remote_port:local_host:local_port ssh_server
Dynamic Port Forwarding (SOCKS Proxy)¶
# Create a SOCKS5 proxy through the SSH server
ssh -D 1080 bastion.example.com
# Configure your browser to use SOCKS5 proxy at localhost:1080
# All browsing traffic goes through the bastion
SSH Tunnel Options¶
# Background the tunnel (no shell)
ssh -fN -L 5432:db.internal:5432 bastion.example.com
# With keepalive (prevent timeout)
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 \
-fN -L 5432:db.internal:5432 bastion.example.com
# With autossh (auto-reconnect on failure)
autossh -M 0 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 \
-fN -L 5432:db.internal:5432 bastion.example.com
Split Tunneling vs Full Tunneling¶
Full Tunnel:
ALL traffic goes through the VPN
+ Simple, everything protected
- VPN server handles all bandwidth
- Slow for internet browsing
WireGuard: AllowedIPs = 0.0.0.0/0
Split Tunnel:
Only specific subnets go through the VPN
+ Internet traffic goes direct (fast)
+ Less load on VPN server
- Some traffic is not protected
WireGuard: AllowedIPs = 10.0.0.0/24, 192.168.0.0/16
Choose split tunneling for developer access to internal resources. Choose full tunneling for compliance requirements or untrusted networks.
Interview tip: A common interview question: "What is split tunneling and when would you use it?" Answer: split tunneling routes only specific subnets through the VPN while internet traffic goes direct. Use it for developer VPNs to avoid bottlenecking all web browsing through the corporate VPN. Avoid it when compliance requires all traffic to be inspected (PCI-DSS, HIPAA environments).
Protocol Comparison¶
WireGuard OpenVPN IPSec SSH Tunnel
────────────── ────────── ────────── ────────── ──────────
Performance Excellent Good Good Fair
Simplicity Very simple Moderate Complex Simple
Maturity Newer Battle-tested Decades Ancient
Kernel support In-kernel Userspace In-kernel Userspace
NAT traversal Built-in Built-in Needs help Built-in
Port UDP only UDP or TCP ESP/UDP TCP
Use case General VPN Enterprise Site-to-site Quick access
Audit surface ~4000 LOC ~100K LOC Very large Very large
When to Use What¶
Developer VPN / road warrior → WireGuard
Enterprise with PKI/LDAP → OpenVPN
Site-to-site (hardware routers) → IPSec
Quick access to one service → SSH tunnel
Overlay networking (containers) → WireGuard or VXLAN
Wiki Navigation¶
Prerequisites¶
- Networking Deep Dive (Topic Pack, L1)
Related Content¶
- Case Study: IPTables Blocking Unexpected (Case Study, L2) — Firewalls, Linux Networking Tools
- Case Study: API Latency Spike — BGP Route Leak, Fix Is Network ACL (Case Study, L2) — Linux Networking Tools
- Case Study: ARP Flux Duplicate IP (Case Study, L2) — Linux Networking Tools
- Case Study: DHCP Relay Broken (Case Study, L1) — Linux Networking Tools
- Case Study: Duplex Mismatch Symptoms (Case Study, L1) — Linux Networking Tools
- Case Study: Firewall Shadow Rule (Case Study, L2) — Firewalls
- Case Study: Jumbo Frames Partial (Case Study, L2) — Linux Networking Tools
- Case Study: Service Mesh 503s — Envoy Misconfigured, RBAC Policy (Case Study, L2) — Linux Networking Tools
- Case Study: Source Routing Policy Miss (Case Study, L2) — Linux Networking Tools
- Case Study: Stuck NFS Mount (Case Study, L2) — Linux Networking Tools