Skip to main content
RunBook Academy

VyOSXLI · WireGuardWireGuard

WireGuard troubleshooting — handshake never happens, allowed-ips mismatch, MTU issues

Advanced⏱ ~22 minshow interfaces wireguard wg0 summaryshow interfaces wireguard wg0 public-keywg showshow firewallshow ip routeconfigurecommitrollbacktcpdumppingtraceroute

What you'll learn

  • Diagnose WireGuard handshake failures (unreachable peer, wrong key, blocked port)
  • Diagnose allowed-ips mismatches, which drop packets silently on the far side
  • Diagnose MTU and MSS problems over a WireGuard tunnel
  • Apply a systematic WireGuard diagnostic flow from symptom to root cause

Prerequisites

Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19

Not yet marked complete on this device.

WireGuard failures fall into a small number of categories: no handshake at all, a handshake that succeeds while no useful traffic passes, MTU problems that let small packets through and stop large ones, and NAT bindings that expire under an idle tunnel. What makes them awkward is not their number but their silence — WireGuard never answers a packet it cannot authenticate and never tears a session down, so almost every failure presents as “nothing happens”.

This lesson is the diagnostic reference. It covers the four failure modes, the evidence that separates them, and the operational commands that produce it.

The diagnostic flow

  1. Symptom — what the operator reports: tunnel down, tunnel up but nothing works, large transfers stall.
  2. show interfaces wireguard wg0 summary on both routers — the peer, its latest handshake line if it has one, and its transfer counters.
  3. The counters decide the direction of the investigation. Nothing in either direction is a handshake problem. Bytes out, nothing in, puts the fault on the far router.
  4. tcpdump on the WAN — is the encrypted UDP leaving, and is it arriving?
  5. tcpdump on wg0 — do the decrypted inner packets appear, and with what addresses?
  6. Routing and firewallshow ip route, show firewall ipv4 input filter, show firewall ipv4 forward filter.
# The minimal WireGuard diagnostic set

# 1. Peer state on the local router. On 1.5 the `summary` subcommand
#    is `sudo wg show wg0` and prints wg's own output verbatim.
vyos@R1:~$ show interfaces wireguard wg0 summary

# 2. Peer state on the remote router
vyos@R2:~$ show interfaces wireguard wg0 summary

# 3. What the kernel holds, narrowly
vyos@R1:~$ show interfaces wireguard wg0 peers
vyos@R1:~$ show interfaces wireguard wg0 endpoints
vyos@R1:~$ show interfaces wireguard wg0 allowed-ips

# 4. Encrypted traffic on the WAN
vyos@R1:~$ sudo tcpdump -ni eth0 'udp port 51820' -c 10 -vv

# 5. Routing for the destination that is failing
vyos@R1:~$ show ip route 192.168.20.0/24

# 6. Firewall counters, underlay then payload
vyos@R1:~$ show firewall ipv4 input filter
vyos@R1:~$ show firewall ipv4 forward filter

# 7. Reachability inside the tunnel
vyos@R1:~$ ping 10.10.10.2 count 3

Failure mode 1 — no handshake

Symptom: neither router prints a latest handshake line for the peer at all, and neither prints a transfer line either.

That wording is deliberate, and it is the single most useful thing in this lesson. There is no “never” to look for. wg show prints latest handshake only when the peer’s stored handshake timestamp is non-zero, and prints transfer only once a byte has moved in one direction or the other. A peer that has never completed a handshake therefore prints its peer line, its endpoint if one is configured, its allowed ips — and then simply stops:

vyos@R1:~$ show interfaces wireguard wg0 summary
interface: wg0
  public key: rSuuvkAuSoyqvogoA5OJjYBbJKKaYS13pWs4K69PTOE=
  private key: (hidden)
  listening port: 51820

peer: cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c=
  endpoint: 198.51.100.20:51820
  allowed ips: 10.10.10.0/30, 192.168.20.0/24

The block ends after allowed ips. The two lines you were looking for are the two lines that are not there. Compare it against a healthy peer, which carries two more:

peer: cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c=
  endpoint: 198.51.100.20:51820
  allowed ips: 10.10.10.0/30, 192.168.20.0/24
  latest handshake: 1 minute, 3 seconds ago
  transfer: 1.15 MiB received, 942.53 KiB sent

Reading an absence is harder than reading a status word, so make the comparison explicit rather than scanning for something alarming. Note also that the peer is identified by its base64 public key and not by the name you gave it in the configuration — on 1.5 this output is wg’s own, and wg has never seen your peer names. show configuration commands | match wireguard is what turns a key back into a site.

Candidate causes, in the order worth checking:

  1. UDP blocked on the underlay. The far end’s port is not reachable — a firewall rule here, a firewall rule there, or something between.
  2. Wrong peer address or port. The initiation is being sent somewhere that is not listening.
  3. Wrong public key. One end holds a key that does not belong to the other; the initiation is discarded without a reply.
  4. Preshared key on one side only. A peer with none configured behaves as though it had one of all zeroes, so the two ends derive different keys and nothing completes.
  5. No route to the far end’s outer address. The underlay routing is wrong, so the packets never leave.
  6. Neither side initiates. Both ends were configured without a peer address, each waiting for the other to start.
# Diagnostic for no handshake

# 1. Both ends, first. Look for the ABSENCE of `latest handshake`
#    and `transfer` under each peer, not for a "never" string.
vyos@R1:~$ show interfaces wireguard wg0 summary
vyos@R2:~$ show interfaces wireguard wg0 summary

# 2. Is the encrypted traffic leaving here, and arriving there?
vyos@R1:~$ sudo tcpdump -ni eth0 'udp port 51820' -c 10
vyos@R2:~$ sudo tcpdump -ni eth0 'udp port 51820' -c 10

# 3. Underlay firewall counters on the receiving side
vyos@R2:~$ show firewall ipv4 input filter

# 4. Do the keys actually match?
vyos@R1:~$ show interfaces wireguard wg0 public-key
vyos@R2:~$ show interfaces wireguard wg0 peers
# The first value must appear in the second list, and vice versa.

# 5. Where does this router think the peer is?
vyos@R1:~$ show interfaces wireguard wg0 endpoints

The capture pair is what splits the cases apart. Packets leaving R1 and arriving at R2, with no reply, means R2 received something it would not answer: a key problem or a preshared-key problem. Packets leaving R1 and never arriving at R2 means the underlay: a firewall, a NAT, or a wrong address.

flowchart TD
  A["No latest handshake<br/>line under the peer"] --> B{"Encrypted UDP<br/>leaves R1?"}
  B -- "no" --> C["Underlay routing, or<br/>peer address unset"]
  B -- "yes" --> D{"Arrives at R2?"}
  D -- "no" --> E["Blocked between:<br/>firewall or NAT"]
  D -- "yes" --> F{"R2 replies?"}
  F -- "no" --> G["R2 will not authenticate it:<br/>public key or preshared key"]
  F -- "yes" --> H["Reply is lost on the<br/>way back: check R1 input filter"]

Failure mode 2 — handshake succeeds, no traffic

Symptom: the peer does print a latest handshake line and it is recent, and pings or sessions through the tunnel fail anyway.

The counters narrow this down before anything else does:

  • Bytes out rising, bytes in flat. Your packets are being sent and the far end is not answering. The fault is on the far router — its allowed-ips do not cover your source prefix, its forward rules drop the decrypted traffic, or it has no route back.
  • Both counters rising, application still broken. The tunnel is carrying traffic; look at routing beyond the tunnel, at NAT, or at the firewall on the way to the host.
  • Nothing moving in either direction. Nothing is being sent into the tunnel at all: the route to the destination does not point at wg0, or the destination is not inside any peer’s allowed-ips.
# Diagnostic for a handshake with no traffic

# 1. Confirm the handshake and read the counters
vyos@R1:~$ show interfaces wireguard wg0 summary

# 2. Does the destination route into the tunnel at all?
vyos@R1:~$ show ip route 192.168.20.0/24

# 3. Does a peer claim that destination?
vyos@R1:~$ show interfaces wireguard wg0 allowed-ips

# 4. The decrypted view, on both ends
vyos@R1:~$ sudo tcpdump -ni wg0 -c 4
vyos@R2:~$ sudo tcpdump -ni wg0 -c 4

# 5. The firewall that governs the decrypted traffic
vyos@R2:~$ show firewall ipv4 forward filter

A packet visible on R1’s wg0 and absent on R2’s wg0 was discarded between decryption and the interface, and there is exactly one thing that does that: the source address was not inside the allowed-ips of the peer it arrived from.

flowchart TD
  A["Handshake recent, no traffic"] --> B{"Bytes out<br/>rising?"}
  B -- "no" --> C{"Route points<br/>at wg0?"}
  C -- "no" --> D["Fix the route"]
  C -- "yes" --> E["Destination not in any<br/>peer allowed-ips here"]
  B -- "yes" --> F{"Bytes in<br/>rising?"}
  F -- "no" --> G["Far end drops it:<br/>allowed-ips, forward rules,<br/>or no route back"]
  F -- "yes" --> H["Tunnel carries traffic:<br/>look beyond it"]

Failure mode 3 — MTU and MSS

Symptom: ping works, interactive SSH works, and anything that moves real data stalls. A web page returns its headers and hangs; a large scp starts and freezes.

The arithmetic first. WireGuard adds 60 bytes to every packet over an IPv4 underlay — 20 IP, 8 UDP, 16 of message header and 16 of authentication tag — and 80 bytes over IPv6, where the outer header is 40. The interface default MTU is 1420, which is 1500 minus 80: the value that fits a standard underlay even when the outer packet is IPv6.

# This is the one place in this lesson where the bare form is the
# right command: MTU is an interface property, not a peer property.
vyos@R1:~$ show interfaces wireguard wg0
# mtu 1420 unless you set it otherwise

So the default is correct for a 1500-byte path, and MTU trouble comes from two other places:

  1. The underlay is smaller than 1500. A path with a 1428-byte MTU leaves room for a 1368-byte tunnel MTU over IPv4. Left at 1420, the encrypted packets are 1480 bytes and are dropped or fragmented by the constrained hop.
  2. TCP never learns the tunnel MTU. Endpoints behind the routers negotiate an MSS from their local MTU, usually 1460. Their large segments arrive at the router, do not fit the tunnel, and the ICMP that would tell them so is filtered somewhere. This is a black hole, and it is the common case.

Measure rather than guess, using the operational ping with its own syntax:

# Inside the tunnel: the largest ICMP payload that fits is MTU minus 28
vyos@R1:~$ ping 192.168.20.10 size 1392 do-not-fragment count 3
vyos@R1:~$ ping 192.168.20.10 size 1400 do-not-fragment count 3

# Bisect between a size that answers and one that does not until the
# ceiling is pinned, then set the interface MTU to that payload + 28
# and clamp beneath it.

The fix is both nodes together — the MTU that reflects the measured path, and a clamp so TCP stops trying to exceed it:

set interfaces wireguard wg0 mtu '1420'
set interfaces wireguard wg0 ip adjust-mss 'clamp-mss-to-pmtu'
set interfaces wireguard wg0 ipv6 adjust-mss 'clamp-mss-to-pmtu'

clamp-mss-to-pmtu derives the value from the interface MTU, which is exactly why setting the MTU deliberately matters. Where the constraint is not this interface’s own MTU — an underlay that is itself a tunnel — use the explicit number instead: MTU minus 40 for IPv4, minus 60 for IPv6, so 1380 and 1360 at an MTU of 1420.

Failure mode 4 — NAT binding expiry

Symptom: a remote-access tunnel that works, idles, and stops. Traffic initiated by the NAT’d side revives it immediately; traffic initiated by the public side does nothing.

# On the public side
vyos@hub:~$ show interfaces wireguard wg0 summary
# The peer's `latest handshake` line is still printed, but it ages past
# a couple of minutes and the received half of its `transfer` line stops
# moving, while the client behind NAT believes all is well.

Cause: the consumer NAT device drops its UDP binding after an idle period, commonly 30 to 120 seconds, so the hub’s packets have nothing to traverse. WireGuard does not notice, because it has no session state to lose.

Fix, on the end behind NAT:

set interfaces wireguard wg0 peer HUB persistent-keepalive '25'
commit
save

The asymmetry in the symptom is the giveaway: a failure that only affects traffic initiated from one side is a path problem, not a crypto problem.

A complete diagnostic flow

# Symptom: the WireGuard tunnel is not working

# Step 1 - peer state on both ends
vyos@R1:~$ show interfaces wireguard wg0 summary
vyos@R2:~$ show interfaces wireguard wg0 summary

# Step 2 - configuration on both ends, redacted for the ticket
vyos@R1:~$ show configuration commands | strip-private
vyos@R2:~$ show configuration commands | strip-private
# Compare peer names, public keys, address and port, allowed-ips

# Step 3 - the key check that needs both routers
vyos@R1:~$ show interfaces wireguard wg0 public-key
vyos@R2:~$ show interfaces wireguard wg0 peers

# Step 4 - underlay firewall counters
vyos@R1:~$ show firewall ipv4 input filter
vyos@R2:~$ show firewall ipv4 input filter

# Step 5 - routing for the failing destination
vyos@R1:~$ show ip route 192.168.20.0/24

# Step 6 - WAN capture, encrypted
vyos@R1:~$ sudo tcpdump -ni eth0 'udp port 51820' -c 10 -vv

# Step 7 - tunnel capture, decrypted, on both ends
vyos@R1:~$ sudo tcpdump -ni wg0 -c 10
vyos@R2:~$ sudo tcpdump -ni wg0 -c 10

# Step 8 - the MTU ceiling
vyos@R1:~$ ping 192.168.20.10 size 1392 do-not-fragment count 3

Production failure modes

The port is already in use

The interface will not commit, with an error saying the UDP port is busy or unavailable and cannot be used for the interface. Another WireGuard interface, or another service, already holds it.

Diagnostic: show configuration commands | match wireguard lists the ports the other interfaces claim. Every WireGuard interface on the router needs its own.

Fix: give the new interface a different port. This is routine on a hub that runs one interface per spoke.

Asymmetric routing through stateful firewalls

The encrypted UDP leaves through one WAN and the reply arrives on the other, where a stateful firewall has no matching entry and drops it. Common on a multi-WAN router where the tunnel was built without pinning its outbound path.

Diagnostic: the WAN capture shows initiations leaving and replies arriving on a different interface, or not arriving at all. Part XXXIX covers the multi-WAN source-address problem in full.

Fix: pin the tunnel’s underlay path, or make both firewalls accept the return traffic.

Key rotated on one side only

A key pair was regenerated on one router and the far end still holds the old public key. The handshake stops immediately and completely.

Diagnostic: after the change window the peer’s latest handshake line stops advancing and, once the interface is bounced, stops being printed at all — while the WAN capture shows initiations arriving at the far end and drawing no reply. Confirm it by comparing show interfaces wireguard wg0 public-key here against the show interfaces wireguard wg0 peers list there; the key printed here must appear in that list.

Fix: complete the rotation — see Part XLI-02. The two commits belong in the same maintenance window, in a planned order.

Peer removed, or disabled

The peer was deleted, or disable was set on it, in an unrelated change. The tunnel stops and the configuration looks superficially plausible because disable is easy to read past.

Diagnostic: show interfaces wireguard wg0 peers shows fewer keys than the site expects; show configuration commands | match wireguard shows the disable node.

Fix: restore the peer, or remove the disable node, and commit.

MTU black hole under an unchanged configuration

Nothing changed on the routers, and large packets stopped passing. The underlay path changed — a failover onto a backup circuit with a smaller MTU, or an upstream tunnel inserted somewhere.

Diagnostic: ping ... do-not-fragment finds a ceiling lower than yesterday. Small packets are unaffected, which is why monitoring that pings with 64 bytes reports everything as healthy.

Fix: lower the tunnel MTU to the measured value and let clamp-mss-to-pmtu follow it. Consider monitoring that probes at a realistic size.

Rollback

# Take a restore point before changing anything
configure
save /config/pre-change-wg-diag-TICKET.conf

# ... make one change, read the diff, and commit it under a timer ...
compare
commit-confirm 5
confirm
save

# If the change made things worse, go back one revision
rollback 1
commit

# Or reload the file
load /config/pre-change-wg-diag-TICKET.conf
commit
save

Diagnostic output is not saved with a pipe — operational mode has no | save. Copy what you need into the ticket, or redirect from the shell. What matters more is the discipline of one change at a time under commit-confirm: a tunnel is the path your own session may be using, and a WireGuard change that fails takes the evidence with it.

Production discipline

Cross-course references

  • Part XLI-02 (XLI-VyOS-WireGuard / keys) covers key generation and the rotation that breaks a tunnel when only one side is done.
  • Part XLI-03 (XLI-VyOS-WireGuard / peers) covers the named-peer tree and allowed-ips as a design.
  • Part LII-04 (LII-VyOS-Troubleshoot / subsystem by subsystem) covers the wider diagnostic methodology.
  • Part LI-04 (LI-VyOS-MTU / MSS clamping) covers ip adjust-mss and how to prove a clamp on the wire.
  • Part L-04 (L-VyOS-Performance / crypto load) covers the throughput ceiling that is CPU, not MTU.

Quiz

Knowledge check · 4 questions

  1. Q1. WireGuard is configured and `show interfaces wireguard wg0 summary` shows a recent `latest handshake`. A ping from the router through the tunnel fails immediately with `Destination Host Unreachable` raised locally. What is the most likely cause?

  2. Q2. Capturing on the WAN interface with `tcpdump 'udp port 51820'` shows the encrypted WireGuard packets but not the inner payload; the inner payload is only visible by capturing on `wg0` (the tunnel interface).

  3. Q3. After committing a new WireGuard tunnel between two sites, neither router prints a `latest handshake` line for its peer. How do you tell a blocked port from a wrong key without changing anything?

    R1 and R2 are configured for WireGuard on public addresses 198.51.100.10 and 198.51.100.20, both listening on UDP 51820. Both have a private key, a named peer carrying a public key, a peer address and port, and allowed-ips. On both routers `show interfaces wireguard wg0 summary` prints the peer with its endpoint and allowed ips and then stops — no `latest handshake` line and no `transfer` line — and neither router logs anything, because WireGuard does not log handshake failures. The operator suspects the firewall but has no evidence either way.

  4. Q4. A WireGuard tunnel is up and ping works, but file transfers through it stall. The interface MTU is at its default. What is the diagnostic and the fix?

    R1 and R2 have a working tunnel: recent handshake, counters moving both ways, small pings fine, SSH sessions connect. A file transfer between hosts behind the routers starts and freezes, and a web application returns headers and then hangs. The WAN path failed over to a backup circuit last night whose MTU is 1428, and the WireGuard interfaces are still at the 1420 default. The hosts behind the routers negotiate an MSS of 1460 from their own 1500-byte LANs.

Passing score: 75%. Answers are checked in this browser.