Skip to main content
RunBook Academy

VyOSXLI · WireGuardWireGuard

WireGuard peers — named peers, allowed-ips, peer address and port, persistent keepalive

Advanced⏱ ~20 minshow interfaces wireguardwg showconfigurecomparecommitsaverollbacktcpdumpping

What you'll learn

  • Configure a named WireGuard peer (public-key, address and port, allowed-ips)
  • Explain what allowed-ips actually mean (route + crypto policy)
  • Use persistent keepalive to maintain NAT bindings and detect failures
  • Diagnose common peer configuration problems

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.

A WireGuard peer is one far end of a WireGuard interface — the router this one exchanges encrypted packets with. On VyOS a peer is a named node: you choose a label, and every property of that far end hangs beneath it. The properties are its public key (identity), where to send packets to reach it (an address and a port), which inner addresses belong to it (allowed-ips), and optionally a keepalive interval and a preshared key.

This lesson covers each of those nodes, what allowed-ips really mean (they are simultaneously a route and a crypto policy), the role of persistent keepalive, and the production patterns for peer management.

Anatomy of a peer on VyOS 1.5

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 interfaces wireguard wg0 peer SITE-B persistent-keepalive '25'

SITE-B is the peer name. It is a label of your choosing (up to 100 characters, no spaces), it has no protocol meaning, and it is what compare shows your reviewer and what you type to delete the peer at 03:00. Name it for the site, not for its key.

NodeRequiredMeaning
public-keyYesThe far end’s static Curve25519 public key. Its identity on the wire.
allowed-ipsYesInner prefixes that belong to this peer. Multi-valued: repeat the node.
addressOnly to initiateThe far end’s outer IP, where handshakes are sent.
portWith addressThe far end’s listening UDP port. Set both or neither.
persistent-keepaliveOptionalSeconds between empty keepalive packets. Unset means none.
preshared-keyOptionalSymmetric secret mixed into the handshake. Identical on both ends.
descriptionOptionalFree text.
disableOptionalKeeps the peer configured but inactive.

Commit refuses a peer without a public key (Wireguard public-key required for peer "SITE-B"!) and a peer without allowed-ips (Wireguard allowed-ips required for peer "SITE-B"!), naming the peer in each case. It also refuses an interface with no private-key at all, and — on 1.5 specifically — an interface with no peer at all: At least one Wireguard peer is required!.

There is deliberately no host-name node on this release. A peer’s outer location is an IP address; if the far end’s address moves, something outside WireGuard has to update the configuration. (A host-name leaf taking an FQDN exists in the 1.6 development tree, but writing it on 1.5 is an invalid path.)

public-key — the peer’s identity

The far end’s 32-byte Curve25519 public key, base64 encoded, is what authenticates it. There are no certificates, no CA and no revocation list: possession of the matching private key is the proof of identity.

set interfaces wireguard wg0 peer SITE-B public-key 'cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c='

Two guards apply within one interface. A public key may appear on only one peer — configuring the same key twice is rejected as a duplicate public-key. And a peer may not carry the public key of the interface it is configured on; that is rejected as a peer with an identical public key to the interface, and it almost always means both routers’ key pairs were generated in the same place and got crossed.

Read show interfaces wireguard wg0 peers to see the keys the running interface actually holds, which is the value to compare against what the far end says its own public key is.

address and port — where the far end is

address is the far end’s outer IP and port is the UDP port it listens on. They are two nodes, and commit rejects one without the other:

Both Wireguard port and address must be defined for peer "SITE-B" if either one of them is set!
set interfaces wireguard wg0 peer SITE-B address '198.51.100.20'
set interfaces wireguard wg0 peer SITE-B port '51820'

address takes an IPv4 or an IPv6 literal and nothing else — there is no name here to resolve, and no place to put one. A far end on a dynamic address is therefore a problem you solve outside WireGuard: something has to notice the change and rewrite the address value. That is a real gap on 1.5 rather than a design principle, and it is worth naming as one before you plan a deployment around a peer with a changing address.

Only the side that initiates needs to know where the far end is. WireGuard learns a peer’s current source address from the authenticated packets it receives, which is both how roaming works and why a hub with a stable public address can serve spokes behind NAT without configuring any address for them at all.

51820 is the conventional port. Any UDP port works, and moving to 443 or 53 to slip past a restrictive middlebox is a real deployment pattern — but it is obfuscation, not security, and it does not change the fact that the port must be open on the underlay.

flowchart LR
  R1["R1 wg0<br/>public 198.51.100.10:51820<br/>peer SITE-B address 198.51.100.20"]
  R2["R2 wg0<br/>public 198.51.100.20:51820<br/>peer SITE-A address 198.51.100.10"]
  R1 -- "handshake to the configured address" --> R2
  R2 -. "replies to the learned source address" .-> R1

allowed-ips — both a route and a crypto policy

allowed-ips is the most subtle node in WireGuard, and it does two jobs at once:

  1. Outbound, it is the routing decision. A packet whose destination falls inside a peer’s allowed-ips is encrypted to that peer. This table is WireGuard’s own, consulted after the kernel has already decided to send the packet out of wg0.
  2. Inbound, it is the crypto policy. A packet that decrypts correctly is still discarded if its source address is not inside the allowed-ips of the peer it came from. That is what stops an authenticated peer from injecting traffic that claims to come from somewhere else.

The node is multi-valued, so several prefixes means several commands — not one comma-separated string:

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'

A common remote-access pattern, from each side:

# Hub: this client owns one tunnel address and nothing else
set interfaces wireguard wg0 peer LAPTOP-01 allowed-ips '10.10.10.7/32'

# Client: send everything through the hub (full tunnel)
set interfaces wireguard wg0 peer HUB allowed-ips '0.0.0.0/0'

And site to site, where each side lists what lives behind the other:

# On R1, describing R2
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'

# On R2, describing R1
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'

The two sides are not copies of each other and they are not free to disagree. What R1 sends must be inside what R2 will accept from R1, in both directions, or the packet is dropped after decryption with no error anywhere.

persistent-keepalive

persistent-keepalive sends an empty authenticated packet to the peer every N seconds. It is unset by default, which means no keepalive at all. It exists for two reasons:

  1. NAT traversal. A peer behind NAT depends on a UDP binding that the NAT device drops after some idle period — commonly 30 to 120 seconds, and not something you control. A keepalive shorter than that timeout keeps the binding alive so the far end’s packets still have a path back.
  2. Failure detection. A silent peer is noticed sooner. WireGuard has no session teardown, so without traffic there is nothing to fail; with a keepalive, missed packets are visible in the handshake and transfer counters within a couple of intervals.
set interfaces wireguard wg0 peer HUB persistent-keepalive '25'

25 seconds is the conventional value: comfortably below the shortest NAT timeouts you are likely to meet, and cheap — a handful of small packets a minute.

Configuration and validation

configure

# R1 (SITE-A) - identity, address, port
run generate pki wireguard key-pair install interface wg0
set interfaces wireguard wg0 address '10.10.10.1/30'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 mtu '1420'
set interfaces wireguard wg0 description 'tunnel to SITE-B'

# R1 - the far end as a named peer
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'

# R1 - and the route, because allowed-ips does not install one
set protocols static route 192.168.20.0/24 interface wg0

compare
commit
save

That last line is not optional and it is the step most often missed. allowed-ips is passed straight to wg set and nothing more; the VyOS interface script installs no routes from it. Without a route, the packet for 192.168.20.10 never reaches wg0 at all and the tunnel looks healthy while carrying nothing. The tunnel /30 itself needs no static route — it is a connected route from the interface address.

On R2, the mirror image — note that the peer name, the allowed-ips, the address and the route all differ; only the port happens to match:

configure

run generate pki wireguard key-pair install interface wg0
set interfaces wireguard wg0 address '10.10.10.2/30'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 mtu '1420'

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

compare
commit
save

Validation:

# The peer, its handshake and its counters. On 1.5 this IS `sudo wg show
# wg0`, so peers appear by public key - the name SITE-B is not in it.
vyos@R1:~$ show interfaces wireguard wg0 summary

# The three narrow views, each `wg show wg0 <subcommand>`
vyos@R1:~$ show interfaces wireguard wg0 peers
vyos@R1:~$ show interfaces wireguard wg0 endpoints
vyos@R1:~$ show interfaces wireguard wg0 allowed-ips

# The same data from the underlying tool
vyos@R1:~$ sudo wg show wg0

# Reachability, inside the tunnel and then behind it
vyos@R1:~$ ping 10.10.10.2 count 3
vyos@R1:~$ ping 192.168.20.10 count 3

# What is actually on the wire, encrypted and then decrypted
vyos@R1:~$ sudo tcpdump -ni eth0 'udp port 51820' -c 4 -vv
vyos@R1:~$ sudo tcpdump -ni wg0 -c 4
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
  latest handshake: 1 minute, 3 seconds ago
  transfer: 1.15 MiB received, 942.53 KiB sent

A clean validation: the peer’s key is the one you configured for SITE-B, latest handshake is inside the last few minutes, and the transfer counters are non-zero in both directions. Bytes sent with zero bytes received is the single most informative state this produces — your packets are leaving and the far end is not replying, which puts the fault on the other router.

Note that the peer is identified by key, not by the name you chose. On 1.5 the peer name is a configuration-side label only; wg show never sees it. show configuration commands | match wireguard is the lookup that turns a key back into a site, and it is the reason the naming convention has to be paired with an inventory rather than replacing one.

Production failure modes

Allowed-ips that do not cover the real source

R1 sends 192.168.10.50 to 192.168.20.10. R1 is happy: the destination is inside SITE-B’s allowed-ips, so the packet is encrypted and sent. R2 decrypts it, checks the source against SITE-A’s allowed-ips, finds 192.168.10.0/24 missing — someone listed only the tunnel /30 — and discards it.

Diagnostic: sudo tcpdump -ni wg0 on R1 shows the packet entering the tunnel; the same capture on R2 shows nothing. The counters agree: bytes sent on R1, bytes received on R2, and no reply. The router that is wrong is the one that sees nothing.

Fix: add the missing prefix to the far end’s allowed-ips for this peer. Treat allowed-ips as a routing design that both sides must agree on, and change them in pairs.

Peer address without port, or port without address

A peer configured with address and no port, or with port and no address, does not commit:

Both Wireguard port and address must be defined for peer "SITE-B" if either one of them is set!

The error says so explicitly, but it arrives at commit time rather than at set time, which surprises operators who expect the CLI to reject the line as they type it. Only value constraints — a key that is not base64, an interface not named wgN — are checked at set.

Fix: set address and port together, or neither. Leaving both unset is a legitimate configuration for a peer this router never initiates to.

Wrong public key on the peer

A truncated paste, or a key from the wrong router. The far end cannot authenticate the initiation and discards it in silence — WireGuard never answers a packet it cannot authenticate, which is a design goal, not a bug.

Diagnostic: show interfaces wireguard wg0 summary shows the peer with its endpoint and allowed ips and no latest handshake line and no transfer line — wg show prints those only once there has been a handshake and once a byte has moved, so you are reading an absence rather than a “never”. Then compare show interfaces wireguard wg0 public-key on each router with the peer NAME public-key configured on the other. It cannot be diagnosed from one side.

Fix: correct the key on the router that has it wrong.

No keepalive on the end behind NAT

The tunnel establishes, carries traffic, idles, and stops. Traffic initiated from the NAT’d side revives it; traffic initiated from the public side does not.

Diagnostic: on the public side, a latest handshake that is minutes old and no recent received bytes, while the NAT’d side believes the peer is fine.

Fix: persistent-keepalive 25 on the peer definition at the NAT’d end.

Overlapping allowed-ips between peers

Two peers on one interface are given prefixes that overlap. The newest assignment takes the prefix, so one peer silently stops receiving traffic for it — and nothing in the configuration looks wrong, because both lines are still there.

Diagnostic: show interfaces wireguard wg0 allowed-ips shows what the kernel actually holds, which is not what the configuration reads.

Fix: make the peers disjoint, or split them onto separate interfaces with separate ports.

Rollback

# Enter configuration mode and write the running configuration to a
# file you can load back. `save` is a configuration-mode command that
# takes a path; operational mode has no `| save` pipe.
configure
save /config/pre-change-wg-peer-TICKET.conf

# Remove the peer by its name - the whole subtree goes with it
delete interfaces wireguard wg0 peer SITE-B

# Read the diff before committing anything
compare
commit-confirm 5
confirm

# Or restore the previous configuration
load /config/pre-change-wg-peer-TICKET.conf
commit
save

Removing the peer takes the tunnel down and, with it, every route that resolved through wg0. If your session runs over that tunnel, commit-confirm is not optional.

Production discipline

Cross-course references

  • Part XLI-01 (XLI-VyOS-WireGuard / concept) covers the protocol and where peers fit in.
  • Part XLI-02 (XLI-VyOS-WireGuard / keys) covers generate pki wireguard key-pair and the public key that populates peer NAME public-key.
  • Part XLI-04 (XLI-VyOS-WireGuard / routing) covers how allowed-ips interact with the routing table.
  • Part XLI-06 (XLI-VyOS-WireGuard / troubleshoot) covers the diagnostic flow for peer failures.
  • Part XLIII-02 (XLIII-VyOS-VPNRouting / BGP over VPN) builds iBGP across a tunnel configured exactly this way.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the role of `allowed-ips` in a WireGuard peer configuration?

  2. Q2. Persistent keepalive is required for all WireGuard peers, regardless of whether they are behind NAT or not.

  3. Q3. R1 can ping R2's tunnel address but hosts behind R1 cannot reach hosts behind R2. The handshake is recent on both routers. Where is the fault and how do you prove it?

    R1 and R2 have a WireGuard tunnel on 10.10.10.0/30. R1 has peer SITE-B with allowed-ips 10.10.10.0/30 and 192.168.20.0/24. R2 has peer SITE-A with allowed-ips 10.10.10.0/30 only — the LAN prefix behind R1, 192.168.10.0/24, was never added. Pings between 10.10.10.1 and 10.10.10.2 succeed because those addresses are inside both lists. A ping from 192.168.10.50 to 192.168.20.10 leaves R1 and is never answered, and R1 shows bytes sent rising with received flat.

  4. Q4. A remote-access client behind a home broadband NAT connects to a corporate WireGuard hub. The handshake succeeds, but after about a minute of idling the tunnel stops working. What is the most likely cause?

    The hub has a public address and UDP 51820 open. The client is behind a consumer NAT. Traffic flows immediately after connecting. After the session idles, traffic initiated from the client revives the tunnel instantly, while traffic initiated from the hub gets nowhere. The hub shows the peer with a latest handshake from a minute or two ago and no recent received bytes. No persistent-keepalive is configured on the client peer definition.

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