How to configure firewalld on AlmaLinux and Rocky Linux
Turn on firewalld, open services and ports for good, limit SSH to your own addresses with a zone, and use rich rules, without locking yourself out.
Tested on AlmaLinux 10, Rocky Linux 9
Before you start
- A server running AlmaLinux 9 or 10 or Rocky Linux 9
- Root or a user with sudo
To configure firewalld on AlmaLinux or Rocky Linux, start it with systemctl enable --now firewalld, open what you need with firewall-cmd --permanent --add-service=https (or --add-port=8080/tcp), and apply the change with firewall-cmd --reload. Everything else, from limiting SSH to your office to forwarding ports, is the same three moves on a different zone or rule.
firewalld is the standard firewall on RHEL-family systems, in the role ufw plays on Ubuntu. It writes nftables rules for you. This tutorial was run on AlmaLinux 10 (firewalld 2.4) and Rocky Linux 9 (firewalld 1.3); the commands are the same on both and on AlmaLinux 9.
How firewalld thinks
Three ideas cover almost everything:
| Idea | What it means |
|---|---|
| Zone | A set of rules with a trust level. Each network interface, and optionally each source address range, belongs to one zone. On a server, public is the default, and the one that matters. |
| Service | A named group of ports, like ssh (22/tcp) or https (443/tcp). firewall-cmd --get-services lists the predefined ones. |
| Runtime vs permanent | A command without --permanent changes the running firewall only, and is lost on reload or reboot. With --permanent it is saved, but only takes effect after --reload. |
Anything not allowed in a zone is rejected. Replies to connections the server makes itself are always let back in.
Install and start firewalld
Some cloud images ship without firewalld. Install it if firewall-cmd isn't found, then start it now and at every boot:
sudo dnf install -y firewalld
sudo systemctl enable --now firewalldThe public zone allows ssh out of the box, so starting firewalld doesn't cut off your session.
Check it worked
sudo firewall-cmd --state
sudo firewall-cmd --list-allThe first prints running. The second shows the default zone's rules, typically:
public (default, active)
target: default
interfaces: eth0
services: cockpit dhcpv6-client ssh
ports:
rich rules:cockpit is the web console's port, 9090. If you don't run Cockpit, close it (next section).
Open and close services and ports
Use a service name when one exists, a port when it doesn't. Add --permanent and reload:
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reloadEach command prints success. Adding something that is already open prints Warning: ALREADY_ENABLED: http and changes nothing, which is harmless.
To see what a service opens before you add it:
firewall-cmd --info-service=sshCheck it worked
sudo firewall-cmd --list-services
sudo firewall-cmd --list-portsYou should see dhcpv6-client http https ssh and 51820/udp. Then confirm from outside, for example with curl -I http://203.0.113.10 from your computer, or with the port checks in check open ports with ss and nmap.
Try a rule before you keep it
A runtime rule with a timeout removes itself, which makes it safe for experiments:
sudo firewall-cmd --add-port=8080/tcp --timeout=10mIf you like the result, save the whole runtime state as the permanent one:
sudo firewall-cmd --runtime-to-permanentDon't run that while a timed rule you don't want to keep is still active: it saves everything that's running.
Allow SSH only from your own addresses
The cleanest way is a zone for trusted sources. Packets from a source in that zone get its rules; everyone else falls through to public. Here 198.51.100.0/24 stands for your office or VPN range:
sudo firewall-cmd --permanent --new-zone=admin
sudo firewall-cmd --permanent --zone=admin --add-source=198.51.100.0/24
sudo firewall-cmd --permanent --zone=admin --add-service=ssh
sudo firewall-cmd --reloadCheck it worked
Open a new SSH session from an address in that range before you change anything else:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=admin --list-alladmin is listed with sources: 198.51.100.0/24 and services: ssh. Only when your new session works, remove SSH from the public zone:
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reloadNow SSH answers only from your range. If your home address changes, you'll need the serial console to get back in, so use a range you control (an office or a VPN such as WireGuard).
Rich rules for one-off cases
A rich rule says exactly who may reach which port. For example, PostgreSQL from one network only:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" port port="5432" protocol="tcp" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rulesTo remove it, repeat the command with --remove-rich-rule and the same quoted rule. For more than two or three of these, a source zone like admin above is easier to read.
Forward a port
To send traffic arriving on port 80 to an app listening on 8080 on the same server:
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
sudo firewall-cmd --reloadA reverse proxy (nginx, Caddy) is usually the better tool for web traffic, because it can also handle HTTPS.
Where the rules live
Permanent rules are XML files under /etc/firewalld/zones/, one per zone you changed (public.xml, admin.xml). You can read them to see the whole picture, and back them up with the rest of /etc. Edit them with firewall-cmd rather than by hand; if you do edit one, run firewall-cmd --reload afterwards.
Troubleshooting
FirewallD is not running. The service is stopped. sudo systemctl enable --now firewalld, then journalctl -u firewalld -n 30 if it won't start.
Error: INVALID_SERVICE: Zone 'public': 'xyz' not among existing services. There's no predefined service by that name. Use --add-port=PORT/tcp instead, or check the name with firewall-cmd --get-services.
A rule works until the next reboot, then disappears. It was added without --permanent. Add it again with --permanent and reload, or run --runtime-to-permanent while it's active.
A --permanent rule does nothing. Permanent changes wait for sudo firewall-cmd --reload.
You're performing an operation over default zone ('public'), but your connections/interfaces are in zone 'admin'. Rocky Linux 9's firewalld prints this once your SSH session comes from a source in the admin zone. It's a reminder, not an error: the change went to public. If you meant the admin zone, repeat the command with --zone=admin.
Docker containers are reachable even though the port isn't open. Docker publishes ports with its own nftables rules, in a zone called docker. Bind published ports to 127.0.0.1 (-p 127.0.0.1:8080:80) and put a reverse proxy in front, or filter them before they reach the server with the panel firewall (below).
Next steps
- Check from outside what's actually reachable with ss and nmap.
- Add a sudo user and turn off root login.
- On Ubuntu or Debian, use ufw instead.
Doing it on VPSNine
VPSNine servers also have a panel firewall that filters traffic before it reaches the server, so it covers Docker's ports too and still applies if firewalld is stopped. Using both is fine: open a port in both places, and keep SSH allowed in both. Every plan runs AlmaLinux 10, AlmaLinux 9 and Rocky Linux 9.