WireGuard VPN
Applies to CentOS Stream 9 & 10 / AlmaLinux 9.x & 10.x / Rocky Linux 9.x & 10.x
A VPN lets remote clients reach a private network — or route all their traffic through a trusted gateway — over an encrypted tunnel. On modern Enterprise Linux, WireGuard is the fastest path to a working tunnel: the kernel module ships in-tree, the configuration is a handful of lines, and the cryptography is fixed and modern. This guide walks through a road-warrior setup (remote clients dialing into a central server) and then covers IPsec/libreswan as an alternative for site-to-site deployments.
What you’ll learn
Section titled “What you’ll learn”- How to install
wireguard-toolsand generate key pairs on the server and each client - How to write a server
wg0.confwith NAT masquerade and a matching client config - How to enable kernel IP forwarding and open the right firewalld ports
- How to bring the tunnel up persistently with systemd and verify the handshake
- When to reach for IPsec/libreswan instead
Prerequisites
Section titled “Prerequisites”- A server reachable on a public IP (or a forwarded UDP port) running EL 9 or EL 10
sudo/root access on the server and each client- firewalld and NetworkManager active (the defaults on these distributions)
- EPEL configured only if
wireguard-toolsis not in your base repositories
How WireGuard works
Section titled “How WireGuard works”WireGuard creates a virtual network interface (wg0) backed by a small set of static keys. Each side holds a private key and shares its public key with the other. Peers are identified solely by their public key, and AllowedIPs decides which tunnel addresses (and, for full-tunnel clients, which destinations) are routed through the interface. There are no user accounts, certificates, or negotiation phases to debug — if the keys and AllowedIPs line up and UDP can flow, the tunnel forms.
In this guide the server uses the tunnel subnet 10.10.0.0/24, with the server at 10.10.0.1 and the first client at 10.10.0.2.
Install WireGuard
Section titled “Install WireGuard”The kernel module is in-tree on modern EL kernels, so you only need the userspace tooling.
$ sudo dnf install wireguard-toolsGenerate keys
Section titled “Generate keys”Run this on the server and again on each client. The commands write a private key and derive its public key in one shot.
$ umask 077$ wg genkey | tee privatekey | wg pubkey > publickeyumask 077 ensures the private key is created without group or world access. Inspect the two files:
$ cat privatekey$ cat publickeyConfigure the server and client
Section titled “Configure the server and client”Each side needs its own config. The server config defines the interface plus one [Peer] block per client; the client config defines its own interface plus a single [Peer] block pointing back at the server.
Create /etc/wireguard/wg0.conf on the server. Replace <SERVER_PRIVATE_KEY> with the server’s private key and <CLIENT_PUBLIC_KEY> with the client’s public key. Set PostUp/PostDown so the server masquerades client traffic out of its public interface (eth0 below — adjust to match your NIC).
[Interface]Address = 10.10.0.1/24PrivateKey = <SERVER_PRIVATE_KEY>ListenPort = 51820
PostUp = firewall-cmd --zone=public --add-masqueradePostUp = firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.10.0.0/24 -o eth0 -j MASQUERADEPostDown = firewall-cmd --zone=public --remove-masqueradePostDown = firewall-cmd --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.10.0.0/24 -o eth0 -j MASQUERADE
[Peer]# First clientPublicKey = <CLIENT_PUBLIC_KEY>AllowedIPs = 10.10.0.2/32On the server, AllowedIPs is a routing/whitelist setting: it lists exactly which tunnel addresses belong to that peer. Give each client a unique /32. Lock the file down:
$ sudo chmod 600 /etc/wireguard/wg0.confCreate /etc/wireguard/wg0.conf on the client. Replace <CLIENT_PRIVATE_KEY> with the client’s private key, <SERVER_PUBLIC_KEY> with the server’s public key, and <SERVER_PUBLIC_IP> with the server’s reachable address.
[Interface]Address = 10.10.0.2/24PrivateKey = <CLIENT_PRIVATE_KEY>
[Peer]PublicKey = <SERVER_PUBLIC_KEY>Endpoint = <SERVER_PUBLIC_IP>:51820AllowedIPs = 10.10.0.0/24PersistentKeepalive = 25The AllowedIPs = 10.10.0.0/24 above is a split tunnel: only traffic destined for the VPN subnet goes through WireGuard. To route all the client’s traffic through the server (a full tunnel), set:
AllowedIPs = 0.0.0.0/0, ::/0With a full tunnel, also add a DNS = line under [Interface] pointing to a resolver that is actually reachable through the tunnel — either a public resolver (for example DNS = 1.1.1.1) or a DNS server you run on the VPN host — otherwise name resolution leaks outside the tunnel. The split-tunnel example above omits DNS on purpose: it only routes the VPN subnet, so the client keeps its normal resolver.
PersistentKeepalive = 25 keeps the tunnel alive through NAT and stateful firewalls on the client side; it is useful when the client sits behind home/office NAT.
Enable IP forwarding (server)
Section titled “Enable IP forwarding (server)”The server only routes client traffic if the kernel forwards packets between interfaces. Set this persistently with a drop-in under /etc/sysctl.d/.
$ sudo tee /etc/sysctl.d/99-wireguard-forward.conf <<'EOF'net.ipv4.ip_forward = 1net.ipv6.conf.all.forwarding = 1EOF$ sudo sysctl --systemConfirm it is active:
$ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1Open the firewall (server)
Section titled “Open the firewall (server)”WireGuard listens on UDP 51820. Open that port and enable masquerade so client traffic can NAT out of the server.
$ sudo firewall-cmd --permanent --add-port=51820/udp$ sudo firewall-cmd --permanent --add-masquerade$ sudo firewall-cmd --reloadBring the tunnel up
Section titled “Bring the tunnel up”For a quick test, bring the interface up by hand. wg-quick reads /etc/wireguard/wg0.conf and configures the interface, addresses, routes, and the PostUp rules in one step.
$ sudo wg-quick up wg0To stop it again:
$ sudo wg-quick down wg0Once it works, make it persistent with the bundled systemd template unit so the tunnel comes back after a reboot:
$ sudo systemctl enable --now wg-quick@wg0Run the same enable command (with each host’s own config) on both the server and the clients.
Verify the handshake
Section titled “Verify the handshake”Check tunnel state with wg show. A successful connection reports a recent handshake and a non-zero transfer count.
$ sudo wg showinterface: wg0 public key: <SERVER_PUBLIC_KEY> private key: (hidden) listening port: 51820
peer: <CLIENT_PUBLIC_KEY> endpoint: 203.0.113.55:48124 allowed ips: 10.10.0.2/32 latest handshake: 18 seconds ago transfer: 1.21 KiB received, 980 B sentFrom the client, ping the server’s tunnel address to confirm end-to-end connectivity:
$ ping -c 3 10.10.0.1If you see a latest handshake and traffic counters increase, the tunnel is up.
Full setup at a glance
Section titled “Full setup at a glance”-
Install
wireguard-toolson the server and every client. -
Generate a key pair on each host with
wg genkey | tee privatekey | wg pubkey > publickey. -
Write the server
/etc/wireguard/wg0.confwith the server private key and one[Peer]block (client public key,AllowedIPs = 10.10.0.2/32). -
Write each client
/etc/wireguard/wg0.confwith its private key and a[Peer]block (server public key,Endpoint, andAllowedIPs). -
Enable IP forwarding on the server via
/etc/sysctl.d/andsudo sysctl --system. -
Open
51820/udpand add masquerade in firewalld, then reload. -
Run
sudo systemctl enable --now wg-quick@wg0on every host and verify withsudo wg show.
IPsec alternative (libreswan)
Section titled “IPsec alternative (libreswan)”WireGuard is the right default for most road-warrior and modern site-to-site links. Reach for IPsec when you need interoperability with appliances and clouds that speak standard IKEv2, or when policy mandates an IPsec-only VPN. On Enterprise Linux the supported implementation is libreswan.
$ sudo dnf install libreswan$ sudo systemctl enable --now ipseclibreswan supports both site-to-site tunnels (two gateways joining their subnets) and road-warrior setups (remote clients dialing into a gateway, typically over IKEv2). Connections are defined in /etc/ipsec.conf and /etc/ipsec.d/*.conf, with secrets in /etc/ipsec.secrets. The firewall must allow UDP 500 and 4500 plus the ESP protocol; SELinux ships with the policy libreswan needs.
$ sudo firewall-cmd --permanent --add-service=ipsec$ sudo firewall-cmd --reloadTroubleshooting
Section titled “Troubleshooting”No handshake at all. This is almost always a connectivity or key issue. Work through:
- UDP port reachability. Confirm
51820/udpis open on the server (sudo firewall-cmd --list-ports) and on any upstream router/cloud security group. WireGuard is UDP-only — a TCP-only rule will not work. - IP forwarding. If the handshake succeeds but clients cannot reach the wider network, re-check
sysctl net.ipv4.ip_forwardreturns1on the server. AllowedIPsmismatch. On the server, each peer’sAllowedIPsmust include that client’s tunnel/32. On the client,AllowedIPsmust include the subnets you expect to reach. A missing range silently drops the traffic.- Clock skew. WireGuard’s handshake is time-sensitive; a badly wrong system clock on either side breaks it. Ensure
chronydis running and synced (chronyc tracking). - Wrong public key or endpoint. Double-check you exchanged public (not private) keys, and that the client
Endpointpoints at the server’s reachable address and port.
Routing or NAT problems. If clients connect but cannot browse the internet through the server (full tunnel), confirm masquerade is enabled (sudo firewall-cmd --query-masquerade) and that the PostUp masquerade rule references the server’s actual outbound interface name.
DNS leaks. With AllowedIPs = 0.0.0.0/0, set a DNS entry in the client [Interface] that points to a resolver reachable through the tunnel. Without it, queries may still go to the client’s local DNS server, leaking which sites you visit even though web traffic flows through the VPN.
Further reading
Section titled “Further reading”- SSH Hardening — secure the remote access that often sits behind a VPN
- Firewalld Advanced — rich rules, zones, and masquerade in depth
- Firewalld Basics — zones, services, and ports fundamentals