Objective
By the end of this lab one WireGuard tunnel will carry traffic between two sites, and you will be able to state which of four separate things is true at any moment: the underlay reaches the far router, the handshake completed, packets are being encrypted and sent, and packets are being received and accepted. Those four fail independently, and WireGuard’s defining operational property is that it reports none of them by complaining. It reports them by staying silent.
That silence is the reason this lab spends more time on evidence than on configuration. The configuration is about fifteen lines per router. The skill is knowing which two numbers to read, and in which order, when it does not work.
Architecture
Two VyOS routers, each standing in for a site edge. Each carries a dummy interface holding the prefix its site owns, so neither needs a host behind it to have something worth routing.
SITE A SITE B
dum0 192.168.10.1/24 dum0 192.168.20.1/24
| |
+----+---------------------+ +-------------------+----+
| site-a | | site-b |
| eth1 198.51.100.10/24 +-----+ eth1 198.51.100.20/24 |
| wg0 10.10.10.1/30 | | wg0 10.10.10.2/30 |
+--------------------------+ +------------------------+
| vmbr95 — the "internet" segment, |
| 198.51.100.0/24, no uplink |
| |
+----+--------------------------------------------------------+
| vmbr0 — your lab management LAN |
+--------------------------------------------------------------+
Three address families doing three jobs, and keeping them apart is what
makes the failures legible. 198.51.100.0/24 is the underlay: the
network the encrypted UDP crosses, and the only one that exists before the
tunnel does. 10.10.10.0/30 is the tunnel transport, a point-to-point
link that exists only inside the encryption. 192.168.10.0/24 and
192.168.20.0/24 are the payload prefixes — the traffic the sites
actually care about.
vmbr95 is an isolated bridge with no physical uplink. vmbr0 carries
management only and nothing in this lab touches it, which matters in Task
5 where you install a default-deny input policy.
Requirements
- A hypervisor able to run two VMs with two NICs each — one on your management LAN and one on an isolated bridge. The commands are written for Proxmox VE; any KVM/libvirt host works.
- 1 GB RAM and 8 GB disk per router — 2 GB and 16 GB in total.
- The VyOS 1.5 LTS ISO uploaded to hypervisor storage. WireGuard is in the base image; there is nothing to install.
- Console access to both VMs, and this one is not optional. Task 5
sets
firewall ipv4 input filter default-action drop, which governs traffic addressed to the router itself — including your SSH session. The task adds a management permit before the default action for exactly that reason, and if you commit them in the wrong order you will lose the session and need the console to recover. - Roughly 100 minutes, of which the VM build is about 25.
Scenario
Two offices need a private path between them. There is no MPLS, no
existing IPsec, and no appetite for a device in the middle: both sites
have a VyOS router with a public address and that is the whole inventory.
Site A owns 192.168.10.0/24, Site B owns 192.168.20.0/24, and neither
prefix may appear on the public network.
Nothing is configured yet. You will build the starting state.
Tasks
Task 1: Build the starting state
One isolated bridge. On the Proxmox host, add a Linux bridge with no ports
— no ports is what makes it isolated. This is config text for
/etc/network/interfaces, not a command:
auto vmbr95
iface vmbr95 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
Apply with ifreload -a, or reboot the node if your host has no
ifupdown2. Then create the two VMs:
# Run on the Proxmox host.
# Substitute your own values before running. The ISO volume ID must match
# exactly what `pvesm list local` prints for your upload.
ISO=local:iso/vyos-1.5-lts-amd64.iso
STORE=local-lvm
for VMID in 280 281; do
qm create "$VMID" --memory 1024 --cores 1 \
--net0 "virtio,bridge=vmbr0,firewall=0" \
--net1 "virtio,bridge=vmbr95,firewall=0" \
--scsihw virtio-scsi-single --scsi0 "$STORE:8" \
--ide2 "$ISO,media=cdrom" --boot order=ide2 --ostype l26
done
qm set 280 --name site-a
qm set 281 --name site-b
qm start 280
qm start 281
firewall=0 on the NICs matters more than usual here. WireGuard is UDP on
port 51820 and nothing else; a host-side filter that drops it produces a
tunnel that never handshakes and logs nothing at either end, which is the
single hardest WireGuard symptom to attribute.
Install on each VM from the console — log in with the live-boot
credentials, run install image, accept the defaults — then move the boot
order back to disk and detach the ISO:
# Run on the Proxmox host, after `install image` completes on both VMs.
for VMID in 280 281; do
qm set "$VMID" --boot order=scsi0
qm set "$VMID" --delete ide2
done
Task 2: Baseline, and prove the underlay before you encrypt anything
On site-a:
configure
set system host-name site-a
set interfaces ethernet eth0 address dhcp
set interfaces ethernet eth0 description "management - not part of the lab"
set interfaces ethernet eth1 address 198.51.100.10/24
set interfaces ethernet eth1 description "underlay - the public side"
set interfaces dummy dum0 address 192.168.10.1/24
set interfaces dummy dum0 description "site A payload prefix"
set service ssh
commit
save
On site-b, the mirror image:
configure
set system host-name site-b
set interfaces ethernet eth0 address dhcp
set interfaces ethernet eth0 description "management - not part of the lab"
set interfaces ethernet eth1 address 198.51.100.20/24
set interfaces ethernet eth1 description "underlay - the public side"
set interfaces dummy dum0 address 192.168.20.1/24
set interfaces dummy dum0 description "site B payload prefix"
set service ssh
commit
save
Save a named baseline on both; Cleanup restores from it:
save /config/pre-lab.boot
Prove the underlay works before adding a tunnel to it:
$ ping 198.51.100.20 count 3PING 198.51.100.20 (198.51.100.20) 56(84) bytes of data.
64 bytes from 198.51.100.20: icmp_seq=1 ttl=64 time=0.482 ms
64 bytes from 198.51.100.20: icmp_seq=2 ttl=64 time=0.401 ms
64 bytes from 198.51.100.20: icmp_seq=3 ttl=64 time=0.398 ms
--- 198.51.100.20 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2039msIllustrative output
This is the one moment in the lab where ping between the routers proves
something. Once the tunnel exists, this ping will keep succeeding through
every failure you are about to create — the underlay and the tunnel are
independent, and confusing them is how an hour goes missing.
Task 3: Keys, generated in two places
Generate a key pair on each router, separately. Enter configuration
mode on site-a with configure, then run the generator from inside the
session — the install form only works there, because that is where a set
can be staged:
$ run generate pki wireguard key-pair install interface wg01 value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.
Corresponding public-key to use on peer system is: 'rSuuvkAuSoyqvogoA5OJjYBbJKKaYS13pWs4K69PTOE='Illustrative output
The private half is written straight into the configuration and never
passes through your clipboard, your scrollback or your ticket. Run the
same command outside configuration mode and it installs nothing; it prints
the set line for you to paste instead, which is a message rather than an
error.
Do the same on site-b. Record both public keys in your journal against
the router they belong to — this is the key inventory, and it is a
deliverable because WireGuard gives you nothing else. There is no
certificate, no subject name, no chain to walk: a peer’s identity is its
44 characters of base64, and wg will only ever show you the key.
Now the interfaces. On site-a, still inside the configuration session
that staged the private key:
set interfaces wireguard wg0 address 10.10.10.1/30
set interfaces wireguard wg0 description "site-to-site to site-b"
set interfaces wireguard wg0 port 51820
set interfaces wireguard wg0 peer SITE-B public-key 'cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c='
set interfaces wireguard wg0 peer SITE-B address 198.51.100.20
set interfaces wireguard wg0 peer SITE-B port 51820
set interfaces wireguard wg0 peer SITE-B allowed-ips 10.10.10.0/30
set interfaces wireguard wg0 peer SITE-B allowed-ips 192.168.20.0/24
set protocols static route 192.168.20.0/24 interface wg0
commit
save
On site-b, the mirror image — note that each router holds the other
router’s public key, which is the mistake worth being deliberate about:
set interfaces wireguard wg0 address 10.10.10.2/30
set interfaces wireguard wg0 description "site-to-site to site-a"
set interfaces wireguard wg0 port 51820
set interfaces wireguard wg0 peer SITE-A public-key 'rSuuvkAuSoyqvogoA5OJjYBbJKKaYS13pWs4K69PTOE='
set interfaces wireguard wg0 peer SITE-A address 198.51.100.10
set interfaces wireguard wg0 peer SITE-A port 51820
set interfaces wireguard wg0 peer SITE-A allowed-ips 10.10.10.0/30
set interfaces wireguard wg0 peer SITE-A allowed-ips 192.168.10.0/24
set protocols static route 192.168.10.0/24 interface wg0
commit
save
Substitute the public keys your own routers printed. The values above are the examples from this course’s lessons and belong to nothing.
Task 4: Prove the tunnel four times
Four independent claims, four different commands. Take all four; the failure tasks are about watching them come apart.
Claim one — the handshake completed and bytes have moved in both directions. This is the command to run first, always, and on both ends:
$ show interfaces wireguard wg0 summaryinterface: 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
latest handshake: 41 seconds ago
transfer: 4.52 KiB received, 5.18 KiB sentIllustrative output
Three things carry the answer. latest handshake measured in seconds or a
couple of minutes means the keys match and the underlay works. Two
non-zero transfer counters mean packets are crossing in both directions.
The peer is identified by key, not by the name SITE-B you chose —
that name exists in the configuration and nowhere else, so
show configuration commands | match wireguard is the lookup that turns a
key back into a site.
Absence is also information. wg prints the latest handshake line only
once there has been one, and the transfer line only once a byte has
moved. A peer that has never come up appears as three lines and no
“never” — the missing lines are the report.
Claim two — the encrypted packets are on the underlay.
$ sudo tcpdump -ni eth1 'udp port 51820' -c 414:06:11.884210 IP 198.51.100.10.51820 > 198.51.100.20.51820: UDP, length 128
14:06:11.884702 IP 198.51.100.20.51820 > 198.51.100.10.51820: UDP, length 32
14:06:12.885991 IP 198.51.100.10.51820 > 198.51.100.20.51820: UDP, length 128Illustrative output
UDP both ways, and nothing legible. There is no protocol negotiation to watch and no session to inspect — an observer on the underlay learns the two endpoints, the timing and the sizes, and nothing else.
Claim three — the tunnel transport works.
$ ping 10.10.10.2 count 3PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=1.102 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=64 time=0.921 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=64 time=0.944 ms
--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2043msIllustrative output
Claim four — the payload prefixes route across it. This is the one
that matters to a user, and it is a different claim from the one above,
because it exercises the static route and the second entry in
allowed-ips:
$ ping 192.168.20.1 interface dum0 count 3PING 192.168.20.1 (192.168.20.1) from 192.168.10.1 dum0: 56(84) bytes of data.
64 bytes from 192.168.20.1: icmp_seq=1 ttl=64 time=1.208 ms
64 bytes from 192.168.20.1: icmp_seq=2 ttl=64 time=0.988 ms
64 bytes from 192.168.20.1: icmp_seq=3 ttl=64 time=1.011 ms
--- 192.168.20.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2041msIllustrative output
interface dum0 forces the source address to Site A’s payload prefix,
which is what a real host behind the router would use. Without it the
router sources from 10.10.10.1 and you have re-run claim three with a
different destination.
Keep all four captures.
Task 5: Silence, cause one — the underlay is filtered
Give site-b the input policy a public-facing router should have. Order
matters: the management permit is committed in the same operation as the
default action, and if you commit the default action alone you will lose
your session.
configure
set firewall ipv4 input filter rule 1 action accept
set firewall ipv4 input filter rule 1 description "management - keep this first"
set firewall ipv4 input filter rule 1 inbound-interface name eth0
set firewall ipv4 input filter rule 5 action accept
set firewall ipv4 input filter rule 5 description "established and related to the router"
set firewall ipv4 input filter rule 5 state established
set firewall ipv4 input filter rule 5 state related
set firewall ipv4 input filter default-action drop
set firewall ipv4 input filter default-log
commit-confirm 5
Type confirm once you have checked you still have a session. Note what
is deliberately missing: nothing permits UDP 51820.
Wait a couple of minutes, then look at both routers.
$ show interfaces wireguard wg0 summaryinterface: 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
latest handshake: 3 minutes, 12 seconds ago
transfer: 4.52 KiB received, 9.86 KiB sentIllustrative output
The handshake is ageing and sent is climbing while received is not.
site-a is initiating and getting nothing back. Confirm from the wire on
the far side:
$ sudo tcpdump -ni eth1 'udp port 51820' -c 414:22:07.118442 IP 198.51.100.10.51820 > 198.51.100.20.51820: UDP, length 148
14:22:12.121004 IP 198.51.100.10.51820 > 198.51.100.20.51820: UDP, length 148
14:22:17.123551 IP 198.51.100.10.51820 > 198.51.100.20.51820: UDP, length 148Illustrative output
This is the pair that people mis-read. The packets arrive, and there is
no reply, and the obvious conclusion is a key mismatch — a router that
will not authenticate also arrives-and-does-not-reply. But tcpdump on an
ingress interface sees the packet before the input chain does, so
“arrived” here means “reached the NIC”, not “reached WireGuard”.
The counter settles it:
$ show firewall ipv4 input filterRuleset Information
ipv4 Firewall "input filter"
Rule Action Protocol Packets Bytes Conditions
------- -------- ---------- --------- ------- ------------------------------
1 accept all 318 41K iifname eth0
5 accept all 12 960 state established,related
default drop 27 3.9KIllustrative output
The default-action counter is climbing at the rate of the initiations. A
key mismatch would leave it flat, because the packets would have been
accepted by the firewall and discarded by WireGuard, which counts nothing.
That is the discriminator: the drop counter separates “refused entry”
from “refused authentication”, and nothing in show interfaces wireguard
can tell them apart.
Fix it with a rule scoped to the peers you actually have:
configure
set firewall group address-group WIREGUARD-PEERS address 198.51.100.10
set firewall ipv4 input filter rule 10 action accept
set firewall ipv4 input filter rule 10 description "WireGuard from known peers"
set firewall ipv4 input filter rule 10 protocol udp
set firewall ipv4 input filter rule 10 destination port 51820
set firewall ipv4 input filter rule 10 source group address-group WIREGUARD-PEERS
set firewall ipv4 input filter rule 10 inbound-interface name eth1
commit-confirm 5
Confirm, and watch show interfaces wireguard wg0 summary on site-a:
within seconds the handshake age resets and received starts moving.
The tunnel is up and ping 192.168.20.1 interface dum0 still fails, which
is the second thing this policy teaches. The input chain governs traffic
addressed to this router, and that includes the decrypted inner
packets destined for site-b’s own dum0 and wg0 addresses. They
arrive on wg0 rather than on eth1, so no rule you have written so far
describes them:
set firewall ipv4 input filter rule 20 action accept
set firewall ipv4 input filter rule 20 description "from the tunnel to this router"
set firewall ipv4 input filter rule 20 inbound-interface name wg0
commit-confirm 5
Two rules, two hooks, two completely different packets: rule 10 admits the encrypted UDP on the underlay, rule 20 admits what comes out of it. A policy that has one and not the other produces a tunnel that handshakes perfectly and carries nothing, which is Task 6’s symptom arriving from a different cause — and the counters tell them apart, because a firewall drop increments something and an allowed-ips discard does not.
Task 6: Silence, cause two — the far end will not claim your source
Restore the tunnel to health, then remove Site A’s prefix from site-b’s
peer definition — the mistake that copying one peer block to make another
produces, and the one that is hardest to see by reading:
configure
delete interfaces wireguard wg0 peer SITE-A allowed-ips 192.168.10.0/24
commit
Now re-run the two pings from site-a.
ping 10.10.10.2 still works: 10.10.10.0/30 is still in the list.
ping 192.168.20.1 interface dum0 fails, and it fails silently — no ICMP
unreachable, no error, just no replies. Look at the counters on site-a:
$ show interfaces wireguard wg0 summarypeer: cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c=
endpoint: 198.51.100.20:51820
allowed ips: 10.10.10.0/30, 192.168.20.0/24
latest handshake: 22 seconds ago
transfer: 1.12 KiB received, 8.44 KiB sentIllustrative output
A recent handshake with sent climbing faster than received is the
signature of a far-end problem, and it is the most useful single reading
this command produces. The keys are fine — a handshake proves that. Your
packets are leaving. Something at the other end is receiving them and
declining to answer.
Confirm it by capturing the plaintext view at both ends, with the ping still running:
$ sudo tcpdump -ni wg0 -c 414:41:02.118442 IP 192.168.10.1 > 192.168.20.1: ICMP echo request, id 4102, seq 1, length 64
14:41:03.119901 IP 192.168.10.1 > 192.168.20.1: ICMP echo request, id 4102, seq 2, length 64Illustrative output
Run the same capture on site-b’s wg0 and nothing appears. The
packet was decrypted correctly and discarded before it reached 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.
Put it back:
configure
set interfaces wireguard wg0 peer SITE-A allowed-ips 192.168.10.0/24
commit
save
Task 7: Silence, cause three — an MTU nothing ever told the tunnel about
Small packets work and large transfers stall. This is the failure that survives every check you have run so far, because everything you have tested has been small.
Constrain the underlay, the way a provider link or another tunnel in the path would. On both routers:
configure
set interfaces ethernet eth1 mtu 1400
commit
Now ping 192.168.20.1 interface dum0 still succeeds, and so does
show interfaces wireguard wg0 summary. Everything reports healthy. Then
measure, rather than assume, by bisecting with the do-not-fragment flag:
$ ping 192.168.20.1 interface dum0 size 1312 do-not-fragment count 2PING 192.168.20.1 (192.168.20.1) from 192.168.10.1 dum0: 1312(1340) bytes of data.
1320 bytes from 192.168.20.1: icmp_seq=1 ttl=64 time=1.884 ms
1320 bytes from 192.168.20.1: icmp_seq=2 ttl=64 time=1.702 ms
--- 192.168.20.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002msIllustrative output
Now a payload that does not fit. Run it with a capture of the underlay going at the same time, because what you are looking for is on the outside, not in the ping:
$ ping 192.168.20.1 interface dum0 size 1392 do-not-fragment count 2$ sudo tcpdump -ni eth1 -v host 198.51.100.20 -c 6Record which of two things you see, because both are defects and which one
you get depends on your hypervisor and driver. Either the ping gets no
replies at all — the oversized encrypted packet was discarded — or the
verbose capture shows the outer packet arriving as IP fragments, which
reassemble and answer. Fragments are easy to spot in -v output: one
encrypted packet becomes more than one line, carrying flags [+] and a
non-zero offset. Fragmentation looks like success and is not — it costs
a reassembly buffer at the far end, and any middlebox on a real path that
drops fragments turns it back into the first case without warning.
The do-not-fragment flag in the ping applies to the inner packet.
It is the tunnel’s own encapsulation that has decided whether the outer
packet may be fragmented, and that decision is not yours to make from
here — which is the whole reason the interface MTU has to be right.
The arithmetic is worth doing by hand once. WireGuard adds 60 bytes to every packet over an IPv4 underlay: 20 of outer IP header, 8 of UDP, 16 of message header and 16 of authentication tag. The interface default MTU is 1420, which is 1500 minus 80 — the IPv6 figure, chosen so the default is safe on either underlay.
With eth1 at 1400, the largest inner packet that fits is 1400 minus 60,
which is 1340. An ICMP echo carries 28 bytes of headers, so the largest
payload that fits is 1312 — and wg0, still at its default 1420, will
happily accept a 1392-byte payload and hand the underlay a 1480-byte
encrypted packet that does not fit.
Set the interface to what you measured, and clamp TCP beneath it:
configure
set interfaces wireguard wg0 mtu 1340
set interfaces wireguard wg0 ip adjust-mss clamp-mss-to-pmtu
set interfaces wireguard wg0 ipv6 adjust-mss clamp-mss-to-pmtu
commit
save
Re-run both pings. The 1312-byte one still succeeds. The 1392-byte one now
fails immediately and locally, with a message from your own kernel
naming the MTU it refused to exceed, because wg0 knows the packet is too
big instead of handing the problem to the underlay. That change — from
silence, or from a fragment nobody counted, to an error at the sender — is
the entire value of setting the MTU from a measurement.
Production notes — running this as a change
Build one direction at a time and let it be asymmetric for a while.
Configure site-b fully, then site-a. Between the two commits the
tunnel simply does not exist; there is no half-configured state to clean
up, because WireGuard has no state to leave behind. That property makes a
WireGuard rollout unusually safe to stage across two maintenance windows.
The window is for the routing change, not the tunnel. Creating wg0
and its peer affects nothing until a route points at it. Adding the static
route is the disruptive step, and it is a single line, which makes the
rollback a single line.
Decide the preshared key before deployment, not after. It is cheap to add at build time and awkward later, because adding it is a simultaneous change at both ends with a tunnel outage between the two commits if they are not close together. A peer with none configured behaves as though it had one of all zeroes, so “configured on one side” is a mismatch rather than a partial win — and its only symptom is a handshake that never happens.
Plan for rotation, because nothing will remind you. WireGuard has no expiry, no revocation and no certificate to lapse; a key that leaks stays valid until somebody removes it by hand. Put the key inventory somewhere that gets reviewed, with a date, and treat removing a decommissioned peer’s public key as part of decommissioning it rather than as cleanup.
A tunnel with no traffic looks broken. Handshakes refresh lazily and only when there is something to send, so a quiet tunnel shows an ageing handshake and flat counters — indistinguishable, at a glance, from a failure. Monitor a probe across the tunnel rather than the handshake age, or the first real incident will be preceded by weeks of alerts nobody believed.
Validation
show interfaces wireguard wg0 summaryon both routers lists the peer with alatest handshakeinside the last few minutes and non-zero transfer counters in both directions.- The public key each router prints for itself is the value configured as
peer ... public-keyon the other, and your journal records which key belongs to which router. ping 10.10.10.2 count 3fromsite-areturns three replies.ping 192.168.20.1 interface dum0 count 3returns three replies, and the mirror test fromsite-bto192.168.10.1also does.sudo tcpdump -ni eth1 'udp port 51820'on either router shows UDP in both directions and nothing legible.- Your journal holds the Task 5 evidence: initiations arriving at
site-b, no reply, and a climbing default-action counter — plus one sentence on why that counter, rather than the capture, was the discriminator. - Your journal holds the Task 6 counter reading with
sentclimbing andreceivedflat, and records thatsite-b’swg0capture was empty whilesite-a’s was not. - Your journal holds the two bracketing ping sizes from Task 7, the MTU derived from them, and which of the two oversized-packet outcomes your hypervisor produced — loss, or fragmentation.
- Answer in writing: which single command, run on one router, would have looked healthy in all three failures? The answer is why the validation list is nine items and why every one of them names a router.
Expected Outcome
One WireGuard interface per router with one peer, a static route for the
far site’s prefix, an MTU set from measurement rather than from the
default, and MSS clamping on both ends. site-b carries an input policy
that defaults to drop and permits UDP 51820 only from a known peer
address, with management permitted ahead of it.
Both routers still hold /config/pre-lab.boot, so the topology resets to
its baseline without a rebuild. Your journal holds the key inventory and
every capture named in Validation.
Troubleshooting
No latest handshake line at all, on both ends. Nothing has ever
completed. Work outwards: capture UDP 51820 on both underlay interfaces to
see whether the initiations leave and arrive, then read the receiving
router’s input-filter counters, then compare
show interfaces wireguard wg0 public-key on each router against the
peer ... public-key configured on the other.
A handshake exists but no traffic passes. Read the two transfer
counters. Sent climbing with received flat puts the fault on the far
router — its allowed-ips, its firewall, or its route back. Neither moving
means nothing is being handed to the tunnel here: check
show ip route 192.168.20.0/24.
ping 10.10.10.2 works and the payload prefix does not. The tunnel
transport is fine and the payload prefix is missing from one of the two
lists it has to appear in. Check the static route locally and the
peer’s allowed-ips remotely.
Destination host unreachable, immediately, from your own router. The
route hands the packet to wg0 and no peer claims the destination. This
is the local half of an allowed-ips gap and it is the loud one.
The tunnel comes up and dies whenever it is idle, on one side only.
That side is behind a NAT whose UDP binding is expiring. Add
set interfaces wireguard wg0 peer NAME persistent-keepalive 25 on the
NAT’d side so it keeps the binding alive. It is not needed in this lab,
where both routers have their own addresses.
Everything works until you copy a large file. Task 7. Measure the
ceiling with size and do-not-fragment, set the interface MTU, clamp
the MSS.
Cleanup
Everything here is on an isolated bridge and management was never touched, so cleanup is about leaving a reproducible state.
Step 1. On both routers, load the baseline saved in Task 2:
configure
load /config/pre-lab.boot
compare
commit
save
compare before commit shows exactly what loading the file will change.
An empty comparison means you are already at the baseline. Confirm the
firewall went with it — show firewall ipv4 input filter on site-b
should report no such rule set — because a default-drop input policy left
behind on a VM you later reuse for something else is a confusing afternoon.
Step 2. Confirm the tunnel is gone with show interfaces wireguard, which
should list nothing.
Step 3. Remove the VMs, then the bridge.
# Run on the Proxmox host, after confirming these IDs with `qm list`.
for VMID in 280 281; do
qm stop "$VMID"
qm destroy "$VMID" --purge
done
Then remove the vmbr95 stanza from /etc/network/interfaces and run
ifreload -a. Confirm with ip link show vmbr95, which should report
that the device does not exist.
What You Learned
- The handshake and the counters are the tunnel’s whole instrument panel. A recent handshake means the keys and the underlay are good; two moving counters mean traffic is crossing. Everything else in this lab was diagnosis of one of those two readings being wrong.
- Silence is the design, not a fault. WireGuard never answers what it cannot authenticate and never logs it, so three unrelated failures produced the same symptom and had to be separated by evidence rather than by error messages.
- A drop counter distinguishes “refused entry” from “refused authentication”. A capture on the ingress interface cannot: it sees the packet before the firewall does.
- Every prefix is named twice and the two lists fail differently. A
missing route is a local error you see immediately; a missing
allowed-ipsat the far end is silence you can only see from the far end. - The default MTU is a guess about someone else’s network. You measured the real ceiling with two pings and set the interface from the measurement — which converted a silent stall into an immediate local error.
- The MSS clamp is the part that serves the users. Setting the tunnel MTU fixes the router’s own traffic; clamping fixes everybody behind it.