Port forwarding lets your VPS accept traffic on one port and pass it somewhere else: to another port on the same server, or to a backend machine reached over a private network or VPN. firewalld does this with a single option, --add-forward-port, plus masquerading when the target is another host. This guide covers both cases and how to test them safely.
Before you start
- A Linux VPS running firewalld, usually AlmaLinux or Rocky Linux 8 or 9. See Set up firewalld on AlmaLinux and Rocky Linux if needed.
- Root or a sudo user.
- For forwarding to another host: that host's IP, reachable from the VPS. Examples use
10.0.0.5, for instance a machine on a WireGuard tunnel. - Access to the browser console in your VPS provider's control panel.
Warning: Do not forward or redirect the port your SSH session uses, and confirm ssh (or your custom SSH port) is still allowed in the zone before you reload. If SSH stops answering, log in through the browser console and run sudo firewall-cmd --reload to drop runtime changes.
Check your starting point:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-allNote the zone bound to your public interface. The examples use public.
Case 1: redirect to another port on the same server
An app listens on port 8080, and you want visitors to reach it on port 80 without changing the app:
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toport=8080No masquerading is needed, because the traffic never leaves the server. The app on 8080 does not need its own open port in the zone.
This redirect applies to traffic arriving from outside. Requests the server makes to itself, such as curl http://localhost, are not redirected, so test from another machine.
Case 2: forward to another host
Send traffic arriving on port 8443 of the VPS to port 443 on 10.0.0.5.
- Enable masquerading on the public zone. It rewrites the source address so the backend replies through the VPS:
sudo firewall-cmd --zone=public --add-masquerade- Add the forward rule with
toaddr:
sudo firewall-cmd --zone=public --add-forward-port=port=8443:proto=tcp:toport=443:toaddr=10.0.0.5If you leave out toport, the same port number is used on the backend.
- Check that the kernel forwards packets. firewalld normally turns this on when masquerading is enabled, but confirm it:
sysctl net.ipv4.ip_forwardIf it prints 0, enable it permanently:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ip-forward.conf
sudo sysctl --system- Make sure nothing on the backend blocks the traffic. Because of masquerading, the backend sees connections coming from the VPS's private address, not from the visitor.
Note: Masquerading hides the visitor's real IP from the backend. If the backend needs it, for example for logs or rate limiting, use a reverse proxy such as Nginx on the VPS instead of plain port forwarding.
Test before saving
All commands above were runtime only. They work now and disappear on reload, which is the safe way to try them.
From a computer outside the server:
nc -vz 203.0.113.10 8443
curl -vk https://203.0.113.10:8443/Replace 203.0.113.10 with your VPS IP. A successful connection and a response from the backend mean the forward works.
If it fails, look at the live NAT rules that firewalld generated:
sudo nft list ruleset | grep -i -A3 dnatMake it permanent
Once it works, save the runtime state:
sudo firewall-cmd --runtime-to-permanentOr add the rules again with --permanent and reload:
sudo firewall-cmd --permanent --zone=public --add-masquerade
sudo firewall-cmd --permanent --zone=public --add-forward-port=port=8443:proto=tcp:toport=443:toaddr=10.0.0.5
sudo firewall-cmd --reloadCheck it works
sudo firewall-cmd --zone=public --list-forward-ports
sudo firewall-cmd --zone=public --query-masqueradeYou should see your forward rule and yes for masquerade. Reboot during a quiet period, or reload, and repeat the outside test to confirm the rules survive.
To remove a forward later, repeat the same rule with --remove-forward-port:
sudo firewall-cmd --permanent --zone=public --remove-forward-port=port=8443:proto=tcp:toport=443:toaddr=10.0.0.5
sudo firewall-cmd --reloadTroubleshooting
- Connection times out to a remote backend: masquerade is off or
ip_forwardis0. Check both. Also make sure the backend has a route back to the VPS. - Works from outside, not from the VPS itself: expected. Forward rules apply to incoming traffic, not local connections. Test from another machine.
- Backend logs show the VPS IP, not the visitor: that is how masquerading works. Use a reverse proxy if you need the client IP.
- Rules gone after reboot: you skipped
--permanentor--runtime-to-permanent. - SSH dropped after reload: the zone lost the
sshservice or your custom port. Use the browser console to add it back.
Next steps
- Build the private link to the backend: Set up a WireGuard VPN server.
- Restrict who can use the forwarded port: Write firewalld rich rules.
- The same job without firewalld: NAT and port forwarding with nftables.
