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:

bash
sudo dnf install -y wireguard-tools
wg --version

For QR codes for phones later, qrencode comes from EPEL:

bash
sudo dnf install -y epel-release
sudo dnf install -y qrencode

Create the server's keys

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

Stay 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]:

ini
[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/128

There 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

bash
cat > /etc/sysctl.d/99-wireguard.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system

Configure firewalld

Three changes: let WireGuard's port in, masquerade traffic leaving through the public interface, and trust traffic arriving from the tunnel:

bash
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 --reload

Putting 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

bash
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all

trusted lists interfaces: wg0, and the public zone shows ports: 51820/udp and masquerade: yes.

Start the tunnel

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

wg 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:

ini
[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 = 25

AllowedIPs = 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:

bash
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:

bash
qrencode -t ansiutf8 < /etc/wireguard/phone.conf

Run clear afterwards; the code holds the phone's private key.

Check that it works

With the laptop connected:

bash
wg show wg0 latest-handshakes

On the server, each connected peer has a recent timestamp. On the laptop:

bash
ping -c 3 10.8.0.1
curl -4 https://icanhazip.com

The 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/24 as 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.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.

  • One email, on launch day
  • Monthly or yearly, no lock-in
  • 14-day money-back guarantee