What is WireGuard – a lightweight, and open-source VPN (Virtual Private Network) protocol

Excellent question — since Tailscale is built on top of WireGuard, understanding WireGuard will help you see the foundation of modern secure networking.
Let’s go step by step 👇


🧩 What is WireGuard?

WireGuard is a modern, lightweight, and open-source VPN (Virtual Private Network) protocol and implementation.
It’s designed to be simpler, faster, and more secure than traditional VPN protocols like IPSec or OpenVPN.

Key facts:

  • Created by Jason A. Donenfeld in 2015.
  • Officially merged into the Linux kernel in 2020 (Linux 5.6).
  • Available for all major platforms — Linux, macOS, Windows, Android, iOS.
  • Licensed under the GPLv2 open source license.

⚙️ How WireGuard Works (Simplified Flow)

WireGuard uses public key cryptography and a concept called peers to establish secure tunnels between devices.
Each device that connects to the network is called a peer.

Here’s the overall flow:


1. 🔑 Key Pair Generation

Each peer generates:

  • A private key (kept secret)
  • A public key (shared with others)

This works like SSH:

  • Your private key stays on your device.
  • Your public key identifies you to others.

2. 📜 Configuration

Each peer (e.g., a server or client) has a small configuration file specifying:

  • Its private key
  • Its internal VPN IP (e.g., 10.0.0.2/24)
  • The public key of the remote peer
  • The endpoint (IP:port) of the remote peer
  • Allowed IPs (what traffic should go through the tunnel)

Example:

# On client
[Interface]
PrivateKey = <client-private-key>
Address = 10.0.0.2/24

[Peer]
PublicKey = <server-public-key>
Endpoint = 203.0.113.5:51820
AllowedIPs = 0.0.0.0/0

3. 🧠 Handshake Process

When two peers want to communicate:

  • The client initiates a handshake using Noise Protocol Framework, a modern, minimal cryptographic handshake protocol.
  • It uses Curve25519 for key exchange, ChaCha20 for encryption, Poly1305 for authentication, and BLAKE2s for hashing.
  • The handshake results in ephemeral session keys (short-lived), adding perfect forward secrecy.

Result: Both peers derive symmetric encryption keys and start a secure tunnel.


4. 🔒 Encryption & Tunnel Creation

Once the handshake is done:

  • All packets between peers are encrypted using ChaCha20-Poly1305.
  • Only packets from authenticated peers (known public keys) are accepted.
  • Each peer directly sends UDP packets to the other (default port 51820).
  • WireGuard sits at Layer 3 (network layer), providing an encrypted IP tunnel.

5. ⚡ Data Transfer

  • Data moves seamlessly between peers via the tunnel.
  • WireGuard doesn’t use “sessions” — it’s stateless and only keeps minimal state.
  • If a peer disappears (e.g., changes IP), the other peer can reconnect automatically without restarting.

🧠 Architectural Simplicity

FeatureWireGuardIPSecOpenVPN
Codebase size~4,000 lines400,000+600,000+
SpeedVery fastModerateSlower
SecurityModern cryptoOlder crypto, complexOlder crypto
ConfigSimple (few lines)ComplexComplex
OS SupportKernel-levelKernel/userUser space
TransportUDP onlyUDP/TCPUDP/TCP

Because it’s tiny, it’s easier to audit, maintain, and embed in apps and devices.


🔐 Key Security Principles

WireGuard uses:

  • ChaCha20 — for encryption
  • Poly1305 — for message authentication
  • Curve25519 — for key exchange
  • BLAKE2s — for hashing
  • SipHash24 — for hashtable keys
  • HKDF — for key derivation

It follows a strict “cryptographic minimalism” approach — no optional algorithms, no negotiation, fewer mistakes.


🌐 Network Topologies Supported

WireGuard supports:

  • Point-to-point (single client ↔ single server)
  • Mesh networks (each peer talks directly to others)
  • Hub-and-spoke setups (many clients connect to one server)
  • Subnet routing (a peer acts as a gateway for a LAN)
  • Roaming clients (e.g., mobile devices changing IPs)

This flexibility is what allows Tailscale to build its mesh VPN model over WireGuard.


🧩 Integration with Tailscale

Tailscale uses WireGuard’s core tunneling and encryption features, but adds:

  • Automatic key distribution
  • NAT traversal
  • DNS management
  • Access control (ACLs)
  • Single Sign-On (SSO)
  • DERP relay fallback

So Tailscale handles all the coordination and policy logic, while WireGuard does the packet encryption and secure transport.


🚀 Advantages

Blazing fast — minimal CPU overhead.
Secure by design — modern cryptography only.
Tiny codebase — easier auditing.
Cross-platform — runs almost everywhere.
Simple setup — just a few lines of config.


⚠️ Limitations

  • No built-in user authentication (depends on key pairs).
  • No built-in key distribution (handled manually or by tools like Tailscale).
  • UDP-only (some networks block UDP).
  • Kernel integration may vary by OS.

flowchart LR
  %% ===============
  %% WIREGUARD ARCHITECTURE (COMPONENTS + DATA PATHS)
  %% ===============

  %% -------- Peer A --------
  subgraph PeerA["Peer A"]
    direction TB
    AOS["OS Kernel"]
    subgraph A_WG["WireGuard Kernel Module (wg)"]
      direction TB
      A_wg0_iface["wg0 (virtual interface)"]
      A_Crypto["ChaCha20-Poly1305\nCurve25519 key exchange\nBLAKE2s hashing\nHKDF KDF"]
      A_Keys["Key Material\n- Private key (A)\n- Remote Public key (B)\n- Ephemeral session keys"]
      A_Route["AllowedIPs table\n(what prefixes go to peer B)"]
    end
    A_Net["NIC + UDP 51820"]
  end

  %% -------- Peer B --------
  subgraph PeerB["Peer B"]
    direction TB
    BOS["OS Kernel"]
    subgraph B_WG["WireGuard Kernel Module (wg)"]
      direction TB
      B_wg0_iface["wg0 (virtual interface)"]
      B_Crypto["ChaCha20-Poly1305\nCurve25519 key exchange\nBLAKE2s hashing\nHKDF KDF"]
      B_Keys["Key Material\n- Private key (B)\n- Remote Public key (A)\n- Ephemeral session keys"]
      B_Route["AllowedIPs table\n(routes for Peer A)"]
    end
    B_Net["NIC + UDP 51820"]
  end

  %% -------- Apps --------
  subgraph AppA["User Space Apps (Peer A)"]
    direction TB
    A_App1["App (TCP/UDP)"]
  end
  subgraph AppB["User Space Apps (Peer B)"]
    direction TB
    B_App1["Service (TCP/UDP)"]
  end

  %% -------- Data path: A -> B --------
  A_App1 -->|"IP dst in AllowedIPs(B)"| AOS
  AOS -->|"route decision"| A_wg0_iface
  A_wg0_iface -->|"encrypt packet"| A_Crypto --> A_Net
  A_Net -->|"UDP/IPv4 or v6 (outer) over Internet"| B_Net
  B_Net -->|"decrypt"| B_Crypto --> B_wg0_iface
  B_wg0_iface -->|"deliver inner packet"| BOS --> B_App1

  %% -------- Data path: B -> A (return) --------
  B_App1 --> BOS --> B_wg0_iface --> B_Crypto --> B_Net --> A_Net --> A_Crypto --> A_wg0_iface --> AOS --> A_App1

  %% -------- Styling --------
  classDef box fill:#f6f6ff,stroke:#6366f1,stroke-width:1px,color:#111;
  classDef wg fill:#eefbf4,stroke:#10b981,stroke-width:1px,color:#111;
  classDef net fill:#fff7ed,stroke:#f59e0b,stroke-width:1px,color:#111;
  classDef app fill:#fafafa,stroke:#9ca3af,stroke-width:1px,color:#111;

  class A_WG,B_WG wg
  class A_Net,B_Net net
  class A_App1,B_App1 app
  class A_wg0_iface,B_wg0_iface,A_Crypto,B_Crypto,A_Keys,B_Keys,A_Route,B_Route box
sequenceDiagram
participant A as Peer_A_Initiator
participant Net as Internet_UDP
participant B as Peer_B_Responder
A->>B: Initiation (Noise IK): Ae, encrypted static proof, timestamp_counter
activate B
B-->>A: Response: Be, encrypted payload (identity, cookie, etc.)
deactivate B
A->>B: First encrypted data packet (under session key)
B-->>A: First encrypted data packet (reverse)
rect rgba(255,248,220,0.5)
A->>B: Roaming re-initiation from new IP_port tuple
B-->>A: Validate tuple and rotate keys
end
sequenceDiagram
participant A as Peer_A_Initiator
participant Net as Internet_UDP
participant B as Peer_B_Responder
Note over A,B: Static public keys are exchanged out of band. Peer A knows public key of B and Peer B knows public key of A. Each peer holds its own private key.
A->>B: Initiation using Noise_IK pattern. Includes Ae ephemeral key and encrypted static proof with timestamp counter.
activate B
B-->>A: Response with Be ephemeral key and encrypted payload such as identity and cookie.
deactivate B
Note over A,B: Both sides derive session keys using HKDF based on static and ephemeral keys for ChaCha20 Poly1305 encryption.
A->>B: First encrypted data packet using session key.
B-->>A: First encrypted data packet in reverse direction.
Note over A,B: Rekeying happens periodically or by counter timeout. PersistentKeepalive may maintain NAT bindings.
rect rgba(255,248,220,0.5)
Note over A,B: When IP or port changes the initiator sends new initiation from updated tuple. Responder validates and rotates keys seamlessly.
end
flowchart TB
  %% ==========================
  %% DATA PLANE PACKET FLOW AND ROUTING (IN-DEPTH)
  %% ==========================

  subgraph A["Peer A"]
    A_APP["App generates packet\nDst 10.0.0.2:443"]:::app
    A_L3["Kernel Routing Table"]:::box
    A_WG["wg0"]:::wg
    A_ENC["Encrypt inner IP packet -> ChaCha20 Poly1305"]:::box
    A_OUT["Outer UDP packet\nSrc A_public_ip:51820\nDst B_public_ip:51820"]:::net
  end

  subgraph WAN["Internet / NATs / Firewalls"]
    NATs["NAT traversal aided by:\n- UDP 51820\n- optional PersistentKeepalive 25s\n- stateful pinholes"]:::note
  end

  subgraph B["Peer B"]
    B_IN["Receive outer UDP"]:::net
    B_DEC["Decrypt to recover inner IP packet"]:::box
    B_WG["wg0"]:::wg
    B_L3["Kernel Routing Table"]:::box
    B_APP["Service consumes packet\n10.0.0.2:443"]:::app
  end

  A_APP --> A_L3 -->|prefix match 10.0.0.0/24 in AllowedIPs of B| A_WG --> A_ENC --> A_OUT --> NATs --> B_IN --> B_DEC --> B_WG --> B_L3 --> B_APP

  classDef wg fill:#e8fff5,stroke:#10b981,color:#111;
  classDef net fill:#fff7ed,stroke:#f59e0b,color:#111;
  classDef app fill:#f4f4f5,stroke:#9ca3af,color:#111;
  classDef box fill:#eef2ff,stroke:#6366f1,color:#111;
  classDef note fill:#ffffff,stroke:#d1d5db,color:#111;
flowchart LR
  %% ==========================
  %% CONFIG RELATIONSHIPS (wg-quick / wg)
  %% ==========================

  subgraph Local["Peer Local Config"]
    IFACE["Interface"]:::sec
    IF_Pri["PrivateKey = A_priv"]:::box
    IF_Addr["Address = 10.0.0.1/24"]:::box
    IF_Port["ListenPort = 51820"]:::box
    IF_PostUp["PostUp / PostDown actions such as iptables, routes, sysctl"]:::box
  end

  subgraph PeerBConf["Peer Entry for B"]
    PB_Pub["PublicKey = B_pub"]:::box
    PB_Endp["Endpoint = B_public_ip:51820"]:::box
    PB_Allowed["AllowedIPs = 10.0.0.2/32 and 10.10.0.0/16"]:::box
    PB_PSK["Optional PresharedKey"]:::box
    PB_Keep["PersistentKeepalive = 25"]:::box
  end

  IFACE --- IF_Pri & IF_Addr & IF_Port & IF_PostUp
  IFACE --- PeerBConf
  PeerBConf --- PB_Pub & PB_Endp & PB_Allowed & PB_PSK & PB_Keep

  classDef sec fill:#fef3c7,stroke:#f59e0b,color:#111;
  classDef box fill:#eef2ff,stroke:#6366f1,color:#111;
stateDiagram-v2
  %% ==========================
  %% KEY/TIMER LIFECYCLE (SIMPLIFIED)
  %% ==========================
  [*] --> NoSession: No valid session keys
  NoSession --> Initiating: Send Handshake Initiation
  Initiating --> Established: Receive valid Handshake Response\n(Derive session keys)
  Established --> Rekeying: Timer/Counter threshold reached\n(or peer IP/port changed)
  Rekeying --> Established: New keys installed (make-before-break)
  Established --> Idle: No user data for a while
  Idle --> Rekeying: Rekey to refresh keys on inactivity
  Idle --> Keepalive: Send PersistentKeepalive (if set) to maintain NAT
  Keepalive --> Established: Peer responds / pinhole intact
  Established --> [*]: Interface down / peer removed / keys cleared
flowchart TB
  %% ==========================
  %% SUBNET ROUTER / GATEWAY PEER (HUB-AND-SPOKE EXAMPLE)
  %% ==========================
  subgraph Spoke1["Spoke Peer A"]
    A_IF[[wg0]]:::wg
    A_Allowed[AllowedIPs includes 10.20.0.0/16 via Hub]:::box
  end

  subgraph Hub["Hub Peer (also a Subnet Router)"]
    H_IF[[wg0]]:::wg
    H_Forward[IP Forwarding enabled]:::box
    H_Routes[Routes to on-prem LANs:<br/>10.20.0.0/16, 10.21.0.0/16]:::box
    LAN1[(On-Prem LAN 10.20/16)]:::net
    LAN2[(On-Prem LAN 10.21/16)]:::net
  end

  subgraph Spoke2["Spoke Peer B"]
    B_IF[[wg0]]:::wg
    B_Allowed[AllowedIPs includes 10.20.0.0/16 via Hub]:::box
  end

  A_IF -- encrypted UDP --> H_IF
  B_IF -- encrypted UDP --> H_IF
  H_IF --> H_Forward --> H_Routes --> LAN1
  H_Routes --> LAN2

  classDef wg fill:#e8fff5,stroke:#10b981,color:#111;
  classDef net fill:#fff7ed,stroke:#f59e0b,color:#111;
  classDef box fill:#eef2ff,stroke:#6366f1,color:#111;

Leave a Comment