How to set up a WireGuard VPN server on AlmaLinux and Rocky Linux
Install WireGuard on AlmaLinux or Rocky Linux, write wg0.conf, route clients through the server with firewalld masquerading, and connect your devices.
Tested on AlmaLinux 10, Rocky Linux 9
Before you start
- A server running AlmaLinux 9 or 10 or Rocky Linux 9 with a public IPv4 address
- Root or a user with sudo
- firewalld running (see the firewalld tutorial)
- A laptop or phone with the WireGuard app
To set up a WireGuard VPN server on AlmaLinux or Rocky Linux, install wireguard-tools with dnf, create a key pair, write /etc/wireguard/wg0.conf, turn on IP forwarding, and let firewalld do the rest: open UDP 51820, turn on masquerading in the public zone, and put wg0 in the trusted zone. Then add one [Peer] per device.
The difference from our Ubuntu WireGuard tutorial is the firewall: on RHEL-family systems, firewalld handles the NAT, so wg0.conf needs no PostUp iptables rules. We ran this on AlmaLinux 10 with firewalld 2.4 and connected a Debian 13 client through it: the handshake completed, the client reached the server at 10.8.0.1, and its internet traffic went out through the server. wireguard-tools installs the same way on Rocky Linux 9.
The examples use these addresses. Replace them with yours:
| What | In this tutorial |
|---|---|
| The server's public IPv4 address | 203.0.113.10 |
| The tunnel network | 10.8.0.0/24 and fd08::/64, server 10.8.0.1 and fd08::1 |
| The laptop | 10.8.0.2 and fd08::2 |
| The phone | 10.8.0.3 and fd08::3 |
Install WireGuard
The WireGuard kernel module is part of the AlmaLinux and Rocky Linux kernels; the tools are in AppStream:
sudo dnf install -y wireguard-tools
wg --versionFor QR codes for phones later, qrencode comes from EPEL:
sudo dnf install -y epel-release
sudo dnf install -y qrencodeCreate the server's keys
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
wg genkey | tee laptop.key | wg pubkey > laptop.pubStay in this root shell for the next steps. umask 077 makes the key files readable by root only. The second line makes the laptop's key pair now, so its config can be written in one go.
Write the server config
Create /etc/wireguard/wg0.conf, with the contents of server.key after PrivateKey and of laptop.pub in the [Peer]:
[Interface]
Address = 10.8.0.1/24, fd08::1/64
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
[Peer]
# laptop
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = 10.8.0.2/32, fd08::2/128There are no PostUp or PostDown lines: firewalld takes care of forwarding and NAT, and keeps those rules across reboots and firewall-cmd --reload. Mixing iptables commands with firewalld tends to end with one silently undoing the other.
Turn on IP forwarding
cat > /etc/sysctl.d/99-wireguard.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --systemConfigure firewalld
Three changes: let WireGuard's port in, masquerade traffic leaving through the public interface, and trust traffic arriving from the tunnel:
firewall-cmd --permanent --zone=public --add-port=51820/udp
firewall-cmd --permanent --zone=public --add-masquerade
firewall-cmd --permanent --zone=trusted --add-interface=wg0
firewall-cmd --reloadPutting wg0 in trusted means your VPN devices can reach every port on the server, which is usually what you want from a private network. If you'd rather limit them, create a zone for wg0 with only the services they need, and add masquerading and forwarding to it instead (see firewalld zones).
Check it worked
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-alltrusted lists interfaces: wg0, and the public zone shows ports: 51820/udp and masquerade: yes.
Start the tunnel
systemctl enable --now wg-quick@wg0
wg showwg show lists wg0 listening on 51820 with the laptop's peer. If wg-quick fails, journalctl -u wg-quick@wg0 shows which line it didn't like.
Write the laptop's config
Create laptop.conf in /etc/wireguard, with the contents of laptop.key and server.pub:
[Interface]
PrivateKey = LAPTOP_PRIVATE_KEY
Address = 10.8.0.2/32, fd08::2/128
[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs = 0.0.0.0/0, ::/0 sends all the laptop's traffic through the server. To reach only the server and other VPN devices, use AllowedIPs = 10.8.0.0/24, fd08::/64.
Copy it to the laptop (scp root@203.0.113.10:/etc/wireguard/laptop.conf .) and import it: Import tunnel(s) from file in the WireGuard app on Windows and macOS, or sudo wg-quick up ./laptop.conf on Linux. Delete the downloaded copy afterwards; it contains the laptop's private key.
Add a phone
Make its keys, add a [Peer] to wg0.conf with AllowedIPs = 10.8.0.3/32, fd08::3/128, and write phone.conf like laptop.conf with Address = 10.8.0.3/32, fd08::3/128. Apply the new peer without dropping existing connections:
cd /etc/wireguard
wg genkey | tee phone.key | wg pubkey > phone.pub
# edit wg0.conf and write phone.conf, then:
wg syncconf wg0 <(wg-quick strip wg0)Show phone.conf as a QR code and scan it in the WireGuard app:
qrencode -t ansiutf8 < /etc/wireguard/phone.confRun clear afterwards; the code holds the phone's private key.
Check that it works
With the laptop connected:
wg show wg0 latest-handshakesOn the server, each connected peer has a recent timestamp. On the laptop:
ping -c 3 10.8.0.1
curl -4 https://icanhazip.comThe ping answers, and the second command prints the server's address, 203.0.113.10.
Troubleshooting
No handshake. The packets don't arrive. Check firewall-cmd --list-ports shows 51820/udp, that the panel firewall (if you use one) allows udp 51820, and that the Endpoint is right.
Handshake, but no internet through the tunnel. Forwarding or NAT is missing. sysctl net.ipv4.ip_forward must print 1; firewall-cmd --zone=public --query-masquerade must print yes; and firewall-cmd --get-zone-of-interface=wg0 must print trusted (or your own zone).
wg-quick: 'wg0' already exists. The tunnel is already up. Use systemctl restart wg-quick@wg0 after editing the interface settings, or wg syncconf for peer changes.
wg0 ends up in the public zone after a reboot. The zone change was made without --permanent. Repeat it with --permanent and reload.
Next steps
- Limit SSH to the VPN once it works: add
10.8.0.0/24as a source of an admin zone, as in firewalld. - Same setup on Ubuntu, with iptables rules instead: WireGuard on Ubuntu 24.04.
Doing it on VPSNine
On VPSNine, add allow udp 51820 to the server's panel firewall if it has a deny default. WireGuard is also a one-click app when you order, if you'd rather not do this by hand. Runs on AlmaLinux 10, AlmaLinux 9 and Rocky Linux 9, on every plan; the Budget plan is plenty for a personal VPN.