# 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.

Source: https://vpsnine.com/tutorials/wireguard-vpn-server-almalinux · Updated: 2026-10-21

Difficulty: intermediate · Time: about 30 minutes · Tested on: AlmaLinux 10, Rocky Linux 9

Prerequisites:

- 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](https://vpsnine.com/tutorials/wireguard-vpn-server-ubuntu) 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](https://vpsnine.com/tutorials/firewalld-almalinux-rocky)).

### 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](https://vpsnine.com/help/server-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](https://vpsnine.com/tutorials/firewalld-almalinux-rocky).
- Same setup on Ubuntu, with iptables rules instead: [WireGuard on Ubuntu 24.04](https://vpsnine.com/tutorials/wireguard-vpn-server-ubuntu).

## Doing it on VPSNine

On VPSNine, add `allow udp 51820` to the server's [panel firewall](https://vpsnine.com/help/server-firewall) if it has a deny default. WireGuard is also a [one-click app](https://vpsnine.com/apps/wireguard) when you order, if you'd rather not do this by hand. Runs on [AlmaLinux 10](https://vpsnine.com/os/almalinux-10), [AlmaLinux 9](https://vpsnine.com/os/almalinux-9) and [Rocky Linux 9](https://vpsnine.com/os/rocky-9), on every [plan](https://vpsnine.com/plans); the Budget plan is plenty for a personal VPN.
