linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Simulator
  • Knowledge base
  • Interview prep
Index
Categories
All entries
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
home/linux/kb/Protocols/wireguard

kb/protocols ── Protocols ── intermediate

WireGuard: Modern UDP VPN

WireGuard is a UDP VPN built into the Linux kernel. A Curve25519 key pair, peers with AllowedIPs (both ACL and routing table). About 4000 lines of code versus millions in OpenVPN/IPsec. Flat config, no TLS, no certificates.

view as markdownaka: wg, wireguard-vpn, wg-quick

Why WireGuard

WireGuard appeared in 2016 and landed in the mainline kernel with 5.6 (2020). The goal was a VPN without the hyper-complexity of [[ipsec-ike|IPsec]] and without the overhead of [[openvpn|OpenVPN]]. What came out:

  • One packet type (UDP), one cipher suite (ChaCha20-Poly1305 + Curve25519), no negotiation. If future primitives become obsolete, WireGuard 2 will ship.
  • Stateless from the admin's perspective: a key pair on each side, the peer's public key is the only identifier.
  • AllowedIPs acts as both an ACL (what a peer may send) and a routing table (where to forward packets destined for that peer).
  • Roaming: a peer can change its IP or port without reconnecting, because authentication is by key, not by 5-tuple.

Basic Config

Server side /etc/wireguard/wg0.conf:

ini
[Interface]
PrivateKey = <server-private-key>
Address = 10.10.0.1/24
ListenPort = 51820
PostUp   = iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADE
[Peer]
PublicKey  = <client-public-key>
AllowedIPs = 10.10.0.2/32

Client wg0.conf:

ini
[Interface]
PrivateKey = <client-private-key>
Address = 10.10.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey  = <server-public-key>
Endpoint   = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0           # full-tunnel; for split-tunnel use specific subnets
PersistentKeepalive = 25                # behind NAT, keeps the conntrack entry alive

Bring it up:

bash
wg-quick up wg0
systemctl enable wg-quick@wg0          # start on boot

Key Generation

bash
umask 077
wg genkey | tee privatekey | wg pubkey > publickey

The private key never leaves the machine. The config stores it in plain text, so keep /etc/wireguard/ at mode 600. Always generate the private key on the peer itself, never on the server.

PSK (optional, for quantum resistance):

bash
wg genpsk > preshared.key

Add to [Peer]: PresharedKey = <psk>.

AllowedIPs: the Most Important Field

AllowedIPs = 10.10.0.2/32                  # single host
AllowedIPs = 10.10.0.0/24, 192.168.5.0/24  # multiple subnets
AllowedIPs = 0.0.0.0/0, ::/0               # everything - full-tunnel
  • Incoming packet from a peer: the source address must fall inside AllowedIPs, otherwise the packet is dropped. This is the trust-boundary check.
  • Outgoing packet: if the destination matches a peer's AllowedIPs, wg0 forwards it to that peer. Equivalent to a route lookup.

The same subnet 10.10.0.0/24 cannot appear in the AllowedIPs of two peers at once; that creates a routing conflict.

Topologies: Hub-and-Spoke vs Full Mesh

At L3, WireGuard has no "server" or "client" concept. Both sides are equal peers. Topology is determined by who has whom in [Peer]:

  • Hub: one peer holds all peer blocks; each client gets a narrow AllowedIPs. A simple model, the classic VPN server.
  • Mesh: every node knows every other node. Harder to provision, but traffic flows directly without a hub hop.
  • Tailscale/Headscale automate mesh provisioning on top of WireGuard.

NAT and Keepalive

Behind NAT, a conntrack entry dies after 30-180 seconds with no traffic. WireGuard handles this: PersistentKeepalive = 25 sends an empty keepalive every 25 seconds to keep the NAT hole open.

A hub behind a static IP does not need PersistentKeepalive. A client behind CGNAT (mobile carrier) does.

Diagnostics

bash
wg show                                  # all interfaces and peers
wg show wg0                              # details for one interface
wg show wg0 dump                         # machine-readable output
wg show wg0 latest-handshakes            # when the last handshake occurred
wg show wg0 transfer                     # rx/tx bytes per peer

The key health indicator is latest handshake: it should update roughly every 2 minutes. If it shows "30 minutes ago", the peer is unreachable.

WireGuard vs OpenVPN vs IPsec

FeatureWireGuardopenvpnipsec-ike
Code base~4 KLOC~600 KLOCmillions (strongSwan)
Where it runsin kerneluserspacekernel + IKE userspace
Configflat ininested + certsswanctl.conf + certificates
CipherChaCha20-Poly1305 (fixed)TLS suiteIKEv2-negotiated
UDPyesyes or TCPyes (ESP)
Roamingnativenomobile-extension
Performancenoticeably fasterslower (userspace)comparable to WG
Production maturity2020+2002+1995+
Certificates / PKInoyesyes

When to skip WireGuard:

  • You need PKI (corporate CRL/OCSP): use IPsec or OpenVPN.
  • You need TCP fallback (port 443 as camouflage): use OpenVPN.
  • You need per-user authentication with RADIUS/LDAP: use OpenVPN.
  • You need interop with Cisco/Juniper: use IPsec.

Troubleshooting

  • No such device wg0: the module is not loaded. On kernel 5.6+ it is built in; on older kernels install wireguard-dkms and run modprobe wireguard.
  • Handshake never happens: UDP 51820 is blocked somewhere. Check with nc -u -z -v server 51820, review the host firewall and NAT.
  • Handshake completes but traffic does not flow: ip route has no route to the VPN subnet; or ip-forwarding is disabled on the server; or MASQUERADE is missing.
  • Oversized packets are dropped (MTU): WireGuard adds 60 bytes of overhead (UDP
    • headers). Set MTU = 1420 explicitly when the path MTU is 1500.
  • Address already in use on wg-quick up: the IP is already assigned to another interface (an old wg0 is still up). Run wg-quick down wg0 first.
  • AllowedIPs overlap across two peers: the last entry wins and the others become unreachable. Do not overlap subnets.
  • PersistentKeepalive too frequent: wastes battery on mobile clients. 25 seconds is a reasonable compromise with typical NAT timeouts (60 s).

§ команды

bash
wg genkey | tee privatekey | wg pubkey > publickey

Generate a key pair in one line: private key and public key

bash
wg-quick up wg0

Bring up the interface using /etc/wireguard/wg0.conf

bash
wg show wg0 latest-handshakes

Show the timestamp of the last handshake with each peer to verify connectivity

bash
wg show wg0 transfer

Show rx/tx byte counts per peer as an activity indicator

bash
wg syncconf wg0 <(wg-quick strip wg0)

Apply config changes without restarting the interface

bash
ip -d link show wg0

Show interface parameters: MTU, statistics, type

bash
tcpdump -i any -nn 'udp port 51820' -c 5

Check whether WireGuard packets are visible on the wire; the first thing to check when there is no handshake

§ см. также

  • openvpnOpenVPN: TLS-based VPNOpenVPN is a userspace TLS VPN built on X.509 certificates. Modes: tun (L3, default) or tap (L2). Supports UDP/TCP, push routes, per-user authentication, and TCP-443 as HTTPS camouflage. Heavier than [[wireguard|WG]].
  • ipsec-ikeIPsec and IKEv2: the enterprise VPN standardIPsec is the L3 VPN standard. ESP encapsulates and encrypts, IKEv2 exchanges keys. Tunnel mode adds a new IP header for site-to-site; transport mode stays host-to-host. On Linux this is strongSwan.
  • udp-basicsUDP: User Datagram ProtocolUDP delivers datagrams without establishing a connection, without retransmits, and without ordering guarantees. Header is 8 bytes. Use it for DNS, DHCP, QUIC, VoIP, and any case where latency matters more than reliability.
  • natNAT: Network Address TranslationNAT rewrites the src or dst address of a packet at a router. Masquerade is the common case: the src IP is replaced with the router's outbound address so hosts on a private network can reach the public internet.
  • cmd-nftnft: modern firewall (nftables)`nft` is the single CLI for modern netfilter. Replaces iptables/ip6tables/ arptables/ebtables. Structure: tables, chains, rules.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies