VPN mini-HOWTO: WireGuard, OpenVPN and IPsec on Linux
A VPN joins machines or networks over the Internet through an encrypted tunnel. On Linux today the simplest choice is WireGuard, which is built into the kernel and needs a dozen lines of configuration. OpenVPN is the choice when you need TCP, certificates or very wide client support, and IPsec (strongSwan) when the other end is a firewall appliance or another vendor’s gateway.
The classic VPN mini-HOWTO ran PPP over SSH, which nobody should do any more. The Linux Documentation Project keeps a related classic VPN HOWTO at tldp.org. This page covers current tools as of November 2026: OpenVPN 2.6 (Debian 13) and 2.7 (upstream), strongSwan 6.
Which VPN to use
| WireGuard | OpenVPN | strongSwan (IPsec/IKEv2) | |
|---|---|---|---|
| Transport | UDP only (one port) | UDP or TCP (one port) | UDP 500 and 4500, ESP |
| Authentication | Public keys per peer | X.509 certificates, optional user/password | Certificates, pre-shared keys, EAP |
| Setup effort | Low | Medium (needs a small PKI) | Medium to high |
| Best for | Your own servers, laptops and phones | Restrictive networks that only allow TCP; existing OpenVPN fleets | Site-to-site links with routers and firewalls from other vendors; built-in IKEv2 clients |
WireGuard
Each WireGuard peer has a key pair. A peer’s AllowedIPs works both ways: it is the list of addresses that may arrive from that peer, and the routes sent into the tunnel towards it. There is no “connection” to start; packets flow as soon as both sides know each other’s public key and one side knows where to find the other (Endpoint).
# Debian / Ubuntu
sudo apt install wireguard-tools
# Fedora / RHEL / Alma / Rocky
sudo dnf install wireguard-tools
Make a key pair on each machine. umask 077 keeps the private key unreadable to other users:
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
Server: /etc/wireguard/wg0.conf
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>
[Peer]
# alice-laptop
PublicKey = <client-public-key>
AllowedIPs = 10.20.0.2/32
Client: /etc/wireguard/wg0.conf on the laptop
[Interface]
Address = 10.20.0.2/24
PrivateKey = <client-private-key>
[Peer]
PublicKey = <server-public-key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.20.0.0/24
PersistentKeepalive = 25
Fill in your own keys. Replace each placeholder with the contents of the privatekey or publickey file made by wg genkey on that machine: the server file holds the server’s private key and the client’s public key, the client file holds the client’s private key and the server’s public key. A private key never leaves the machine it was made on.
PersistentKeepalive = 25 sends a small packet every 25 seconds so that a home router’s NAT keeps the path open; the server does not need it. Each client gets its own [Peer] block on the server, with its own /32 address.
Bring it up
sudo chmod 600 /etc/wireguard/wg0.conf
sudo wg-quick up wg0
sudo wg show
sudo systemctl enable --now wg-quick@wg0 # start at boot (run wg-quick down wg0 first)
wg show on the server prints something like:
interface: wg0
public key: <server-public-key>
private key: (hidden)
listening port: 51820
peer: <client-public-key>
allowed ips: 10.20.0.2/32
Once the client has sent traffic, the peer also shows an endpoint, a latest handshake time and transfer counters. No handshake means the UDP port is blocked or a key is wrong. Open the port on the server: sudo firewall-cmd --permanent --add-service=wireguard and sudo firewall-cmd --reload, or sudo ufw allow 51820/udp.
Send all traffic through the tunnel
To use the server as the client’s Internet exit, set AllowedIPs = 0.0.0.0/0, ::/0 on the client, and add DNS = 10.20.0.1 if the server runs a resolver (wg-quick applies it by calling resolvconf, so a resolvconf program must be installed). wg-quick then installs policy-routing rules (with ip rule) so that the tunnel’s own packets still leave through the normal gateway. The server must forward and masquerade the traffic; see the IP Masquerade HOWTO for net.ipv4.ip_forward and the nftables or firewalld NAT rules.
OpenVPN
OpenVPN uses TLS with certificates. Build a small certificate authority with Easy-RSA on a machine you trust, ideally not the VPN server:
# Debian / Ubuntu
sudo apt install openvpn easy-rsa
cp -r /usr/share/easy-rsa ~/easy-rsa
# Fedora / RHEL (EPEL): sudo dnf install openvpn easy-rsa
# then copy /usr/share/easy-rsa/<version>/ instead
cd ~/easy-rsa
./easyrsa init-pki
./easyrsa build-ca
./easyrsa build-server-full server nopass
./easyrsa build-client-full alice nopass
openvpn --genkey tls-crypt tc.key
This creates pki/ca.crt, pki/issued/server.crt, pki/private/server.key and the same for alice. nopass leaves the server key unencrypted so the service can start unattended; keep the CA key (pki/private/ca.key) offline. tc.key is a shared key for tls-crypt, which encrypts and authenticates the control channel: it hides the certificates and makes OpenVPN traffic harder to identify, and packets made without the key are dropped.
Copy ca.crt, server.crt, server.key and tc.key to /etc/openvpn/server/ and create /etc/openvpn/server/server.conf:
port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh none
tls-crypt tc.key
topology subnet
server 10.8.0.0 255.255.255.0
keepalive 10 120
persist-tun
verb 3
explicit-exit-notify 1
dh none uses only elliptic-curve key exchange (OpenVPN 2.7 does this when dh is left out). server 10.8.0.0 255.255.255.0 gives clients addresses from 10.8.0.0/24.
sudo systemctl enable --now openvpn-server@server
sudo firewall-cmd --permanent --add-service=openvpn && sudo firewall-cmd --reload
journalctl -u openvpn-server@server -f
The unit name after @ is the config file name without .conf. A matching client file, alice.ovpn:
client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind
persist-tun
remote-cert-tls server
ca ca.crt
cert alice.crt
key alice.key
tls-crypt tc.key
verb 3
remote-cert-tls server makes the client reject a certificate that was not issued for a server, which stops one client from impersonating the server. Run it with sudo openvpn --config alice.ovpn, or put it in /etc/openvpn/client/alice.conf and use openvpn-client@alice.
IPsec with strongSwan
strongSwan 6 is configured through swanctl and /etc/swanctl/swanctl.conf; the old ipsec.conf/stroke interface is no longer enabled by default. Install it with sudo apt install charon-systemd strongswan-swanctl on Debian and Ubuntu, or sudo dnf install strongswan on Fedora. This site-to-site example links 10.1.0.0/16 behind gateway A (203.0.113.1) with 10.2.0.0/16 behind gateway B (198.51.100.1), using IKEv2 and a pre-shared key. On gateway A:
connections {
a-to-b {
version = 2
local_addrs = 203.0.113.1
remote_addrs = 198.51.100.1
local {
auth = psk
id = gw-a.example.com
}
remote {
auth = psk
id = gw-b.example.com
}
children {
net-net {
local_ts = 10.1.0.0/16
remote_ts = 10.2.0.0/16
start_action = trap
}
}
}
}
secrets {
ike-ab {
id-a = gw-a.example.com
id-b = gw-b.example.com
secret = "<pre-shared-key>"
}
}
Gateway B gets the mirror image (swap the addresses, IDs and subnets). start_action = trap brings the tunnel up when the first matching packet appears. Leaving out proposals and esp_proposals uses strongSwan’s default list of algorithms it considers safe. Replace <pre-shared-key> with a long random secret, the same on both gateways, for example from openssl rand -base64 24, or better, use certificates (auth = pubkey).
sudo systemctl enable --now strongswan
sudo swanctl --load-all
sudo swanctl --initiate --child net-net
sudo swanctl --list-sas
sudo firewall-cmd --permanent --add-service=ipsec && sudo firewall-cmd --reload
NetworkManager on desktops
- WireGuard has been native in NetworkManager since version 1.16:
nmcli connection import type wireguard file /etc/wireguard/wg0.confturns a wg-quick file into a profile you can toggle from the desktop. - OpenVPN needs the plugin (
network-manager-openvpn-gnomeon Debian and Ubuntu,NetworkManager-openvpn-gnomeon Fedora). Thennmcli connection import type openvpn file alice.ovpn. - IPsec/IKEv2 works through the NetworkManager-strongswan plugin.
Mistakes to avoid
- Overlapping subnets: if the office and the home network both use 192.168.1.0/24, routing breaks. Pick an unusual private range for the tunnel and the networks behind it.
- Forgetting forwarding and NAT on the server when clients should reach the LAN or the Internet through it.
- Two WireGuard peers with the same
AllowedIPs: the last one wins and the other loses its traffic. - Copying private keys or the Easy-RSA CA key around by email or chat. Generate keys where they are used and send only public keys and signed certificates.
- Locking yourself out over SSH by setting
AllowedIPs = 0.0.0.0/0on a remote server. Full-tunnel settings belong on clients.
Sources
- WireGuard, wireguard.com
- WireGuard quick start, wireguard.com
- wg(8), man7.org
- wg-quick(8), man7.org
- wireguard-tools package file list (wg-quick@.service), Debian 13
- OpenVPN 2.x HOWTO, openvpn.net
- OpenVPN sample server.conf and client.conf, OpenVPN project
- OpenVPN 2.7 manual sections (--genkey, --tls-crypt, --dh none), OpenVPN project
- OpenVPN releases, OpenVPN project
- Easy-RSA quick start, OpenVPN project
- openvpn package file list (openvpn-server@.service), Debian 13
- strongSwan quick start, strongswan.org
- swanctl.conf reference, strongswan.org
- swanctl tool, strongswan.org
- strongSwan 6.0.0 released (stroke no longer enabled by default), strongswan.org
- charon-systemd package file list (strongswan.service), Debian 13
- nmcli(1), NetworkManager project
- WireGuard in NetworkManager, Thomas Haller (NetworkManager developer)