Runbook: Configure WireGuard
1 · Prerequisites
Confirm every item is in place before any state change.
- WireGuard concept — kernel WireGuard, Noise protocol, UDP-based VPN
- WireGuard keys — Curve25519 key pairs, preshared key, key management
- WireGuard peers — named peers, allowed-ips, peer address and port, persistent keepalive
- WireGuard routing — routing over WireGuard, MTU 1420, MSS clamping
- WireGuard firewall and outer-packet routing — UDP port, input chain, multi-WAN
- MSS clamping — ip adjust-mss, MSS = MTU - 40, clamp-mss-to-pmtu, and which interface to clamp on
- commit-confirm — the rollback safety net for remote changes
2 · Pre-checks
Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.
- · Both ends of the tunnel are identified by name, by public WAN address, and by owner, and you have a working session on both. A tunnel configured from one side is not a tunnel; it is a route into a black hole.
- · Out-of-band access to this router is open and proved. Bringing a tunnel into service means committing a static route into it in the same change, and a route that swallows the prefix your management session arrives on takes the router with it.
- · The exact prefixes each side will reach through the tunnel are agreed in writing before any key is generated. These become
allowed-ipson both ends and each list must be the mirror image of the other; a disagreement here produces silent one-way drops, not an error. - · Neither set of prefixes overlaps anything either router already reaches — check with
show ip routeon both ends. The static route you will add for each prefix is a routing change riding inside a VPN ticket, and it wins or loses against the existing route on ordinary longest-prefix and administrative-distance rules. - · The UDP listen port is agreed (51820 unless there is a reason) and confirmed reachable end to end: a firewall or a carrier-grade NAT in the path silently prevents the handshake and produces exactly the same symptom as a wrong key.
- · Which end sits behind NAT is established. That end needs
persistent-keepaliveand must be the one that initiates; the other end can leave the peer address unset. - · A decision on the preshared key is recorded either way, with its owner. It is optional, it must be identical on both ends, and it is a second secret to distribute and rotate.
- · A secure channel exists to move public keys between the two operators. Public keys are not secret, but a key substituted in transit is an authenticated tunnel to the wrong router — read the fingerprint back over a second channel.
- · Pre-change configuration captured on both routers:
show configuration commands | strip-privatesaved off-box, plusshow ip routeandshow firewall ipv4 forward filter. - · The MTU the tunnel will carry is decided in advance (1420 is the usual starting point for an IPv4 underlay) and there is a plan to measure it rather than assume it.
3 · Procedure
Execute each step in order. Verify the expected output of a step before moving to the next.
- 1Open the out-of-band session on this router. Keep it open until the change is confirmed and saved.
- 2Capture the baseline on both routers:
show configuration commands | strip-private,show ip route,show interfaces. Save off-box. - 3Generate the key pair on each router separately — never generate both ends in one place. From configure mode:
run generate pki wireguard key-pair install interface wg01, which assigns the private key to the interface and prints the public key. - 4Record each public key and read it back to the other operator over a second channel. Retrieve it later with
show interfaces wireguard wg01 public-key. - 5If a preshared key is in scope, generate it once on one side only —
generate pki wireguard preshared-key— and move it over a secret-bearing channel. Both ends must carry the identical value. - 6Build the interface on the far end first, including its peer entry for this router: address, port, and the agreed
allowed-ips. A far end that is ready before this one is ready turns a half-finished change into a tunnel that simply has not come up yet, rather than a route into nothing. - 7Build the interface on this router:
set interfaces wireguard wg01 address 10.255.255.1/30,set interfaces wireguard wg01 port 51820,set interfaces wireguard wg01 mtu 1420,set interfaces wireguard wg01 description SITE-B. - 8Add the peer, naming it for the site rather than for its key:
set interfaces wireguard wg01 peer SITE-B public-key ...,... peer SITE-B address 198.51.100.20,... peer SITE-B port 51820,... peer SITE-B allowed-ips 10.255.255.0/30,... peer SITE-B allowed-ips 192.168.20.0/24. Addpersistent-keepalive 25on whichever end is behind NAT. - 9Add the routes, because
allowed-ipsdoes not:set protocols static route 192.168.20.0/24 interface wg01for every far-side prefix. On VyOSallowed-ipsis handed towg setand nothing else — unlikewg-quick, the VyOS interface script installs no kernel route from it, so without this step the packet never reacheswg01and the tunnel looks healthy while carrying nothing. The tunnel /30 needs no static route; it is connected from the interface address. - 10Open the underlay:
set firewall ipv4 input filter rule 20 action accept,... rule 20 protocol udp,... rule 20 destination port 51820,... rule 20 source address 198.51.100.20,... rule 20 inbound-interface name eth0. Constrain it to the peer address; a WireGuard port open to the Internet is a scanning target that answers nothing but is still a target. - 11Permit the traffic that will now transit the tunnel: forward-chain rules naming
wg01in both directions, matching the prefixes agreed in the pre-checks. Routing the traffic and permitting it are two separate decisions and only one of them isallowed-ips. - 12Read the candidate before it goes live:
compare. Check theallowed-ipslist character by character against the written agreement, and check that no entry covers the prefix your management session arrives on. - 13Apply with
commit-confirm 5. Treat the commit as capable of interrupting every peer already on this interface, not only the one being added. - 14Verify the handshake, which is the only liveness signal WireGuard has:
show interfaces wireguard wg01 summary. The bare command withoutsummaryshows the link and its aggregate counters, not the peers. A peer that has never handshaked prints nolatest handshakeline at all, so you are checking that a line is present, not reading a status word. - 15Verify the data plane in both directions: ping the far tunnel address, then a host behind the far end, then have the far end do the same back. One-way success is the signature of an
allowed-ipsmismatch and must not be recorded as a pass. - 16
confirmto cancel the rollback timer, thensaveon both routers. - 17Measure the usable MTU before declaring the tunnel finished:
ping 192.168.20.10 size 1400 do-not-fragment, walking the size down until it succeeds. A tunnel that passes small pings and stalls on TLS is an MTU problem, and it will be reported as an application fault a week later. - 18Set MSS clamping from the measured result:
set interfaces wireguard wg01 ip adjust-mss clamp-mss-to-pmtu, or an explicit value if the path needs one. Commit, confirm, save. - 19Record in the change ticket: both public keys, the agreed prefixes, the measured MTU, whether a preshared key is in use and where it is stored, and the rotation owner for the key material.
4 · Verification
Confirm the procedure actually fixed the problem.
- ✓
show interfaces wireguard wg01 summaryshows the peer with a recentlatest handshakeline and atransferline with non-zero counts both ways. Peers appear there by base64 public key, not by the name you chose, because on 1.5 this subcommand printswg showoutput verbatim. Interface state alone proves nothing — WireGuard interfaces come up whether or not any peer will ever answer. - ✓Transfer counters are non-zero in both directions for that peer. Bytes sent with zero bytes received is the classic half-open tunnel: your packets are leaving, and the far end is discarding them or cannot reply.
- ✓
show ip route 192.168.20.0/24on this router resolves viawg01, and the mirror-image lookup on the far end resolves back via its own tunnel interface. - ✓A host behind this router reaches a host behind the far router, and the reverse direction is tested independently by the far-end operator. Both directions, both operators, before the change is called done.
- ✓
show firewall ipv4 input filtershows the UDP rule matching a non-zero packet count. A rule at zero after a successful handshake means the traffic is being accepted somewhere else, and the rule you think is protecting the port is not. - ✓The
allowed-ipson each end is the exact mirror of the other end, checked against the written agreement rather than against memory. - ✓No static route added by this change covers the management prefix or the WAN prefix, and no
allowed-ipsentry covers them either — confirmed by readingshow ip routeafter the commit, not by intent before it. The route is what can strand the session; theallowed-ipsentry is what would encrypt that traffic to the wrong peer once a route existed. - ✓A sized, do-not-fragment ping establishes the usable MTU, and MSS clamping is configured to match. A tunnel signed off on 64-byte pings has not been tested.
- ✓Existing peers on the same interface still show recent handshakes after the commit, if there were any.
- ✓Both routers are saved. A tunnel that survives only until the next reboot is worse than no tunnel, because it will be trusted.
5 · Rollback
If verification fails, undo the procedure in reverse order.
- ↶Inside the
commit-confirmwindow with a router gone quiet: wait. The timer expires, the previous configuration returns, and no action is the correct action. - ↶With a working session:
rollback 1, thencompareto read what it will undo, thencommit.rollbackalone loads the candidate and changes nothing until the commit. - ↶Prefer the narrow revert, and remember it is two nodes.
delete protocols static route 192.168.20.0/24 interface wg01stops the router steering that prefix into the tunnel;delete interfaces wireguard wg01 peer SITE-B allowed-ips 192.168.20.0/24stops WireGuard encrypting it to that peer and accepting it back. Removing only theallowed-ipsentry leaves the route pointing at an interface that will now refuse the traffic. Deleting the whole interface to fix one prefix is how a second outage starts. - ↶
delete interfaces wireguard wg01removes the tunnel entirely. Every flow using it drops at the commit, and everyset protocols static route ... interface wg01added by this change goes inactive with the interface — delete those too rather than leaving them behind pointing at nothing. This is the right move when the tunnel is actively harming traffic and the wrong move when it is merely not working yet. - ↶POINT OF NO RETURN: a private key that has been generated, committed and distributed cannot be un-distributed. Rolling back the configuration does not un-tell the far end your public key, and it does not remove the private key from any configuration backup taken since the commit. If the key material is the reason for the rollback, treat it as a rotation, not a revert.
- ↶Rolling back this end alone leaves the far end holding a peer entry for a router that no longer answers. That is harmless for traffic but it is a stale trust relationship — tell the far-end operator, and have them remove the peer.
- ↶If a static route added by this change has already black-holed the management path, in-band rollback is not available and the console is the only route in. This is the failure
commit-confirmexists for, and it is the reason the confirm window is set before the risky commit rather than after it.
6 · Escalation
When the runbook isn't enough, contact:
- · Escalate to the far-end operator, not to your own team, when the handshake never completes and the underlay is proved open from this side. Half the evidence lives on their router and no amount of work on yours will produce it.
- · Escalate to the network team when the underlay path traverses a carrier-grade NAT or a provider that filters UDP. A tunnel that cannot get its first handshake packet through is not a VyOS problem and will not be fixed by re-reading the configuration.
- · Escalate to security immediately if a private key may have been exposed — pasted into a ticket, mailed, left in an unencrypted configuration backup, or generated on a shared machine. The remedy is rotation on both ends, and it is a security decision about scope, not an operator decision about convenience.
- · Escalate to the service owner before adding an
allowed-ipsentry that overlaps a prefix the router already reaches by another path. That is a routing change wearing VPN clothes and it will move traffic that nobody asked to move. - · Escalate to the application owner when the tunnel passes ping and fails TLS or file transfer. That signature is MTU, the fix is clamping, and the person seeing the failure needs to know it is not their application.
- · Escalate when a tunnel that was working stops after an unrelated commit on the same interface. Peers sharing an interface share its lifecycle, and that is a design question about how many peers belong on one WireGuard interface.
WireGuard is small enough to configure in ten lines and unforgiving enough that most of those lines have a way of failing without saying so. It has no negotiation phase to watch, no proposal mismatch to read in a log, and no notion of a tunnel being administratively “up” — the interface exists from the moment you commit it, whether or not anything at the far end has ever agreed to talk to you.
That leaves an operator with two sources of truth and nothing else: the handshake timestamp, and whether packets came back. Everything in this runbook is arranged around getting to those two facts quickly and not accepting a substitute for them.
When this runbook applies
- A new site-to-site tunnel between two routers you or a counterpart control at both ends.
- Adding a peer to a WireGuard interface that already exists.
- Rebuilding a tunnel whose key material has to be rotated.
When it does not
- Remote-access VPN for laptops and phones. Same protocol, very different operational shape: many short-lived peers, client configuration generation, and an address pool. The two-ended coordination that dominates this runbook does not apply.
- Diagnosing a tunnel that used to work. Go to the troubleshooting material for that. This runbook builds; it does not investigate.
- Anywhere the far end requires IKEv2, certificate identities, or revocation. WireGuard has no PKI. A public key is the entire identity and the only way to revoke one is to delete it from the peer configuration at the other end.
Blast radius
Larger than it looks, in one specific way: bringing a tunnel into service is a routing change, committed at the same moment as the crypto. A tunnel is not a passive addition to the router.
Be precise about where that routing change lives, because the folklore
here is wrong. On VyOS, allowed-ips installs no kernel route. The
value is handed to wg set and nothing more; unlike wg-quick, the
VyOS interface script does not turn it into a route. So a tunnel only
carries a prefix once you have added set protocols static route 192.168.20.0/24 interface wg01 alongside the allowed-ips entry. Two
nodes, one intent, and the one people forget is the route.
That is also where the shift-ending case lives. set protocols static route 0.0.0.0/0 interface wg01 points the default route into the
tunnel; on a laptop that is a full-tunnel VPN, on a router it is a
lockout, and it is why the out-of-band session in the pre-checks is not
a formality. allowed-ips 0.0.0.0/0 on its own will not strand you —
but it makes the peer eligible to receive anything the router later
routes into wg01, so the two entries together are the hazard and they
tend to be typed together.
The one field that decides everything
allowed-ips is doing two jobs at once, and they are jobs that most VPN
implementations keep apart.
- Outbound, it is peer selection. Once the kernel has already
decided to send a packet out of
wg01, WireGuard picks the peer whoseallowed-ipscovers the destination and encrypts to it. It is the second decision, not the first: the kernel routing table still has to put the packet on the interface, which is what the static route is for. - Inbound, it is an access control list. A decrypted packet whose
source address is not in that peer’s
allowed-ipsis discarded.
The consequence is the failure mode you will actually meet. Side A lists side B’s LAN; side B forgets to list side A’s LAN. A ping from A is encrypted, arrives, decrypts correctly, and is then dropped by B because B has no rule that says packets from A’s LAN may come from that peer. There is no log line, no counter that obviously moves, and no error at either commit. What you see is a handshake that works and traffic that does not.
Keys: generate locally, exchange carefully
configure
run generate pki wireguard key-pair install interface wg01
# Read the public key back at any time:
run show interfaces wireguard wg01 public-keyThe private key never leaves the router that generated it. Generating both ends in one place — a laptop, a jump host, a ticket — creates a copy of both secrets in a third location and quietly makes that location part of the trust boundary.
Public keys are not secret, but they are the identity, and a public key substituted in transit gives you a correctly authenticated tunnel to the wrong router. Move them over one channel and confirm them over a second.
The interface and the peer
set interfaces wireguard wg01 address 10.255.255.1/30
set interfaces wireguard wg01 port 51820
set interfaces wireguard wg01 mtu 1420
set interfaces wireguard wg01 description 'tunnel to SITE-B'
set interfaces wireguard wg01 peer SITE-B public-key 'PASTE-THE-FAR-END-PUBLIC-KEY'
set interfaces wireguard wg01 peer SITE-B address 198.51.100.20
set interfaces wireguard wg01 peer SITE-B port 51820
set interfaces wireguard wg01 peer SITE-B allowed-ips 10.255.255.0/30
set interfaces wireguard wg01 peer SITE-B allowed-ips 192.168.20.0/24
# allowed-ips installs no route on VyOS. Without this line the packet
# never reaches wg01 and the tunnel comes up carrying nothing.
set protocols static route 192.168.20.0/24 interface wg01
compareThat last line is not optional and it is the step most often missed.
allowed-ips is a WireGuard-internal table; the kernel routing table is
a separate thing and only the static route writes to it. The tunnel /30
is the one prefix that needs no static route, because the interface
address makes it connected.
Name the peer for the site, not for its key. The name is what appears in
compare, in the configuration diff your reviewer reads, and in the
command you will type at 03:00 to delete it. A peer called
SITE-B is a peer someone can act on; a peer named after a base64 string
is one they will hesitate over.
The peer address is only needed by the side that initiates. If the far
end is behind NAT and this router has a stable public address, leave the
far end’s address unset here and let it come to you — WireGuard learns
the peer’s current source address from the packets it receives, which is
also how roaming works.
Whichever end is behind NAT needs persistent-keepalive 25 so the NAT
binding is refreshed before it expires. Without it the tunnel comes up,
works, goes quiet, and stops — a failure that presents as intermittent
and is in fact perfectly periodic.
Two decisions worth making explicitly
Split tunnel or full tunnel. List only the prefixes that must cross
the tunnel. 0.0.0.0/0 is a legitimate choice for a client and almost
never the right one between two routers; when it is, it is a deliberate
default-route change that needs its own review, not a side effect of a
VPN ticket.
Preshared key or not. It is an optional second secret mixed into the handshake, identical on both ends. It buys resistance to a future attacker who has recorded the traffic and later acquires the ability to break the public-key exchange. It costs a second secret to distribute, store and rotate, and a tunnel that fails to come up with no useful error if the two ends disagree about it. Decide, record the decision and its owner, and do not leave it half-applied.
Firewall: two chains, two different jobs
# Underlay: let the peer reach this router's WireGuard port
set firewall ipv4 input filter rule 20 action accept
set firewall ipv4 input filter rule 20 protocol udp
set firewall ipv4 input filter rule 20 destination port 51820
set firewall ipv4 input filter rule 20 source address 198.51.100.20
set firewall ipv4 input filter rule 20 inbound-interface name eth0
# Payload: let traffic transit between the LAN and the tunnel
set firewall ipv4 forward filter rule 30 action accept
set firewall ipv4 forward filter rule 30 inbound-interface name wg01
set firewall ipv4 forward filter rule 30 destination address 192.168.10.0/24
set firewall ipv4 forward filter rule 31 action accept
set firewall ipv4 forward filter rule 31 outbound-interface name wg01
set firewall ipv4 forward filter rule 31 source address 192.168.10.0/24
compareThese are genuinely separate concerns and a working tunnel needs both. The input rule governs the encrypted UDP arriving on the WAN interface; without it the handshake never happens and the symptom is identical to a wrong key. The forward rules govern the decrypted traffic crossing between the tunnel and the LAN; without them the handshake succeeds, the counters move, and nothing reaches a host.
Constraining the input rule to the peer’s source address is worth the extra line. WireGuard does not answer unauthenticated packets, so an open port leaks nothing — but it is still a port that appears in a scan, and narrowing it costs nothing.
Commit, then look for the handshake
commit-confirm 5
run show interfaces wireguard wg01 summary
run show ip route 192.168.20.0/24
confirm
saveshow interfaces wireguard wg01 summary is the only place the truth
lives. The summary subcommand matters: on 1.5 it runs sudo wg show wg01 and prints wg’s output verbatim, while the bare
show interfaces wireguard wg01 runs the generic interface script and
gives you the link, the MTU and aggregate counters with no peer
information at all.
Look for a latest handshake line that is recent, and a transfer line
with non-zero counts in both directions. Read those as presence
checks, not as values: wg show prints latest handshake only when a
handshake has happened and transfer only once a byte has moved, so a
peer that has never come up prints its peer, endpoint and
allowed ips and then simply stops. There is no never to look for.
Note also that peers are listed by base64 public key — wg has never seen
the name SITE-B, which lives only in the VyOS configuration.
Then measure the MTU, because ping will lie to you
ping 192.168.20.10 size 1400 do-not-fragment count 3
ping 192.168.20.10 size 1372 do-not-fragment count 3A default 64-byte ping proves the tunnel forwards a packet. It proves nothing about the packets that actually carry work. WireGuard adds encapsulation overhead to every packet, so an inner packet sized for a 1500-byte Ethernet path no longer fits, and what the user sees is a web page that loads its HTML and hangs on the first large response, or an SSH session that connects and freezes on the first big output.
Set clamping once you have measured, so TCP negotiates a segment size that fits instead of discovering the ceiling the hard way:
set interfaces wireguard wg01 ip adjust-mss clamp-mss-to-pmtu
commit-confirm 5
confirm
saveclamp-mss-to-pmtu derives the value from the interface MTU, which is
why setting the interface MTU deliberately in the first place matters.
Where the underlay itself is constrained — PPPoE, a tunnel inside a
tunnel — an explicit value from your measurement is the safer choice.
Common patterns
| Symptom | Likely cause | Where to look |
|---|---|---|
Peer block ends at allowed ips, with no latest handshake line | UDP blocked, wrong peer address, wrong public key | show firewall ipv4 input filter counters, then the far end |
| Handshake fine, no traffic either way | Forward rules missing on one or both ends | show firewall ipv4 forward filter |
| Handshake fine, traffic one way only | allowed-ips mismatch — the receiving end is wrong | The far end’s peer configuration |
| Works, then stops after a minute or two | NAT binding expired, no persistent-keepalive | The NATed end’s peer configuration |
| Ping works, TLS and file transfer stall | MTU too high, no MSS clamping | Sized do-not-fragment ping |
| Handshake fine, counters flat, nothing entering the tunnel | No set protocols static route ... interface wg01, so nothing is routed to it | show ip route for the prefix that should be crossing |
| Tunnel up, wrong traffic using it | The static route added for the tunnel beat an existing route | show ip route for the overlapping prefix |
| Router unreachable after commit | A static route into the tunnel covers the management prefix | Console; let commit-confirm expire |
| Existing peers dropped when a new one was added | The commit reloaded the shared interface | show interfaces wireguard wg01 summary — a latest handshake line under every peer |
| Tunnel gone after a reboot | Committed, never saved | /config/config.boot |