If you provisioned a plain AlmaLinux or Rocky Linux VPS — no cPanel, no WHM, just a bare server — and tried to open a port the way you would on Ubuntu, you probably typed ufw allow 8080 and got command not found. That's expected. RHEL-family distros don't ship UFW at all. They use firewalld, and it works differently enough that copying commands from a Debian tutorial will just confuse you further. Here's what firewalld actually is, how to open and close ports safely, and the mistake that locks the most people out of their own server.

Symptom: your app is running but the port is unreachable

Classic setup: you deploy an app on port 3000, or Postgres on 5432, or a custom API on 8443. systemctl status shows it's active and listening — ss -tulpn confirms it. Locally, curl localhost:3000 works fine. From outside, the connection just hangs and times out. No error message, no log entry on the app side, because the request never gets there. That's almost always firewalld quietly dropping the packet before it reaches your app.

Cause: firewalld is on by default on AlmaLinux and Rocky

Every fresh AlmaLinux, Rocky Linux, and CentOS Stream VPS ships with firewalld enabled out of the box, and by default it only allows SSH, DHCP client traffic, and a couple of other basics. Everything else is blocked until you explicitly open it. Check whether it's running with:

systemctl status firewalld

If it says active (running), that's your answer. You can also list what's currently allowed:

firewall-cmd --state
firewall-cmd --list-all

The output of --list-all shows the active zone and every service and port currently permitted in it. If your app's port isn't in that list, nothing gets through.

Zones: the concept that trips people up

UFW has one flat rule list. Firewalld groups rules into zones, and each network interface is assigned to exactly one zone at a time. The idea is you'd treat a trusted internal LAN differently from a public-facing interface — but on a single-NIC VPS, you'll almost always be working with just the default zone, which is public unless someone changed it. Confirm which zone your interface actually uses:

firewall-cmd --get-active-zones

This matters because a rule added to the wrong zone silently does nothing. If you add a port to internal while your interface is bound to public, you'll swear the command failed when really it just landed somewhere you're not looking.

Opening a port

To let inbound traffic through, permanently, add the rule and reload:

firewall-cmd --permanent --add-port=3000/tcp
firewall-cmd --reload

Both lines matter. Without --permanent, the rule vanishes on the next reboot or firewalld restart. Without --reload afterward, a --permanent rule is written to the config on disk but not actually applied to the running firewall until you either reload or restart the service. This is the single most common firewalld mistake: someone runs the --permanent command, tests immediately, sees it's still blocked, and assumes firewalld is broken — when really they just forgot the reload.

If you want to test a rule live without committing to it yet, drop --permanent and it applies immediately but disappears on reload or reboot:

firewall-cmd --add-port=3000/tcp

Good workflow: test without --permanent first, confirm the app is reachable, then add it again with --permanent and reload once you're sure.

Using service names instead of raw ports

Firewalld ships a library of predefined services — http, https, ssh, mysql, postgresql, and dozens more — that map to the correct port and protocol automatically. For standard services this is cleaner than remembering port numbers:

firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

See the full list with firewall-cmd --get-services. For anything custom — your own app on a nonstandard port — stick with --add-port.

Closing a port

Removing access is the mirror image of opening it, and just as easy to get half-right:

firewall-cmd --permanent --remove-port=3000/tcp
firewall-cmd --reload

Double-check with firewall-cmd --list-ports and firewall-cmd --list-services afterward that it's actually gone. If you opened the port originally via a service name rather than a raw port number, you need to remove it the same way you added it — --remove-service, not --remove-port, or the rule won't match anything and will silently stay in place.

Restricting a port to specific IPs with rich rules

Sometimes you don't want a port wide open to the internet — a database port, an admin panel, a monitoring dashboard you only access from your office IP. Firewalld's rich rules handle this without needing a separate tool:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.4/32" port protocol="tcp" port="5432" accept'
firewall-cmd --reload

That allows PostgreSQL only from a single IP and drops everyone else by default, since the port was never opened generally in the first place. Swap the /32 for a wider CIDR range if you need to allow a whole office or VPN subnet.

The mistake that locks you out over SSH

This one deserves its own warning. If you're managing the firewall over an SSH session and you accidentally remove SSH access or block your own connecting IP, you don't get a graceful error — your session just freezes and the server becomes unreachable until you use your host's console/VNC access to fix it from a local login. Before touching any rule that could affect port 22:

  • Confirm SSH is allowed first: firewall-cmd --list-services should show ssh in the active zone.
  • Open a second SSH session before making changes, so you have a fallback connection if the first one drops.
  • Never run firewall-cmd --panic-on unless you genuinely want to cut all traffic immediately — it's meant for active-attack situations, not routine config work, and it will also lock out your own SSH session.

firewalld vs UFW vs CSF: which one am I actually using?

ToolTypical environmentConfig style
UFWUbuntu, DebianSimple flat rule list, ufw allow/deny
firewalldAlmaLinux, Rocky Linux, CentOS Stream, RHELZone-based, firewall-cmd, needs --permanent + --reload
CSFcPanel/WHM servers specificallyWHM UI + csf.conf, auto-detects cPanel's own ports

Only run one of these at a time. Installing CSF on a server that already has firewalld active (or vice versa) leads to rules that contradict each other and ports that behave inconsistently depending on which service last touched the chain. If you're moving from cPanel to a bare AlmaLinux VPS, or the reverse, stop and mask the one you're not using:

systemctl stop firewalld
systemctl disable firewalld

Prevention: verify from outside, not just locally

A port that responds to curl localhost can still be completely unreachable externally, so always confirm from a second machine or an online port checker after any firewall change. A quick check from another Linux box:

nc -zv your-server-ip 3000

Keep a running note — even a plain text file on the server — of every port you've intentionally opened and why. Six months from now when firewall-cmd --list-ports shows five open ports, you want to know which app owns each one instead of guessing whether it's safe to close.

Frequently Asked Questions

Why does my port stay closed even after I ran firewall-cmd --add-port?

Two likely reasons: you used --permanent without following it with firewall-cmd --reload, so the rule was written but never applied, or the port was added to a zone that isn't the one your network interface is actually using. Run firewall-cmd --get-active-zones to confirm the right zone, and always pair --permanent changes with a reload.

Do I need firewalld if I already have a cloud provider security group or VPS firewall panel?

Yes, treat them as two independent layers. A provider-level security group filters traffic before it reaches your VPS at all, while firewalld filters what the operating system itself accepts. A port has to be open in both places to be reachable — closing it in either one is enough to block it.

Can I use firewalld and CSF at the same time on a cPanel VPS?

Don't. CSF manages its own iptables rules directly and expects to be the only firewall controller on the box. Running firewalld alongside it causes conflicting rule chains that are painful to debug. If you're on cPanel/WHM, use CSF and leave firewalld disabled.

How do I see every rule firewalld is currently enforcing?

Run firewall-cmd --list-all for the active zone's full picture — interfaces, services, ports, and rich rules in one place. To check a non-default zone specifically, add --zone=zonename to the same command.

I locked myself out changing a firewalld rule over SSH — now what?

You'll need out-of-band access — most VPS providers offer a browser-based console or VNC session that bypasses SSH entirely. Log in there, review recent rules with firewall-cmd --list-all, re-add ssh to the active zone if it's missing, and reload. Keeping a second SSH session open during firewall changes is the cheapest insurance against needing this at all.