WireGuard is a UDP-based Layer 3 encrypted tunnel that binds peer public keys to allowed IP prefixes. It simplifies algorithm selection and session management interfaces, but internally still maintains handshake states, key generations, counters, replay windows, and timers; it cannot be described as "stateless with only encryption."
It provides a secure data plane but does not include a complete user login, device approval, address allocation, DNS, and NAT traversal control platform. These features can be supplemented by configuration tools or other systems; having a pair of public keys does not mean two devices already know how to reach each other.
Question
Key Understanding
Are AllowedIPs routing or access control?
Sending selects peers by destination; receiving verifies by source.
What is in the 148 B handshake?
Two independent encrypted fields, not a single merged block.
Does a 120-second timer trigger periodic re-handshakes even with no traffic?
It depends on key age, role, and send/receive events.
Does keepalive penetrate all NATs?
Maintaining existing state does not equal automatically establishing reachable paths.
Why is business traffic failing despite a recent latest handshake?
It could still be inner routing, AllowedIPs, or firewall issues.
Is MTU always 1420?
It must be calculated based on the outer path and encapsulation budget.
Public Key Identity, AllowedIPs, and Outer Endpoint
The peer public key represents the configured cryptographic identity; the endpoint is the current IP and port for sending UDP; AllowedIPs associate inner IP ranges with that identity. Outer addresses can change, while the relationship between inner addresses and public keys is determined by local policy.
The operating system first hands the inner packet to the WireGuard interface based on routing; the interface then performs longest prefix matching in AllowedIPs based on the destination address to select the peer. On the receiving side, authentication and decryption occur first, followed by verifying whether the inner source address belongs to that peer. Successful decryption alone does not mean that peer can impersonate other inner addresses. Cryptokey Routing
For example, if A configures 10.0.0.0/24 and B configures 10.0.0.7/32: packets destined for 10.0.0.7 select B, while those for 10.0.0.8 select A; if A sends an inner packet with source address 10.0.0.7, it will be rejected in this configuration due to mismatched specific ownership. AllowedIPs is not a simple independent set inclusion test for each peer.
fromipaddressimportip_address,ip_networkroutes=[(ip_network('10.0.0.0/24'),'A'),(ip_network('10.0.0.7/32'),'B')]defowner(address):ip=ip_address(address)candidates=[(net.prefixlen,peer)fornet,peerinroutesifipinnet]returnmax(candidates)[1]ifcandidateselseNoneassertowner('10.0.0.7')=='B'assertowner('10.0.0.8')=='A'assertowner('10.0.1.1')isNoneassertowner('10.0.0.7')!='A'# A cannot impersonate B's inner source address under this configuration
print('Longest prefix selection and receive source ownership model passed')
The wg command configures WireGuard parameters for the interface; addresses and system routes are handled by other configuration layers. wg-quick assists in setting addresses and routes based on configuration, but this does not mean the protocol automatically broadcasts all AllowedIPs to the peer. 0.0.0.0/0 does not cover IPv6's ::/0. wg, wg-quick
Why can A not send .7 despite its /24?
Gold .7 belongs to B via /32. Keep inbound peer A and alternate .7/.8: acceptance changes at that point because ownership is one longest-prefix lookup, not independent per-peer containment.
Preparing the visual
Why can A not send .7 despite its /24?
More-specific ownership overrides a broader prefix. An authenticated A packet with a B-owned source is rejected despite A’s /24 containing it.
Noise Handshake: Fixed Algorithm Combination, Not Just a One-Time Temporary DH
WireGuard uses the Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s construction, combining static and temporary keys to establish authentication and directional keys; an optional PSK can be mixed in as additional material. It does not negotiate a long list of cipher suites for each connection, but fixed algorithms do not mean there are no downgrade or configuration risks outside the protocol.
The initiator already knows the responder's static public key, and the responder identifies the configured peer based on the decrypted initiator's public key. Both parties only need to exchange public identity materials; one cannot write "both parties share pre-shared keys" as meaning they know each other's static private keys.
The handshake involves multiple static/temporary DH operations and transcript mixing; it cannot be simplified into a single temporary DH result with a public key hash added arbitrarily. The correct DH equation is DH(a_private, b_public) = DH(b_private, a_public); putting public/public or private/private in does not constitute another equivalent calculation.
After the responder sends the second message, it must still receive a Transport message from the initiator using the new key to obtain key confirmation for that direction; the initiator can send an empty packet to complete this step if there is no queued data. WireGuard Protocol
encrypted_static and encrypted_timestamp each carry authentication tags and cannot be merged into a single AEAD field for "identity + timestamp." The type is composed of 1 B type and 3 B zero reserved bits, stored in little-endian 32-bit format in the kernel for easy checking; indices and Transport counters are also encoded in little-endian as specified.
Data Plane: IP Packets, Padding, Counters, and Authentication Tags
The Transport message header is 16 B: type/reserved 4, receiver index 4, counter 8; followed by 16 B of tag in the encrypted content, making an empty keepalive WireGuard message 32 B. The receiver index locates the corresponding key state, rather than carrying the full peer public key in plaintext.
Inner IP packets may be zero-padded to reduce fine-grained length leakage. The Linux send path usually aligns to 16 B, but padding is also subject to inner MTU and other boundary limits; one cannot always pad to the next multiple of 16 at the top of the MTU. The following calculation covers a single normal inner packet, excluding complex paths like GSO segmentation.
importstructassert4+4+32+48+28+16+16==148assert4+4+4+32+16+16+16==92assert4+4+24+32==64header=struct.pack('<IIQ',4,0x12345678,9)assertlen(header)==16andheader.hex()=='04000000785634120900000000000000'defouter_size(inner,mtu=1420,ip_header=40):assert0<=inner<=mtupadded=min(mtu,((inner+15)//16)*16)returnip_header+8+16+padded+16assertouter_size(0)==80assertouter_size(1000)==1088# Padded to 1008 B, outer IPv6
assertouter_size(1420)==1500# Padding limited by MTU
assertouter_size(1000,ip_header=20)==1068print('Total length for empty packet / 1000 B inner / 1420 B inner IPv6:',outer_size(0),outer_size(1000),outer_size(1420))
32 B is the lower limit for a WireGuard message; with outer IPv4 and UDP, it becomes 60 B, and with outer IPv6, 80 B, excluding the link layer. 1420 often comes from a budget of 1500−40−8−32, but actual paths, IPv6 extension headers, or other encapsulations will change the available value.
The counter participates in AEAD nonce construction, with state for each direction and key generation; the receiver uses a window to tolerate some reordering and rejects duplicates. It does not retransmit lost inner data packets like TCP; reliability is still determined by the inner protocol. WireGuard Whitepaper
Why accept reordering but reject repetition?
Deliver7 then3:3 is smaller but within the window and is accepted. Repeating3 is a duplicate. After15 advances the left edge to8,7 is too old. State advances only on authenticated packets.
Preparing the visual
Why accept reordering but reject repetition?
Below the highest counter does not necessarily mean replay: check window membership and receipt history. Old and duplicate packets are rejected without automatic retransmission.
Timers: Maintaining Keys by Event, Not Unconditional 120-Second Reconnection
The following checks against Linux 7.2 drivers/net/wireguard/messages.h, timers.c, send.c, and receive.c. Abbreviated units or old formulas in website overviews should not override the constants and trigger conditions of specific implementations.
Condition or Constant
Meaning in Local Implementation
REKEY_AFTER_TIME=120 s
Initiator send path triggers updates based on key age, etc.
REJECT_AFTER_TIME=180 s
Rejects usage after exceeding the corresponding key lifetime.
REKEY_TIMEOUT=5 s
Base retry interval when handshake is unresponsive, plus jitter.
KEEPALIVE_TIMEOUT=10 s
If no reply is sent after receiving data, schedules empty packets or other event behaviors.
REKEY_AFTER_MESSAGES=2^60
Message count triggers update threshold, not the only security limit.
REJECT_AFTER_TIME×3=540 s
Timer scale for cleaning up residual key material under corresponding silence conditions.
Send path, receive path, and handshake retries are different trigger points. The current receive path's last-minute update uses an age threshold of 180−10−5=165 seconds, along with initiator role conditions; one cannot simply copy 120−10−5, nor interpret it as a handshake being initiated if there is no traffic for 120 seconds.
Handshake retries advance by 5 seconds plus random jitter, with a maximum attempt limit; this is not 5, 10, 20 seconds exponential backoff. By default, without PersistentKeepalive, idle devices can remain silent, but protocol empty packets or retries triggered by prior traffic may still exist.
Old and new keys can coexist during transition to accommodate packet loss and reordering; switching does not mean immediately wiping all old state. Fixed message thresholds cannot be converted into a "won't change key for tens of thousands of years" usage guarantee, as time thresholds and other limits participate in processing earlier.
Cookie, Roaming, and NAT Boundaries
mac1 helps the server filter messages before expensive processing, but material derived from the responder's public identity does not equal having authenticated an authorized peer. When load conditions are met and a valid cookie is missing, a cookie challenge can be returned, requiring subsequent mac2 to provide corresponding proof; not every first handshake necessarily triggers a cookie.
mac2 is the MAC calculated using the cookie, not simply stuffing the raw cookie value into the mac2 field. Cookies help verify source reachability and reduce resource abuse, but cannot replace complete peer identity authentication. The mac2 of the response message also needs to be processed according to protocol conditions and cannot always be marked as zero.
Valid authenticated traffic can update the peer's outer endpoint, supporting roaming after address changes. However, if neither party has a reachable endpoint and mappings cannot be established, WireGuard itself has no automatic coordination discovery or universal relay service.
PersistentKeepalive is used to keep NAT or stateful firewall mappings active during idle periods; it is disabled by default, and 25 seconds is a common applicable example rather than a protocol-mandated value. It maintains available paths but does not guarantee interoperability for any NAT combination, nor can it indefinitely maintain already unreachable networks. WireGuard Quick Start
Routing Exit and Diagnostics
When using full tunnel routing, ensure that outer UDP traffic to the peer endpoint has a normal exit, avoiding sending it back into the same WireGuard interface. Policy marks and network namespaces can help separate the inner routing and the network environment to which the outer socket belongs. WireGuard Routing and Namespaces
The following selective commands are used to observe existing interfaces; they do not print private keys or PSKs, nor do they modify configurations:
wg show all endpointswg show all allowed-ipswg show all latest-handshakeswg show all transferip route showip -6 route show
System routing, AllowedIPs source verification, forwarding, and firewall.
Small packets pass, large packets hang
Inner MTU, outer path, and ICMP feedback.
Passive inbound fails after idle
NAT mapping lifetime and keepalive requirements.
Unidirectional communication after roaming
Outer endpoint update, return path, and filtering.
latest-handshakes is a timestamp record, not an application health probe; a stale timestamp during long idle periods does not automatically imply a fault. Transfer count growth also needs to be interpreted in conjunction with direction and actual inner application results.
Continue Reading
Tailscale Architecture: How coordination, identity, discovery, and relays supplement the data plane.
IPsec and IKEv2: Another security association and policy system.