Chapter 15: IPsec and VPNs
"A VPN doesn't make you secure. It makes your insecure traffic travel through an encrypted pipe. The traffic is still insecure -- it just can't be read in transit."
A VPN encrypts everything between you and the VPN gateway. After that, your traffic exits onto the destination network unencrypted -- well, unencrypted by the VPN anyway. HTTPS and other application-layer encryption still apply. And depending on your split tunneling configuration, some of your traffic might not go through the VPN at all. The details matter more than the padlock icon.
Why VPNs Exist
The internet is a public network. When you send a packet from your laptop to a server, that packet traverses multiple networks owned by different organizations -- your ISP, transit providers, peering points, the destination's ISP. Any of these could theoretically inspect or modify your traffic. The Snowden disclosures in 2013 demonstrated that this was not merely theoretical: intelligence agencies routinely tapped internet backbone connections.
A Virtual Private Network creates an encrypted tunnel across this public infrastructure, making it appear as though your device is directly connected to a private network. The key word is "virtual" -- the network is not physically private, but encryption makes it functionally so.
graph LR
subgraph Without_VPN["Without VPN"]
L1[Laptop] -->|"Plaintext traffic<br/>visible to ISP,<br/>transit providers,<br/>coffee shop WiFi"| I1[Internet] --> S1[Server]
end
subgraph With_VPN["With VPN"]
L2[Laptop] ==>|"Encrypted tunnel<br/>ISP sees gibberish"| VPN[VPN Gateway]
VPN -->|"Decrypted traffic<br/>on private network"| S2[Server]
end
style L1 fill:#ff6b6b,color:#fff
style L2 fill:#44aa44,color:#fff
style VPN fill:#4a9eff,color:#fff
The VPN wraps your packets in another encrypted packet. This is called encapsulation. Your original packet becomes the payload of a new packet that is encrypted and addressed to the VPN gateway. The original destination, the original content, even the original protocol -- all hidden inside the encrypted outer packet.
The IPsec Protocol Suite
IPsec (Internet Protocol Security) is a suite of protocols that provides security at the network layer (Layer 3). Unlike TLS, which protects individual application connections (a single TCP stream), IPsec protects ALL IP traffic between two endpoints. Every packet, every protocol, every port -- if it is routed through the IPsec tunnel, it is encrypted.
IPsec was originally developed for IPv6, where it was mandatory. It was then backported to IPv4, where it is optional. Despite decades of deployment, the complexity of IPsec has been both its strength (it handles every edge case) and its weakness (misconfigurations are common and auditing is difficult).
The Two Core Protocols
IPsec provides two distinct protocols for different security needs. Understanding the difference is important because choosing the wrong one, or misunderstanding what each protects, leads to security gaps.
AH -- Authentication Header (IP Protocol 51)
AH provides data integrity and authentication but NOT encryption. It proves that a packet was not tampered with and came from the claimed sender. The critical characteristic of AH is that it authenticates the entire packet, including most fields of the outer IP header.
graph LR
subgraph AH_Packet["AH Protected Packet"]
IP["IP Header<br/>Src: 10.1.0.5<br/>Dst: 10.2.0.5<br/>Proto: 51 (AH)"]
AHH["AH Header<br/>SPI: 0x1234<br/>Seq: 00042<br/>ICV (HMAC)"]
PL["Payload<br/>(NOT encrypted)<br/>Original TCP/UDP data"]
end
IP --> AHH --> PL
AUTH["Authenticated region<br/>(covers IP header + AH + payload)"] -.->|"HMAC covers<br/>immutable IP fields"| IP
AUTH -.-> AHH
AUTH -.-> PL
style IP fill:#4a9eff,color:#fff
style AHH fill:#ffaa00,color:#000
style PL fill:#ff6b6b,color:#fff
Why would anyone use a protocol that does not encrypt? AH authenticates the IP header itself, which ESP cannot fully do because ESP's authentication only covers the ESP header and encrypted payload, not the outer IP header. In theory, this protects against IP spoofing attacks that modify the source address. In practice, AH has a fatal flaw: because it authenticates the IP header, any device that modifies the IP header breaks the authentication. NAT devices modify the IP header on every packet. Since NAT is ubiquitous on the internet, AH is essentially unusable in most real-world deployments. You will almost never see AH deployed today.
ESP -- Encapsulating Security Payload (IP Protocol 50)
ESP provides confidentiality (encryption), data integrity, and authentication. It is the workhorse of IPsec and the protocol used in virtually all modern deployments.
graph LR
subgraph ESP_Packet["ESP Protected Packet"]
IP2["IP Header<br/>Src: GW1<br/>Dst: GW2<br/>Proto: 50 (ESP)"]
ESPH["ESP Header<br/>SPI: 0x5678<br/>Seq: 00042"]
EPL["Encrypted<br/>Payload<br/>(original packet)"]
ESPT["ESP Trailer<br/>Padding<br/>Next Header"]
ESPA["ESP Auth<br/>ICV (HMAC)"]
end
IP2 --> ESPH --> EPL --> ESPT --> ESPA
ENC["Encrypted region"] -.-> EPL
ENC -.-> ESPT
AUTHR["Authenticated region"] -.-> ESPH
AUTHR -.-> EPL
AUTHR -.-> ESPT
AUTHR -.-> ESPA
style IP2 fill:#4a9eff,color:#fff
style ESPH fill:#ffaa00,color:#000
style EPL fill:#44aa44,color:#fff
style ESPT fill:#44aa44,color:#fff
style ESPA fill:#ff6b6b,color:#fff
Key details of the ESP packet structure:
- SPI (Security Parameter Index): A 32-bit identifier that tells the receiver which Security Association (SA) to use for decrypting this packet. Think of it as a session identifier -- it tells the receiver which keys, algorithms, and parameters to apply.
- Sequence Number: A 32-bit counter providing replay protection. The receiver maintains a sliding window (typically 32 or 64 packets) and rejects packets with duplicate or out-of-window sequence numbers. Extended Sequence Numbers (ESN) extend this to 64 bits for high-speed links.
- Encrypted Payload: The original packet (in tunnel mode) or original payload (in transport mode), encrypted with the negotiated algorithm (AES-GCM is the modern standard).
- ESP Trailer: Contains padding (to align with the block cipher's block size) and the Next Header field identifying the encapsulated protocol.
- ESP Authentication (ICV): An Integrity Check Value -- essentially an HMAC over the ESP header and encrypted payload. When using AEAD ciphers like AES-GCM, this is the GCM authentication tag.
The outer IP header is NOT authenticated by ESP. This is exactly why ESP works with NAT (the NAT device can modify the IP header without breaking authentication) but also means the source IP in the outer header could be spoofed. The authentication of the inner IP header (inside the encrypted payload) provides the real identity verification.
**AH vs ESP -- the definitive comparison:**
| Property | AH | ESP |
|-----------------------|-----------------------|----------------------------|
| Encryption | No | Yes |
| Authentication | Yes (entire packet) | Yes (ESP header + payload) |
| IP header protection | Yes (immutable fields)| No (outer header) |
| NAT compatible | No | Yes (with NAT-T) |
| IP Protocol number | 51 | 50 |
| Modern usage | Nearly extinct | Universal |
| RFC | 4302 | 4303 |
**Bottom line:** Use ESP. Always. AH exists in the specification but serves no practical purpose in modern networks. If you see AH in a configuration, it is either a legacy deployment or a misconfiguration.
Transport Mode vs Tunnel Mode
IPsec operates in two modes that determine what gets protected and how packets are structured. The choice between them determines whether the original IP addresses are visible to network observers.
Transport Mode
In transport mode, IPsec protects only the payload of the original IP packet. The original IP header remains intact and is used for routing. This means an observer can see the source and destination IP addresses but cannot read or tamper with the payload.
graph TD
subgraph Original["Original Packet"]
O_IP["IP Header<br/>Src: Host A (10.1.0.5)<br/>Dst: Host B (10.2.0.5)"]
O_PL["TCP/UDP Payload<br/>(application data)"]
end
subgraph Transport["After Transport Mode ESP"]
T_IP["Original IP Header<br/>Src: 10.1.0.5<br/>Dst: 10.2.0.5<br/>(UNCHANGED)"]
T_ESP["ESP Header"]
T_PL["Encrypted Original Payload"]
T_TR["ESP Trailer + Auth"]
end
Original -->|"IPsec Transport Mode"| Transport
style O_IP fill:#4a9eff,color:#fff
style T_IP fill:#4a9eff,color:#fff
style T_PL fill:#44aa44,color:#fff
Use cases: Direct host-to-host communication where both endpoints support IPsec. Commonly used with L2TP (L2TP/IPsec) for remote access VPNs on older systems. Also used for securing traffic between two servers in the same data center where the IP addresses are not sensitive.
Tunnel Mode
In tunnel mode, IPsec encapsulates the ENTIRE original packet -- header and all -- inside a new IP packet. The original packet becomes the encrypted payload. An observer sees only the tunnel endpoints (typically the VPN gateways), not the actual source and destination of the traffic.
graph TD
subgraph Original2["Original Packet"]
O2_IP["IP Header<br/>Src: Host A (10.1.0.5)<br/>Dst: Host B (10.2.0.5)"]
O2_PL["TCP/UDP Payload"]
end
subgraph Tunnel["After Tunnel Mode ESP"]
T2_NIP["NEW IP Header<br/>Src: GW1 (203.0.113.1)<br/>Dst: GW2 (198.51.100.1)"]
T2_ESP["ESP Header"]
T2_ORIG["Encrypted:<br/>Original IP Header +<br/>Original Payload<br/>(entire original packet)"]
T2_TR["ESP Trailer + Auth"]
end
Original2 -->|"IPsec Tunnel Mode"| Tunnel
style O2_IP fill:#4a9eff,color:#fff
style T2_NIP fill:#ff6b6b,color:#fff
style T2_ORIG fill:#44aa44,color:#fff
Use cases: Site-to-site VPNs (connecting two networks through their gateways) and remote access VPNs (connecting a device to a corporate gateway). This is the default mode for most VPN deployments.
Tunnel mode hides even where the traffic is actually going. An attacker sniffing the network only sees traffic between the two VPN gateways. The original source and destination IP addresses are encrypted inside the ESP payload. The outer IP header shows only the tunnel endpoints. An observer on the coffee shop WiFi sees encrypted traffic flowing between your laptop and your corporate VPN gateway at 203.0.113.1. They cannot see that you are actually communicating with the internal database at 10.2.5.100. They cannot see what protocol you are using, what port, or the content. All they know is the volume and timing of the traffic.
IKE: Internet Key Exchange
Before IPsec can encrypt anything, the two endpoints need to agree on encryption algorithms, exchange keys, and authenticate each other. This negotiation is handled by IKE (Internet Key Exchange), which runs on UDP port 500 (and port 4500 for NAT traversal).
Think of IKE as the handshake before the conversation. You cannot start encrypting until both sides agree on HOW to encrypt and have the shared keys to do it. IKE establishes the Security Associations -- the set of parameters (algorithms, keys, lifetimes) that govern the IPsec tunnel.
IKEv2 -- The Modern Standard
IKEv2 (RFC 7296) is the current standard. It consolidates the negotiation into an efficient four-message exchange that establishes both the IKE SA (for protecting the negotiation itself) and the first Child SA (for protecting actual data traffic).
sequenceDiagram
participant I as Initiator
participant R as Responder
Note over I,R: IKE_SA_INIT (2 messages, unencrypted)
I->>R: SA proposals, KE (DH public value),<br/>Nonce, NAT detection
R->>I: Selected SA, KE (DH public value),<br/>Nonce, NAT detection
Note over I,R: Both sides now compute:<br/>SKEYSEED from DH exchange<br/>Derive encryption keys for IKE SA
Note over I,R: IKE_AUTH (2 messages, encrypted under IKE SA)
I->>R: Identity, AUTH (proof of identity),<br/>SA proposals for Child SA,<br/>Traffic Selectors (what to encrypt)
R->>I: Identity, AUTH (proof of identity),<br/>Selected Child SA,<br/>Traffic Selectors
Note over I,R: Result: IKE SA established (management channel)<br/>First Child SA established (data channel)<br/>Total: 4 messages, ~2 round trips
Note over I,R: Optional: CREATE_CHILD_SA
I->>R: Additional Child SA or rekey request
R->>I: Confirmation
The four-message exchange breaks down as follows:
IKE_SA_INIT (messages 1-2): Exchanged in the clear. The initiator proposes cryptographic parameters and provides its Diffie-Hellman public value. The responder selects parameters and provides its DH value. After this exchange, both sides can compute the shared secret and derive keys for encrypting subsequent messages. NAT detection payloads are included to determine if NAT traversal is needed.
IKE_AUTH (messages 3-4): Encrypted under the IKE SA established in the previous step. Both sides authenticate (using certificates, pre-shared keys, or EAP) and negotiate the first Child SA (the actual IPsec data tunnel). Traffic selectors define which traffic will be encrypted.
IKEv1 (Legacy) -- Why You Still See It
IKEv1 required 6-9 messages across two separate phases (Phase 1 for the IKE SA, Phase 2 for each IPsec SA). It had two Phase 1 modes (Main Mode and Aggressive Mode) with different security properties. Aggressive Mode was faster but leaked the initiator's identity in the clear, making it vulnerable to dictionary attacks against pre-shared keys.
Why would anyone still use IKEv1? Legacy equipment. Many VPN appliances and firewall firmware from the 2000s and early 2010s only support IKEv1. In enterprise networks, you will find 15-year-old Cisco ASA appliances running IKEv1 in Main Mode with 3DES-SHA1, and nobody wants to touch them because they "work" and the vendor stopped providing updates. For new deployments, there is no reason to use IKEv1. IKEv2 is faster, more reliable (it has built-in dead peer detection and reconnection), supports MOBIKE for seamless roaming on mobile devices, and handles NAT traversal natively.
**NAT Traversal (NAT-T):**
IPsec and NAT have a troubled relationship. NAT modifies IP headers, which breaks AH entirely. ESP fares better but still has issues: NAT devices cannot inspect inside encrypted ESP packets to update port numbers for port-based NAT (NAPT), and many NAT devices simply drop ESP packets (IP protocol 50) because they don't understand them.
NAT-T (RFC 3947/3948) solves this by encapsulating ESP packets inside UDP:
\```
Without NAT-T: [IP Header][ESP Header][Encrypted Payload]
IP Proto: 50
NAT device: "What is this? Drop it."
With NAT-T: [IP Header][UDP Header (port 4500)][ESP Header][Encrypted Payload]
IP Proto: 17 (UDP), Dst Port: 4500
NAT device: "UDP traffic, I can handle this."
\```
IKEv2 detects NAT automatically during IKE_SA_INIT by including NAT detection payloads (hashes of IP:port pairs). If NAT is detected, it switches to UDP encapsulation on port 4500 for all subsequent traffic. IKEv1 required separate NAT-T configuration and did not always negotiate it correctly.
IPsec in Practice: Configuration Examples
Real-world IPsec configuration requires understanding how the pieces fit together. Here is a complete site-to-site VPN configuration using strongSwan, the most widely deployed open-source IKEv2 implementation.
Configure a site-to-site IPsec VPN with strongSwan:
\```bash
# /etc/swanctl/swanctl.conf on Gateway 1 (NY office)
connections {
ny-to-london {
version = 2 # IKEv2
local_addrs = 203.0.113.1 # NY gateway public IP
remote_addrs = 198.51.100.1 # London gateway public IP
local {
auth = pubkey # Certificate authentication
certs = ny-gateway.pem
id = ny-gw.company.com
}
remote {
auth = pubkey
id = london-gw.company.com
}
children {
office-traffic {
local_ts = 10.1.0.0/16 # NY office network
remote_ts = 10.2.0.0/16 # London office network
esp_proposals = aes256gcm128-prfsha256-ecp256 # Modern crypto
start_action = trap # Establish on first matching traffic
dpd_action = restart # Restart on dead peer detection
}
}
proposals = aes256-sha256-ecp256 # IKE crypto
rekey_time = 86400 # Rekey IKE SA daily
}
}
# Load the configuration
sudo swanctl --load-all
# Check status
sudo swanctl --list-sas
# ny-to-london: #1, ESTABLISHED, IKEv2
# local 'ny-gw.company.com' @ 203.0.113.1[500]
# remote 'london-gw.company.com' @ 198.51.100.1[500]
# AES_CBC-256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
# established 3420s ago, rekey in 82980s
# office-traffic: #1, reqid 1, INSTALLED, TUNNEL, ESP:AES_GCM_16-256
# installed 3420s ago, rekey in 3180s, expires in 3780s
# in c39a2e1d, 42189 bytes, 312 packets
# out ce8f3b2a, 38472 bytes, 287 packets
# local 10.1.0.0/16
# remote 10.2.0.0/16
\```
Debugging IPsec:
\```bash
# Check if IKE packets are flowing (UDP 500 and 4500)
sudo tcpdump -i eth0 'port 500 or port 4500' -nn -v
# Check for ESP packets (protocol 50)
sudo tcpdump -i eth0 proto 50 -nn
# View IPsec Security Associations in the kernel
sudo ip xfrm state
# src 203.0.113.1 dst 198.51.100.1
# proto esp spi 0xc39a2e1d reqid 1 mode tunnel
# enc cbc(aes) 0x... (key material)
# auth hmac(sha256) 0x...
# View IPsec policies (which traffic triggers encryption)
sudo ip xfrm policy
# src 10.1.0.0/16 dst 10.2.0.0/16
# dir out priority 375423
# tmpl src 203.0.113.1 dst 198.51.100.1
# proto esp spi 0xce8f3b2a reqid 1 mode tunnel
# Common failure diagnostics
sudo swanctl --log # Real-time IKE negotiation log
# Look for:
# - "NO_PROPOSAL_CHOSEN" → mismatched crypto proposals
# - "AUTHENTICATION_FAILED" → wrong PSK or cert issue
# - "TS_UNACCEPTABLE" → mismatched traffic selectors
# - "INVALID_KE_PAYLOAD" → DH group mismatch
\```
VPN Architectures
Site-to-Site VPN
Connects two entire networks through their respective gateways. All traffic between the networks is automatically encrypted by the gateways -- individual hosts do not need VPN client software.
graph LR
subgraph NY["New York Office<br/>10.1.0.0/16"]
PC1[PC 10.1.0.10]
PC2[PC 10.1.0.11]
SRV1[Server 10.1.1.5]
GW1[VPN Gateway<br/>203.0.113.1]
end
subgraph LDN["London Office<br/>10.2.0.0/16"]
PC3[PC 10.2.0.10]
PC4[PC 10.2.0.11]
SRV2[Server 10.2.1.5]
GW2[VPN Gateway<br/>198.51.100.1]
end
PC1 --> GW1
PC2 --> GW1
SRV1 --> GW1
GW1 ==>|"IPsec Tunnel<br/>All 10.1.x ↔ 10.2.x traffic<br/>encrypted automatically"| GW2
GW2 --> PC3
GW2 --> PC4
GW2 --> SRV2
Remote Access VPN
Connects individual devices to a corporate network. The VPN client on the device creates a tunnel to the corporate VPN gateway.
graph LR
subgraph Remote["Remote Workers"]
L1["Laptop (Coffee shop)<br/>IPsec/IKEv2 client"]
L2["Phone (Hotel WiFi)<br/>IKEv2 + MOBIKE"]
L3["Tablet (Home)<br/>WireGuard client"]
end
subgraph Corp["Corporate Network"]
VGW["VPN Gateway<br/>vpn.company.com"]
APP["Application Servers"]
DB["Database"]
MAIL["Email"]
end
L1 ==>|"Encrypted"| VGW
L2 ==>|"Encrypted"| VGW
L3 ==>|"Encrypted"| VGW
VGW --> APP
VGW --> DB
VGW --> MAIL
IKEv2 with MOBIKE (Mobility and Multihoming Protocol, RFC 4555) is particularly valuable for mobile users. When a phone switches from WiFi to cellular, the IP address changes. Without MOBIKE, the IPsec tunnel breaks and must be renegotiated (a process that takes seconds and drops connections). With MOBIKE, the device notifies the gateway of its new IP, and the tunnel is seamlessly updated without renegotiation. This is critical for voice calls over VPN and other latency-sensitive applications.
WireGuard: The Modern Alternative
IPsec is powerful but complex. The configuration alone can fill books. Entire days can be spent debugging IKE negotiation failures caused by a single mismatched parameter. WireGuard takes a fundamentally different approach -- simplicity as a security feature.
WireGuard is a modern VPN protocol designed by Jason Donenfeld. It was merged into the Linux kernel in March 2020 (version 5.6) and has since been ported to Windows, macOS, iOS, Android, and BSDs.
Design Philosophy: Fewer Moving Parts
**IPsec vs WireGuard -- complexity comparison:**
| Aspect | IPsec (Linux kernel) | WireGuard (Linux kernel) |
|---------------------|-----------------------------|------------------------------|
| Lines of code | ~400,000 | ~4,000 |
| Cipher options | 30+ algorithms | 1 fixed cipher suite |
| Key exchange | IKE (complex state machine) | Noise protocol (1-RTT) |
| Configuration | Dozens of parameters | ~10 lines |
| State machine | Multiple phases, modes | Minimal, stateless-like |
| CVEs (2015-2025) | Dozens | Single digits |
| Auditability | Difficult (code volume) | Feasible (small codebase) |
| Performance | Good | Excellent (SIMD optimized) |
**WireGuard's fixed cipher suite:**
- **ChaCha20** for symmetric encryption (faster than AES on devices without hardware AES support)
- **Poly1305** for authentication (used as AEAD with ChaCha20)
- **Curve25519** for ECDH key exchange
- **BLAKE2s** for hashing
- **SipHash** for hashtable keys (DoS prevention)
- **HKDF** for key derivation
There is no negotiation. Both sides must use this exact cipher suite. This eliminates cipher downgrade attacks entirely -- there is nothing to downgrade to.
What happens when one of those algorithms is broken? WireGuard's approach is versioning. If ChaCha20 is broken, they will release WireGuard v2 with a new cipher suite. No negotiation means no downgrade attacks -- you cannot trick a WireGuard peer into using a weaker algorithm because there is only one option. The tradeoff is that upgrading requires coordinated deployment across all peers, but in practice this is manageable because WireGuard is typically deployed via configuration management tools.
WireGuard Handshake
The WireGuard handshake uses the Noise IK pattern and completes in a single round trip (2 messages):
sequenceDiagram
participant I as Initiator
participant R as Responder
Note over I,R: Initiator knows Responder's static public key<br/>(pre-configured)
I->>R: Message 1: Initiator's ephemeral public key,<br/>Initiator's static public key (encrypted),<br/>Timestamp (encrypted), MAC
Note over R: Responder decrypts, verifies timestamp<br/>(replay protection), computes shared secrets
R->>I: Message 2: Responder's ephemeral public key,<br/>Empty (encrypted), MAC
Note over I,R: Both sides derive symmetric session keys<br/>Tunnel is ready. Total: 1 round trip.<br/><br/>Compare to IKEv2: 2 round trips (4 messages)<br/>Compare to IKEv1: 3+ round trips (6-9 messages)
WireGuard Configuration
# Server configuration (/etc/wireguard/wg0.conf)
[Interface]
PrivateKey = oKq3Igx6Z8h7vLqV8Yb1Pz/rTmJ2w3hNx4yB5kPaHk=
Address = 10.0.0.1/24
ListenPort = 51820
# Optional: PostUp/PostDown for firewall rules
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]
# Remote laptop
PublicKey = aB3cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV3wX4yZ5zA=
AllowedIPs = 10.0.0.2/32
[Peer]
# London office gateway (entire subnet routed)
PublicKey = xY9zA8bC7dE6fG5hI4jK3lM2nO1pQ0rS9tU8vW7xY=
AllowedIPs = 10.0.0.3/32, 10.2.0.0/16
Endpoint = london-gw.company.com:51820
PersistentKeepalive = 25
# Client configuration (/etc/wireguard/wg0.conf)
[Interface]
PrivateKey = yZ5xW4vU3tS2rQ1pO0nM9lK8jI7hG6fE5dC4bA3zA=
Address = 10.0.0.2/32
DNS = 10.0.0.1
[Peer]
PublicKey = cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV3wX4yZ5zA6bA=
Endpoint = vpn.company.com:51820
AllowedIPs = 0.0.0.0/0 # Route ALL traffic through VPN (full tunnel)
# Or: AllowedIPs = 10.0.0.0/8, 172.16.0.0/12 # Split tunnel
PersistentKeepalive = 25 # Send keepalive every 25 seconds (NAT traversal)
Set up and manage a WireGuard tunnel:
\```bash
# Generate key pair
wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey
# oKq3Igx6Z8h7vLqV8Yb1Pz/rTmJ2w3hNx4yB5kPaHk=
cat publickey
# cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV3wX4yZ5zA6bA=
# Quick setup using wg-quick
sudo wg-quick up wg0
# [#] ip link add wg0 type wireguard
# [#] wg setconf wg0 /dev/fd/63
# [#] ip -4 address add 10.0.0.2/32 dev wg0
# [#] ip link set mtu 1420 up dev wg0
# [#] wg set wg0 fwmark 51820
# [#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820
# [#] ip -4 rule add not fwmark 51820 table 51820
# Check tunnel status
sudo wg show
# interface: wg0
# public key: cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV3wX4yZ5zA6bA=
# private key: (hidden)
# listening port: 41820
#
# peer: server_public_key_here
# endpoint: vpn.company.com:51820
# allowed ips: 0.0.0.0/0
# latest handshake: 42 seconds ago
# transfer: 1.24 MiB received, 384 KiB sent
# Test connectivity through the tunnel
ping -c 3 10.0.0.1
# PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
# 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=23.4 ms
# Monitor tunnel traffic
sudo tcpdump -i wg0 -nn
# Bring tunnel down
sudo wg-quick down wg0
\```
Cryptokey Routing
WireGuard uses a concept called "cryptokey routing" that elegantly unifies the routing table and the cryptographic configuration. Each peer's public key is associated with a set of allowed IP addresses. This creates a bidirectional mapping:
Outbound: When a packet needs to be sent to an IP address, WireGuard looks up which peer has that address in its AllowedIPs. The packet is encrypted with that peer's session key and sent to that peer's endpoint.
Inbound: When an encrypted packet arrives from a peer, it is decrypted using that peer's session key. The source IP of the decrypted packet is then checked against that peer's AllowedIPs. If the source IP is not in the allowed list, the packet is dropped. This prevents a peer from spoofing traffic from IP addresses they are not authorized to use.
Split Tunneling: Convenience vs Security
Split tunneling allows some traffic to go through the VPN while other traffic goes directly to the internet. In WireGuard, this is controlled by the AllowedIPs setting.
graph TD
subgraph Full["Full Tunnel (AllowedIPs = 0.0.0.0/0)"]
F_L[Laptop] ==>|"ALL traffic"| F_VPN[VPN Gateway]
F_VPN --> F_CORP[Corporate Resources]
F_VPN --> F_INT[Internet<br/>Netflix, YouTube, etc.]
end
subgraph Split["Split Tunnel (AllowedIPs = 10.0.0.0/8)"]
S_L[Laptop] ==>|"10.x.x.x traffic only"| S_VPN[VPN Gateway]
S_L -->|"All other traffic<br/>goes direct"| S_INT[Internet]
S_VPN --> S_CORP[Corporate Resources]
end
style F_L fill:#44aa44,color:#fff
style S_L fill:#ffaa00,color:#000
Split tunneling makes sense from a performance perspective -- why push Netflix through the corporate VPN? Performance, bandwidth costs, and user experience all benefit. Corporate VPN bandwidth is expensive and limited. Routing all traffic through it adds latency and can saturate the VPN concentrator. But split tunneling creates real security gaps that you need to understand.
**Why split tunneling is dangerous:**
1. **Dual-homed exposure:** The laptop is simultaneously connected to the corporate network (via VPN) and the public internet (directly). Malware on the laptop could bridge these networks, pivoting from the compromised machine into the corporate network.
2. **DNS leaks:** DNS queries for corporate resources might go to the public DNS resolver instead of the corporate one, leaking which internal services an employee is accessing. This is especially common when the VPN configuration does not force DNS through the tunnel. An attacker on the local network can see DNS queries like `internal-payroll.corp.company.com`.
3. **Bypassed security controls:** Corporate web proxies, data loss prevention (DLP) systems, and network-based threat detection only see VPN traffic. Direct internet traffic bypasses all of these controls.
4. **Malware C2 communication:** If the device is compromised, the attacker's command and control traffic goes directly to the internet, bypassing corporate network monitoring that might detect the C2 beaconing pattern.
5. **Data exfiltration path:** A compromised device can download sensitive data from corporate resources via VPN and simultaneously upload it to the internet directly, bypassing DLP inspection.
**When split tunneling is acceptable:**
- Managed devices with endpoint detection and response (EDR) running
- DNS forced through the tunnel regardless of routing
- HTTPS inspection proxy enforced on the endpoint
- Low-security environments where the bandwidth savings justify the risk
VPN vs Zero Trust
Here is the fundamental problem with VPNs in the traditional model: they create a binary security posture. You are either "inside the network" (trusted) or "outside" (untrusted). Once you VPN in, you typically have broad access to the corporate network. It is a castle-and-moat model -- the VPN is the drawbridge.
The Castle-and-Moat Problem
graph TD
subgraph Traditional["Traditional VPN Model (Castle & Moat)"]
ATK[Attacker<br/>Outside] -->|"Blocked by<br/>firewall"| FW[Firewall /<br/>VPN Gateway]
USER[Employee<br/>with VPN] ==>|"VPN authenticated<br/>Full network access!"| FW
FW --> DB[(Database)]
FW --> APP[Applications]
FW --> MAIL[Email Server]
FW --> HR[HR System]
FW --> FIN[Finance System]
end
subgraph Problem["The Problem"]
NOTE["Once inside, lateral movement is easy.<br/>VPN access = implicit trust for<br/>EVERYTHING on the network."]
end
style ATK fill:#ff4444,color:#fff
style USER fill:#44aa44,color:#fff
style NOTE fill:#ffaa00,color:#000
Zero Trust Architecture
Zero Trust ("never trust, always verify") treats every request as potentially hostile, regardless of network location. Your source IP being on the corporate network does not grant you access to anything.
graph TD
USER2[Employee] --> PE[Policy Engine]
PE -->|"Identity: verified ✓<br/>Device: compliant ✓<br/>MFA: completed ✓<br/>Context: normal ✓"| APP2[App A<br/>ALLOWED]
PE -->|"Identity: verified ✓<br/>Device: compliant ✓<br/>MFA: completed ✓<br/>Role: insufficient ✗"| DB2[(Database<br/>DENIED)]
PE -->|"Identity: verified ✓<br/>Device: non-compliant ✗<br/>(OS not patched)"| HR2[HR System<br/>DENIED]
style APP2 fill:#44aa44,color:#fff
style DB2 fill:#ff4444,color:#fff
style HR2 fill:#ff4444,color:#fff
In Zero Trust, there is not necessarily no VPN at all. Many Zero Trust implementations still use encrypted tunnels for transport security. But the trust decision moves from "are you on the VPN?" to "do you meet the policy requirements for this specific resource at this specific moment?" Google's BeyondCorp is the canonical example -- every Google employee accesses internal applications through an identity-aware proxy, regardless of whether they are in a Google office or at a coffee shop. The network location is irrelevant.
A mid-size tech company (about 500 employees) relied entirely on their VPN for security. If you could VPN in, you could reach every server, database, and internal tool. The policy was simple: VPN = trusted.
A contractor's credentials were phished. The attacker VPN'd in and spent six weeks exploring the network, eventually exfiltrating their entire customer database -- 4.2 million records including names, emails, and hashed passwords. The attacker also pivoted through the network to access source code repositories, financial reports, and internal communications.
The post-mortem was brutal: the VPN gave the attacker the same network access as a trusted employee. There was no network segmentation, no per-application authentication, no behavioral monitoring. The total cost of the breach: $8.4 million in notification costs, credit monitoring, regulatory fines, legal fees, and lost business.
They migrated to a Zero Trust model over the following 18 months. Each application now requires individual authentication and authorization. The VPN still exists for network-level encryption, but it no longer grants implicit access to anything. The cost of the migration: approximately $1.2 million in engineering time and licensing. A fraction of the breach cost.
**VPN and Zero Trust are complementary, not mutually exclusive.** A modern architecture might use:
- **WireGuard or IPsec** for encrypting traffic between endpoints (protecting against network eavesdropping)
- **Identity-aware proxy** (Google BeyondCorp Enterprise, Cloudflare Access, Zscaler Private Access, Microsoft Entra Application Proxy) for application-level access control
- **Device posture checking** at access time (is antivirus running? Is the OS patched? Is the disk encrypted? Is the device enrolled in MDM?)
- **Conditional access policies** (MFA required for sensitive apps, block access from non-compliant devices, require step-up auth from unfamiliar locations)
- **Microsegmentation** (workloads can only communicate with explicitly allowed other workloads -- a compromised web server cannot reach the database server unless a policy explicitly allows it)
The VPN provides the encrypted pipe. Zero Trust provides the gate at each door within the building. You need both.
VPN Protocol Comparison
| Feature | IPsec/IKEv2 | OpenVPN | WireGuard |
|------------------|----------------|----------------|-----------------|
| Layer | 3 (Network) | 3 (via TUN/TAP)| 3 (Network) |
| Encryption | Negotiable | OpenSSL/TLS | ChaCha20-Poly1305|
| Code complexity | ~400K lines | ~100K lines | ~4K lines |
| Performance | Good | Moderate | Excellent |
| Latency | Low | Higher (TLS) | Lowest |
| NAT traversal | NAT-T (UDP 4500)| Native (UDP) | Native (UDP) |
| Mobile roaming | MOBIKE (IKEv2) | Reconnect | Seamless |
| Configuration | Complex | Moderate | Simple |
| OS support | Native (most) | Requires client| Kernel module |
| Auditability | Difficult | Moderate | Easy |
| Enterprise use | Dominant | Common | Growing rapidly |
| Cipher agility | Yes (dozens) | Yes (OpenSSL) | No (versioned) |
| TCP fallback | No* | Yes (TCP 443) | No |
| Firewall bypass | Moderate | Excellent** | Moderate |
*IPsec can use UDP encapsulation but not TCP.
**OpenVPN on TCP 443 is indistinguishable from HTTPS to most firewalls.
**Recommendation by use case:**
- **Enterprise site-to-site:** IPsec/IKEv2 (widest device support, industry standard, interoperable)
- **Remote access (new deployment):** WireGuard (simplest, fastest, smallest attack surface)
- **Restrictive networks (hotels, airports):** OpenVPN on TCP 443 (bypasses most firewalls)
- **Mobile users:** IKEv2 with MOBIKE or WireGuard (both handle network roaming gracefully)
- **Maximum security (government/military):** IPsec with CNSA suite (required by many compliance frameworks)
What You've Learned
- IPsec operates at the network layer, protecting all IP traffic between endpoints using two protocols: AH (authentication only, incompatible with NAT, essentially obsolete) and ESP (encryption + authentication, the universal choice)
- Transport mode protects only the payload while preserving the original IP header; tunnel mode encapsulates the entire original packet inside a new encrypted packet, hiding the true source and destination from observers
- IKEv2 negotiates IPsec parameters in 4 messages (2 round trips), supports MOBIKE for mobile roaming, and handles NAT traversal natively -- always prefer it over IKEv1 for new deployments
- Site-to-site VPNs connect entire networks through gateways transparently; remote access VPNs connect individual devices to a corporate gateway, requiring client software
- WireGuard offers a radically simpler alternative with ~4,000 lines of kernel code, a fixed cipher suite (ChaCha20/Poly1305/Curve25519), 1-RTT handshake, and cryptokey routing that unifies cryptographic identity with network routing
- Split tunneling improves performance by routing only corporate traffic through the VPN but creates security gaps including dual-homed exposure, DNS leaks, bypassed DLP, and covert malware communication channels
- Traditional VPNs create a castle-and-moat model where VPN access implies network-wide trust; Zero Trust architecture replaces this with per-request, per-resource verification based on identity, device posture, and context
- VPN and Zero Trust are complementary: VPNs provide encrypted transport, while Zero Trust provides granular access control -- the VPN encrypts the pipe, Zero Trust gates each door
- NAT-T wraps ESP in UDP port 4500, solving the long-standing incompatibility between IPsec and NAT; IKEv2 negotiates this automatically
- IPsec debugging requires monitoring UDP ports 500/4500 and ESP protocol 50, checking SA status with
swanctl --list-sasorip xfrm state, and reading IKE logs for negotiation failures likeNO_PROPOSAL_CHOSENorAUTHENTICATION_FAILED