m4nifest0-tech

Search the site

Networking

WireGuard: remote access and site-to-site VPN without the complications

A guide to WireGuard on Debian: keys, server and client configuration, IP forwarding, a site-to-site VPN between two sites and good peer management practices.
Published on 7 min readLevel: Intermediate

WireGuard is a VPN built into the Linux kernel, with a configuration reduced to a few text files: each device has a key pair, lists the peers it talks to and declares which addresses it can reach through each one. The protocol makes no distinction between server and client: the "server" is simply the peer with a reachable address. The two most common scenarios are a device's remote access to the lab network and a permanent link between two sites; both are described here for Debian 12 and 13.

How WireGuard thinks

The central concept is AllowedIPs, which does two jobs at once for each peer. Outbound, it is a routing table: a packet destined to an address in the list is encrypted and sent to that peer. Inbound, it is a filter: a decrypted packet whose source address does not belong to the list of the peer that sent it is dropped. The list must therefore be written precisely, not as a simple set of networks to reach. In addition, WireGuard does not answer packets that do not come from an authenticated peer: toward the Internet you only need to expose a single UDP port, which reveals no service.

Installation and keys

WireGuard support is in the Debian 12 and 13 kernel, so only the tools package is needed. Each device generates its own key pair on the device itself: the private key must never leave it.

On the server (same procedure on every device)
apt install wireguard-tools
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub

umask 077 makes the files readable only by the owner. The contents of server.key go in the PrivateKey field of the configuration; server.pub is the only key to share with the other peers. In the examples below, values in angle brackets are placeholders.

Important

A private key is never pasted into chat, tickets, repositories or documentation, and is never shared between devices: compromising a shared key forces you to replace it everywhere.

Remote access

In the example the lab network is 192.0.2.0/24, the tunnel uses 203.0.113.0/24 and the laptop has the address 203.0.113.2. The server is reachable as vpn.example.com.

/etc/wireguard/wg0.conf on the server
[Interface]
Address = 203.0.113.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
 
# example laptop
[Peer]
PublicKey = <PC_PUBLIC_KEY>
AllowedIPs = 203.0.113.2/32
/etc/wireguard/wg0.conf on the laptop
[Interface]
Address = 203.0.113.2/32
PrivateKey = <PC_PRIVATE_KEY>
 
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = vpn.example.com:51820
AllowedIPs = 203.0.113.1/32, 192.0.2.0/24
PersistentKeepalive = 25
  • On the server, AllowedIPs = 203.0.113.2/32 is the only source address accepted from that peer: a compromised device cannot impersonate another.
  • On the client, AllowedIPs determines what goes into the tunnel: here the server's address and the lab network (split tunnel). With 0.0.0.0/0 all traffic would pass through and wg-quick would also set the default route.
  • PersistentKeepalive = 25 sends a packet every 25 seconds to keep the NAT mapping open. It is needed by a peer that sits behind a NAT and must be able to receive traffic; by default it is off.
  • UDP port 51820 is only a convention: you choose it with ListenPort.

Note

The name in Endpoint is resolved when the interface is configured, not on every packet. If the server's public address changes (dynamic IP), the client must restart the tunnel or have a re-resolution mechanism.

Starting, forwarding and verifying

For a manual test use wg-quick up wg0; the configuration file must be readable only by root, otherwise wg-quick warns about it.

On the server: manual test
chmod 600 /etc/wireguard/wg0.conf
wg-quick up wg0

For automatic startup, bring the interface down and enable the systemd unit, which runs the same wg-quick up at every boot. The two methods must not be combined on the same interface.

On the server: automatic startup
wg-quick down wg0
systemctl enable --now wg-quick@wg0

For the server to forward packets between the tunnel and the local network, IPv4 forwarding is required.

/etc/sysctl.d/99-wireguard.conf
net.ipv4.ip_forward = 1
On the server
sysctl --system

Devices on the local network must know how to reply to the tunnel addresses: either the WireGuard server is their default gateway, or a static route to 203.0.113.0/24 is added on the router. Alternatively the server can masquerade (NAT) the tunnel traffic, at the cost of losing the client's real address in the logs. Forwarding and NAT rules belong to the host's firewall and should be limited to what is necessary; on the perimeter firewall you forward only the chosen UDP port.

On the server
wg show
Example output (abbreviated)
interface: wg0
  public key: <SERVER_PUBLIC_KEY>
  listening port: 51820
 
peer: <PC_PUBLIC_KEY>
  endpoint: 198.51.100.77:41234
  allowed ips: 203.0.113.2/32
  latest handshake: 12 seconds ago

If latest handshake does not appear, the peer has never completed the exchange: check the public keys, the reachability of the UDP port and the Endpoint.

Site-to-site VPN

Two sites are linked with the same mechanism, giving the remote networks as AllowedIPs. In the example site A has a stable public address and site B sits behind a NAT. Site A's network is 192.0.2.0/24, site B's is 198.51.100.0/24, and the tunnel is 203.0.113.0/30.

Site A: /etc/wireguard/wg0.conf
[Interface]
Address = 203.0.113.1/30
ListenPort = 51820
PrivateKey = <SITE_A_PRIVATE_KEY>
 
[Peer]
PublicKey = <SITE_B_PUBLIC_KEY>
AllowedIPs = 203.0.113.2/32, 198.51.100.0/24
Site B: /etc/wireguard/wg0.conf
[Interface]
Address = 203.0.113.2/30
PrivateKey = <SITE_B_PRIVATE_KEY>
 
[Peer]
PublicKey = <SITE_A_PUBLIC_KEY>
Endpoint = site-a.example.com:51820
AllowedIPs = 203.0.113.1/32, 192.0.2.0/24
PersistentKeepalive = 25

Site B initiates the connection; site A learns its address from the packets it receives, which is why it does not need an Endpoint. For the link to work:

  • the two local networks have different, non-overlapping subnets;
  • each peer's AllowedIPs includes the other site's network;
  • both gateways enable net.ipv4.ip_forward;
  • the hosts at each site know the route to the remote network (default gateway or a static route on the router).

Warning

A tunnel between sites does not mean mutual trust: without filters every device at one site reaches all those at the other. Allow only the flows that are needed. The tunnel is, for example, the right channel for replicating backups to the remote site (see 3-2-1 backup strategy with Proxmox Backup Server).

Best practices

  • One key per device. Each device has a dedicated pair and a [Peer] block with a comment identifying it.
  • Revocation. Delete the device's block from the file and apply the change without interrupting the other tunnels with wg syncconf wg0 <(wg-quick strip wg0).
  • Rotation. Generate a new pair on the device, update the public key on the counterpart and remove the old one; do it at every device replacement and at any suspicion of compromise. A preshared key per pair of peers (wg genpsk, PresharedKey field) adds an extra symmetric layer.
  • Least privilege. VPN clients reach only the destinations they need, with rules on the wg0 interface and, ideally, in a dedicated segment (see Segmenting the network with VLANs).
  • Minimal exposure. Only the UDP port stays open toward the Internet; SSH and administration interfaces are reached only through the tunnel (see the Debian SSH hardening checklist).
  • Monitoring. wg show and the latest handshake value tell you whether a peer is active. Configuration files have 600 permissions and their copies should be encrypted, because they contain the private key.

Related articles