Network and VPN

WireGuard: a secure tunnel between the office and the server

Contents

For an administrator who wants RDP, SSH and databases on the server to be reachable only by the company’s staff, not the whole internet. The result is an encrypted WireGuard tunnel between the office (or individual computers and phones) and the server, so the service ports can be closed to the outside.

WireGuard has no “server” and “client”, only equal peers, each with a key pair and an AllowedIPs list. The peer with a fixed public address (your server) listens on a UDP port, and the others (clients below) connect to it themselves.

What you will need#

  • A server running Ubuntu LTS (24.04 or 26.04) or Debian 13 and an account with sudo rights. Support sends the server’s public address with the access details.
  • A private subnet for the tunnel. It must not overlap with the office network, staff home networks (most often 192.168.0.0/24 and 192.168.1.0/24) or the private network between servers, if you use one.
  • A free UDP port on the server; the usual one for WireGuard is 51820.

All addresses here are examples; replace them with your own: the server’s public address is 203.0.113.10, the tunnel subnet is 10.66.0.0/24 (server 10.66.0.1, laptop 10.66.0.2, phone 10.66.0.3, office router 10.66.0.10) and the office network is 192.168.10.0/24.

Step 1 A server running Ubuntu or Debian#

Installation and keys#

The WireGuard module is already in the kernel; the package adds the wg and wg-quick tools.

sudo apt update
sudo apt install wireguard

Create the keys in a root shell with umask 077, so only root can read the new files:

sudo -i
umask 077
cd /etc/wireguard
wg genkey | tee server.key | wg pubkey > server.pub
cat server.pub

The public key in server.pub goes to the clients; the private key stays on the server only.

The /etc/wireguard/wg0.conf file#

Still in the root shell, create the file with any editor, for example nano /etc/wireguard/wg0.conf:

[Interface]
Address = 10.66.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY

Replace SERVER_PRIVATE_KEY with the contents of server.key (cat server.key prints it). Add [Peer] sections once you have the clients’ public keys. Leave the root shell with exit.

Firewall and starting the tunnel#

If ufw is installed on the server, open the UDP port. The command only adds a rule and does not switch the firewall on. If ufw is still disabled, do not enable it until you have allowed SSH — see “Basic Linux server hardening”. If you use nftables, add the rule udp dport 51820 accept to the input chain.

sudo ufw allow 51820/udp

Start the tunnel and enable it at boot; wg show should list the wg0 interface and port 51820:

sudo systemctl enable --now wg-quick@wg0
sudo wg show

Step 2 Computers and phones#

Official apps: for Windows, the installer from wireguard.com/install; for macOS and iOS, the App Store; for Android, Google Play.

A computer#

  1. In the app choose “Add Tunnel” → “Add empty tunnel…” (on macOS, “+” → “Add Empty Tunnel…”). The app generates a key pair: the private key is already in the configuration text and the public key is shown above it.
  2. Complete the configuration and save it:
    [Interface]
    PrivateKey = LAPTOP_PRIVATE_KEY
    Address = 10.66.0.2/32
    
    [Peer]
    PublicKey = SERVER_PUBLIC_KEY
    Endpoint = 203.0.113.10:51820
    AllowedIPs = 10.66.0.0/24
    PersistentKeepalive = 25
  3. On the server, open the file with sudo nano /etc/wireguard/wg0.conf and append a section with this computer’s public key:
    [Peer]
    PublicKey = LAPTOP_PUBLIC_KEY
    AllowedIPs = 10.66.0.2/32
  4. Apply the change without dropping other connections, then press “Activate” in the app:
    sudo systemctl reload wg-quick@wg0

On a client, AllowedIPs defines which traffic goes into the tunnel: here only the tunnel subnet, so the rest of the internet works as before. PersistentKeepalive = 25 is needed by peers behind NAT: every 25 seconds the peer sends an empty packet so the router does not “forget” the connection.

A ready-made configuration file can be imported: “Add Tunnel” → “Import tunnel(s) from file…”. The file contains the private key, so delete it after the import.

A phone: configuration by QR code#

Create the configuration on the server and print the QR code in the terminal:

sudo apt install qrencode
sudo -i
umask 077
mkdir -p /root/wg-clients
cd /root/wg-clients
wg genkey | tee phone.key | wg pubkey > phone.pub
cat phone.key
nano phone.conf
qrencode -t ansiutf8 < phone.conf

The phone.conf file is the same as the computer’s, but with the address 10.66.0.3/32 and the private key from phone.key. In the app press “+” and scan the QR code, then delete the files with the private key and print the public one:

rm phone.key phone.conf
cat phone.pub
exit

Add the public key to a new [Peer] section on the server with AllowedIPs = 10.66.0.3/32 and run reload again. The QR code contains the private key: do not keep screenshots of it or send it through messengers.

Step 3 The whole office network#

To avoid installing the app on every computer, the tunnel is set up on the office router, if it supports WireGuard (for example, with OpenWrt firmware). Every device in the office then reaches the server at 10.66.0.1 with no further configuration. Add a peer on the server:

[Peer]
PublicKey = ROUTER_PUBLIC_KEY
AllowedIPs = 10.66.0.10/32, 192.168.10.0/24

The office network in AllowedIPs means the server accepts packets from office computers and replies to them through this peer. This time run sudo systemctl restart wg-quick@wg0 (connections drop for a moment): wg-quick adds the route to a new subnet only when it starts, and reload will not create it.

On the router (field names depend on the model) set the interface address to 10.66.0.10/24, the peer’s public key to the contents of server.pub, Endpoint to 203.0.113.10:51820, AllowedIPs to 10.66.0.0/24 and PersistentKeepalive to 25. The router generates its own key pair; copy its public key to the server. The router’s firewall must allow forwarding from the local network into the tunnel, and NAT towards the tunnel is not needed: the server knows the route to the office network.

If the router does not support WireGuard, a Linux computer in the office can be the tunnel peer with the same settings. On the router add a static route to 10.66.0.0/24 via that computer’s address, and on the computer itself enable packet forwarding:

echo net.ipv4.ip_forward=1 | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system

On a Linux server, forwarding is enabled in the same way, but only if the server has to pass packets between peers — for example, so an employee at home can reach the office network through it. Then add 192.168.10.0/24 to the client’s AllowedIPs and allow transit in ufw on the server: sudo ufw route allow in on wg0 out on wg0.

A server running Windows Server#

WireGuard for Windows supports Windows Server 2016, 2019, 2022 and 2025. No separate “server” mode is needed: the server becomes an ordinary tunnel peer with its own address, and you connect to the remote desktop at that address.

  1. Install the app with the official installer, choose “Add Tunnel” → “Add empty tunnel…” and name the tunnel wg0. The app fills in the private key; add the rest:
    [Interface]
    PrivateKey = SERVER_PRIVATE_KEY
    ListenPort = 51820
    Address = 10.66.0.1/24
    
    [Peer]
    PublicKey = LAPTOP_PUBLIC_KEY
    AllowedIPs = 10.66.0.2/32
  2. Press “Activate”. The tunnel runs as the Windows service WireGuardTunnel$wg0 and starts with the system, before any user signs in. Check this in a new PowerShell window run as administrator:
    Get-Service WireGuardTunnel* | Select-Object Name, Status, StartType
    wg show
  3. Open the inbound UDP port in the Windows firewall:
    New-NetFirewallRule -DisplayName "WireGuard UDP 51820" -Direction Inbound -Protocol UDP -LocalPort 51820 -Action Allow
  4. Set up the tunnel on the client as in step 2 and connect to the remote desktop at 10.66.0.1.

How to check the result#

  1. Run sudo wg show on the server. Each peer should have an external address, the latest handshake time and traffic counters:
    peer: LAPTOP_PUBLIC_KEY
      endpoint: 198.51.100.7:53211
      allowed ips: 10.66.0.2/32
      latest handshake: 37 seconds ago
      transfer: 1.21 MiB received, 3.48 MiB sent
  2. Run ping 10.66.0.1 from the client. Windows Server may not answer ping until its firewall allows it; then rely on the handshake and RDP.
  3. Watch the handshake time: with PersistentKeepalive it is renewed roughly every two minutes. If there is no latest handshake line, the peers have never connected; if the handshake is more than three minutes old, the link is down.

Step 4 Service ports through the tunnel only#

Warning. The commands in this section can cut you off from the server. Run them only after the tunnel has been checked, including after a reboot, and keep your current session open. If access is lost anyway, you will need the remote console: it is provided on request through support, and the Lite line does not have it.

Linux with ufw enabled. First allow SSH from the tunnel (if the SSH port is not 22, use your own) and look at the rule numbers:

sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw status numbered

Open a new SSH session to 10.66.0.1. If it works, delete the rules that allow SSH from everywhere (usually two, for IPv4 and IPv6): sudo ufw delete and the rule number; the numbers shift after each deletion. To roll back, run sudo ufw allow 22/tcp in the old session, which stays connected. Treat database ports the same way.

Windows Server. Connect by RDP to 10.66.0.1 and, from that session, restrict the built-in remote desktop rules to the tunnel subnet:

Set-NetFirewallRule -DisplayGroup "Remote Desktop" -RemoteAddress 10.66.0.0/24

Check a new connection without closing this session. To roll back, allow any address again:

Set-NetFirewallRule -DisplayGroup "Remote Desktop" -RemoteAddress Any

The group name “Remote Desktop” is given for an English-language system. If the RDP port has been changed or the rules were created by hand, see “Secure RDP”; for Linux, see “Basic Linux server hardening”.

Common mistakes#

SymptomCauseWhat to do
No handshakeThe UDP port is closed on the server, the client’s network blocks outbound UDP, or Endpoint is wrongCheck the firewall rule and sudo wg show wg0 listen-port; try connecting from another network
The port is open but there is no handshakeKeys are mixed up: [Interface] takes the peer’s own private key, [Peer] the public key of the opposite peerCompare sudo wg show wg0 public-key on the server with PublicKey on the client
After a peer was added, another one stopped respondingOverlapping AllowedIPs: the same address or subnet is given to two peers, and WireGuard keeps it only for the last oneGive every peer on the server its own /32 address; list the office network for the router only
ping works but RDP or file copying hangsMTU: large packets do not get through the link (mobile internet, PPPoE, another VPN)Add MTU = 1380 to the client’s [Interface]; lower it further if needed, but not below 1280

MTU is tested with a packet that must not be fragmented: on Windows ping -f -l 1392 10.66.0.1, on Linux ping -M do -s 1392 10.66.0.1. If small packets pass and this one does not, lower the MTU. If the tunnel fails to start, sudo systemctl status wg-quick@wg0 shows why.

What next#