WireGuard Port Forwarding and Routing on RHEL VPS

What this tutorial solves
WireGuard port forwarding is useful when a VPS needs to pass traffic for a VPN tunnel, a private service, or a remote site behind it. On a RHEL, Oracle Linux, or Fedora Server VPS, the tunnel is usually the easy part. The harder work is keeping forwarding active after reboots, handling SELinux checks, and setting firewalld rules without cutting off SSH.
This tutorial walks through a complete production setup on RHEL-compatible systems. You will create a non-root admin, install WireGuard tools, enable IPv4 forwarding, open the UDP port, add NAT rules, test the tunnel, verify forwarding, and roll back cleanly if something goes wrong. If you want a smaller VPS for a private routing node, a Hostperl VPS gives you a straightforward place to run it with predictable networking and support when you need to check routes or firewall behavior.
This guide uses RHEL-family commands only, because the workflow depends on dnf, firewalld, and SELinux-aware troubleshooting. For more Hostperl networking guidance, see IPv4 and IPv6 troubleshooting for Hostperl VPS users and RHEL network diagnostics on a VPS.
Before you begin
- Supported systems: RHEL 9, Oracle Linux 9, AlmaLinux 9, Rocky Linux 9, and Fedora Server 40+.
- You need root SSH access to the VPS.
- You need a second terminal or another device for verification.
- This guide assumes the public interface is managed by firewalld.
Connect and confirm the operating system
On your local computer, open the first SSH session:
ssh root@203.0.113.10203.0.113.10 is a reserved documentation address. Replace it with the public IP assigned to your VPS. If your provider gives you a default non-root account, connect with that account instead and use the same IP.
On the VPS as root, identify the distribution before changing anything:
cat /etc/os-releaseYou should see a RHEL-compatible release such as Rocky Linux, AlmaLinux, Oracle Linux, or Fedora Server. Keep this session open until the new admin login is confirmed.
Create a non-root admin first
Do not make networking changes only as root. Create a sudo-capable admin so you have a safer recovery path if the SSH session drops.
On the VPS as root, create the account, set a password, and add it to the wheel group:
useradd -m -s /bin/bash deploy
passwd deploy
usermod -aG wheel deployNow prepare SSH access for that user:
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysIf your root account uses key-based login, the copy command moves the key into the new account. If not, paste your public key into /home/deploy/.ssh/authorized_keys manually. The file must be owned by deploy and not readable by anyone else.
On your local computer, open a second SSH session and test the new account:
ssh deploy@203.0.113.10Replace 203.0.113.10 with your server IP. After login, confirm sudo works:
sudo -v
sudo whoami
pwdYou should see root from sudo whoami. Leave the original root session open until this test passes.
Install WireGuard and the routing tools
On the VPS as the non-root sudo user, install the packages needed for routing and persistence. Package names are the same across the supported RHEL-family releases, so use the matching branch for your system.
RHEL, AlmaLinux, Rocky Linux, Oracle Linux
sudo dnf install -y wireguard-tools firewalld nftables policycoreutils-python-utils iproute
sudo systemctl enable --now firewalldwireguard-tools provides wg and wg-quick. policycoreutils-python-utils gives you semanage, which is useful if SELinux needs a port label adjustment later.
Fedora Server
sudo dnf install -y wireguard-tools firewalld nftables policycoreutils-python-utils iproute
sudo systemctl enable --now firewalldCheck the installed version and confirm the tools are present:
wg --version
systemctl status firewalld --no-pagerSuccessful output should show WireGuard tooling installed and firewalld active.
Enable IPv4 forwarding safely
WireGuard port forwarding only works if the kernel forwards packets. Enable it persistently, then confirm the live setting.
On the VPS as the non-root sudo user, create a sysctl file:
sudo tee /etc/sysctl.d/99-wireguard-forwarding.conf >/dev/null <<'EOF'
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOFThis enables packet forwarding and relaxes reverse-path filtering enough for routed VPN traffic. Now load it:
sudo sysctl --system
sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.all.rp_filterYou want to see net.ipv4.ip_forward = 1. If forwarding stays at 0, the tunnel may come up, but traffic will not pass through the VPS.
Open the VPN port in firewalld
Open the WireGuard UDP port before you bring up the interface. That avoids the awkward situation where the tunnel is running but unreachable from outside.
On the VPS as the non-root sudo user, allow UDP 51820 and permit forwarding through the public zone:
sudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --reload
sudo firewall-cmd --list-allYou should see 51820/udp and masquerade: yes in the active zone. If your public interface uses a custom zone, apply the rule there instead of the default zone.
Create the WireGuard server configuration
Generate keys on the VPS, then create the interface file. For a routing node, you need the server private key, server public key, and a client peer. If you already have keys, skip the generation step and paste your own values.
On the VPS as the non-root sudo user, create a protected directory and generate keys:
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.key
sudo cat /etc/wireguard/server.pubRecord the public key output. You will need it for the client. Now create the interface file:
sudo tee /etc/wireguard/wg0.conf >/dev/null <<'EOF'
[Interface]
Address = 10.44.0.1/24
ListenPort = 51820
PrivateKey = REPLACE_WITH_SERVER_PRIVATE_KEY
SaveConfig = false
PostUp = firewall-cmd --zone=public --add-rich-rule='rule family=ipv4 source address=10.44.0.0/24 accept' ; firewall-cmd --zone=public --add-masquerade
PostDown = firewall-cmd --zone=public --remove-rich-rule='rule family=ipv4 source address=10.44.0.0/24 accept' ; firewall-cmd --zone=public --remove-masquerade
EOFReplace REPLACE_WITH_SERVER_PRIVATE_KEY with the content of /etc/wireguard/server.key. Keep the file permission strict:
sudo chmod 600 /etc/wireguard/wg0.confIf your environment prefers a static NAT rule instead of PostUp, you can add it in firewalld directly, but this inline approach keeps the tutorial self-contained and easy to roll back.
Add a client peer for forwarding
To complete WireGuard port forwarding, define at least one peer. This example uses a single client at 10.44.0.2. Put the client public key into the server config.
On the VPS as the non-root sudo user, append the peer block:
sudo tee -a /etc/wireguard/wg0.conf >/dev/null <<'EOF'
[Peer]
PublicKey = REPLACE_WITH_CLIENT_PUBLIC_KEY
AllowedIPs = 10.44.0.2/32
EOFReplace REPLACE_WITH_CLIENT_PUBLIC_KEY with the client public key. The AllowedIPs value identifies the client address inside the tunnel.
Validate the configuration before starting it
Do not start the interface until the file parses cleanly. This catches key formatting mistakes before you touch live traffic.
On the VPS as the non-root sudo user, run:
sudo wg-quick up wg0If the command succeeds, the interface starts immediately. If you need a syntax check without keeping it up, use:
sudo wg-quick strip wg0Then inspect the interface and routing table:
ip addr show wg0
ip route show
sudo wg showYou should see wg0 with 10.44.0.1/24. If the interface fails, the most common causes are a bad private key, wrong indentation, or a missing peer key.
Make the tunnel persistent at boot
Once the interface comes up cleanly, enable it with systemd. This ensures the forwarding rule comes back after a reboot.
On the VPS as the non-root sudo user, enable and check the service:
sudo systemctl enable wg-quick@wg0
sudo systemctl status wg-quick@wg0 --no-pagerNext, verify the WireGuard socket and systemd unit survived the first start:
systemctl is-enabled wg-quick@wg0
systemctl is-active wg-quick@wg0You want both to report success.
Set up the client side of the tunnel
The client must use the server public key and the server public endpoint. If the tunnel is meant for remote access only, point the client to the VPS public IP and keep its allowed routes narrow.
On the client, create a matching configuration. This example is generic and can be used from another Linux host:
sudo tee /etc/wireguard/wg0.conf >/dev/null <<'EOF'
[Interface]
Address = 10.44.0.2/24
PrivateKey = REPLACE_WITH_CLIENT_PRIVATE_KEY
DNS = 1.1.1.1
[Peer]
PublicKey = REPLACE_WITH_SERVER_PUBLIC_KEY
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.44.0.0/24, 192.0.2.0/24
PersistentKeepalive = 25
EOFReplace 203.0.113.10 with the real VPS IP. Adjust AllowedIPs to the private network or service subnet you want to reach through the tunnel. If you only want the WireGuard subnet, remove the 192.0.2.0/24 example route.
Bring the client interface up and confirm the handshake:
sudo wg-quick up wg0
sudo wg show
ip route showYou should see a recent handshake and an endpoint pointing back to the VPS.
Test forwarding and packet flow
At this point the tunnel may be active, but forwarding still needs proof. Test from the client to the server tunnel address, then through the server toward a routed destination.
On the client, ping the VPS tunnel IP:
ping -c 3 10.44.0.1Then test a routed host or service that should be reachable through the VPS. If you are forwarding to an internal network, use a real target from that network. The packet path should move through wg0 and then out via the VPS uplink.
On the VPS as the non-root sudo user, watch live counters while traffic passes:
sudo wg show
ip -s link show wg0
sudo firewall-cmd --list-allHandshake timestamps should update, and interface counters should rise when traffic crosses the tunnel.
Check SELinux if forwarding still fails
On RHEL-family systems, SELinux can allow the tunnel to start but still block forwarding behavior or related labels. Check the audit log first instead of guessing.
On the VPS as the non-root sudo user, look for denials:
sudo ausearch -m AVC,USER_AVC -ts recent
sudo journalctl -t setroubleshoot --since "30 min ago"If you see AVC denials related to port binding or packet forwarding, inspect the port context. WireGuard commonly uses UDP 51820, which usually does not need a custom SELinux label, but other services you route may need one.
For a custom UDP port, label it explicitly. Example for 51821:
sudo semanage port -a -t wireguard_port_t -p udp 51821If the port already exists, change it instead:
sudo semanage port -m -t wireguard_port_t -p udp 51821After any SELinux change, retest the tunnel and watch the audit log again. Clean logs are a good sign that the forwarding path is no longer being blocked.
Optional: route a private subnet through the VPS
If your goal is to forward traffic to a downstream private network, add the route on the VPS and make sure the downstream host knows the return path. A tunnel is only half of the job; the reply traffic has to know where to go.
Example route for a downstream subnet:
sudo ip route add 192.0.2.0/24 dev wg0Make the change persistent by placing the route in a systemd network script or your downstream router config, depending on your design. If the return path is missing, clients can send traffic in but never receive replies.
Rollback and recovery
If the tunnel breaks SSH access or routes traffic incorrectly, recover in this order. First, keep the root console or KVM session open if your provider offers one. Then disable the unit and remove the active interface.
On the VPS as root, run:
systemctl stop wg-quick@wg0
systemctl disable wg-quick@wg0
wg-quick down wg0Remove the firewall changes only after the interface is down:
firewall-cmd --permanent --remove-port=51820/udp
firewall-cmd --permanent --remove-masquerade
firewall-cmd --reloadIf you changed sysctl values and want to back them out:
rm -f /etc/sysctl.d/99-wireguard-forwarding.conf
sysctl --systemThis sequence restores the server to a non-forwarding state without leaving stale NAT rules behind.
Common failures and how to diagnose them
Handshake never appears
Run this on the VPS:
sudo wg show
sudo ss -ulnp | grep 51820
sudo firewall-cmd --list-portsIf the port is not listening or the firewall rule is missing, fix the service or reopen the UDP port. If the client endpoint points to the wrong IP, correct the Endpoint line and retry.
Tunnel is up, but routed traffic does not pass
Check forwarding and NAT:
sysctl net.ipv4.ip_forward
sudo firewall-cmd --list-all
ip route show
sudo nft list rulesetIf forwarding is 0, reload sysctl. If masquerade is missing, add it back. If the return path is wrong on the downstream network, correct the route there.
SELinux denial appears in the logs
Inspect the denial and then apply the smallest needed fix:
sudo ausearch -m AVC,USER_AVC -ts recent
sudo semanage port -l | grep 51820If a custom port is blocked, label it and test again. Do not disable SELinux globally for a tunnel problem.
Final verification checklist
On the VPS as the non-root sudo user, confirm the service, routes, and packet flow:
systemctl status wg-quick@wg0 --no-pager
sudo wg show
ip addr show wg0
ip route show
sudo firewall-cmd --list-all
On the client, test the tunnel and the routed destination:
ping -c 3 10.44.0.1
traceroute 192.0.2.10Replace 192.0.2.10 with a real address behind your forwarding path. If the client can reach the tunnel and the routed host responds, the setup is complete.
If you are building a routed VPN endpoint for staff access, inter-site links, or a private service bridge, Hostperl VPS plans give you the control needed for WireGuard, firewalld, and SELinux-aware networking. For production environments that need more performance headroom or a dedicated uplink, compare managed VPS hosting and dedicated server hosting before you launch.
FAQ
Can WireGuard forward traffic to another private subnet?
Yes. Add the downstream subnet to AllowedIPs, enable IPv4 forwarding, and make sure return traffic knows how to get back through the tunnel.
Do I need NAT for every WireGuard setup?
No. NAT helps when the VPS is the exit point or when the downstream network cannot route back cleanly. Pure routed setups can work without masquerade if the return path is configured properly.
Why does the tunnel work but the website still does not load?
The most common cause is a missing return route or a firewall on the downstream host. Check the path with traceroute, then inspect the downstream firewall and route table.
Is this procedure safe on a production RHEL VPS?
Yes, if you keep the old SSH session open, add the firewall rule before enabling the interface, and verify the client connection before closing your root session.
