firewalld Basics for Rocky or AlmaLinux VPS
Learn how to open SSH, allow web ports, close ports, survive reboot, understand zones, and avoid firewalld's --permanent trap on Rocky or AlmaLinux.
Wetin firewalld be, and why Rocky and AlmaLinux dey ship am
firewalld na firewall manager wey Rocky Linux, AlmaLinux and other rebuilds of Red Hat Enterprise Linux (RHEL) install by default. E no dey inspect packets by itself. E dey keep saved configuration and turn that configuration into nftables rules. One command, firewall-cmd, fit edit am while server still dey online.
If you already know how ufw dey work for Ubuntu VPS, you know the work wey e dey do. firewalld add two ideas wey ufw no get. The first one na zones: named policy wey packets dey enter based on sorting. The second one na separation between live rules and saved rules. Na the --permanent flag dey control this, and na the biggest source of confusion for this tool.
Everything wey dey below na command wey you go run for your own server. Test every change from another machine, because rule fit look correct for the server but still dey wrong from internet.
Open SSH before you do anything else
Most Rocky and AlmaLinux installs get firewalld dey present and dey run already, and the configuration wey dem release allow SSH. Some minimal cloud images remove am. Check am instead make you assume.
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --statefirewall-cmd --state dey print running. If the service stop, every other firewall-cmd call go answer FirewallD is not running and exit non-zero. Na the first thing to check when command look like e no do anything.
Now read wetin dem currently allow.
sudo firewall-cmd --list-allThe real output get some extra lines. Na these ones matter:
public (active)
target: default
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
rich rules:ssh for the services: line na why your session still dey work. If e no dey there, add am before you touch anything else, because starting firewall without SSH rule go end the session and e no go allow you enter again.
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reloadtarget: default mean say packet wey no match anything go get rejected with ICMP (internet control message protocol) host-prohibited reply, so client wey hit closed port go see No route to host straight away. Setting the target to DROP make the server stay silent instead, and scanners go then wait for timeout.
sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reloadKnow the cost before you run am: DROP also stop the server from answering ping, so your own monitoring go quiet too.
Why my rule disappear? The --permanent flag
firewalld dey hold two configurations at once. The runtime configuration na wetin kernel dey enforce now now. The permanent configuration na wetin dey inside /etc/firewalld/zones/public.xml and wetin go come back after reload or reboot.
Command wey no get --permanent go change runtime only. E go work immediately, but e go disappear for next reload or boot. Command wey get --permanent go write the file but e no go change wetin dey run, so the port go remain closed until you reload. None of these behaviours na bug. Both dey surprise people because command go print success for both cases.
Write the two together every time.
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reloadYou fit read both configurations. Na the fastest way to know which of the two mistakes you make.
sudo firewall-cmd --list-services
sudo firewall-cmd --permanent --list-servicesThe first one go print the live set. The second one go print the saved set. If live set get service wey saved set no get, that rule go die for next reload. If saved set get one wey live set no get, you forget to reload. sudo firewall-cmd --runtime-to-permanent go copy everything wey dey live into the saved file. E useful after you don experiment with rules.
--reload go keep connection tracking state, so your SSH session go survive am. --complete-reload go reload the kernel modules too and lose that state. This one normally go terminate every open connection, including your own. Use the plain reload.
One safety net dey built in. A runtime rule fit expire by itself.
sudo firewall-cmd --add-service=http --timeout=5mThat rule go remove itself after five minutes. You no fit combine am with --permanent. Na the whole point: e dey for testing change wey you no sure about. The older safety net better. Keep second SSH session open while you dey edit rules. No close am until fresh login confirm say the new rules dey work.
Zones, and why only the default zone matter for VPS
Zone na named set of permissions wey get trust level attached. firewalld dey put every incoming packet inside exactly one zone. E first compare source address of the packet with each zone sources: list. If nothing match, e go use the zone wey the incoming interface dey bound to. If interface no dey bound to any zone, packet go enter default zone.
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zonesFor VPS wey get one network interface, the first answer almost always na public, and na only this zone you go use. firewall-cmd wey no get --zone= argument dey work on default zone, na why every short command for this guide work without naming zone.
Na here failure wey fit waste afternoon dey happen. If interface dey bound to another zone, your rules go enter public while traffic dey handled for another place. So nothing wey you add go get effect, and nothing go warn you. --get-active-zones dey show the binding:
public
interfaces: eth0If interface show under another zone name, either write your rules for that zone with --zone=, or move the interface.
sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reloadNetworkManager dey manage interfaces for Rocky and AlmaLinux, and e dey set the zone again when connection come up. Set am there too, so reboot no go cancel your work. Take the connection name from the first command, because e rarely be the same as the device name.
sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone publicSource matching get priority over interface matching. Na this one make one address fit get different policy. The built-in trusted zone dey accept everything.
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reloadTake care with that zone. E dey open every port for the server to that address, including the database wey you think say private. Use a rich rule when you want allow one port, not one host.
Wetin be firewalld service?
Service na named bundle of ports wey dem ship as XML file. --add-service=https dey open 443/tcp because /usr/lib/firewalld/services/https.xml define wetin https mean.
sudo firewall-cmd --get-services
sudo firewall-cmd --info-service=https--info-service dey show the ports wey dey hide behind the name:
https
ports: 443/tcpUse the name when one dey. E go read clear for --list-all six months later, and packages like Cockpit dey install their own service file. Use --add-port for anything wey no get definition.
The gap wey you need watch na this: ssh service mean 22/tcp and nothing else. If you move SSH go another port while hardening SSH access for the server, then --add-service=ssh no go open the port wey you really dey use.
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reloadFor RHEL rebuild, another lock dey for that door. SELinux (security-enhanced Linux) dey label port numbers, and sshd no get permission to bind to port wey dey outside its labels. E go refuse to start, and log go show error: Bind to port 2222 on 0.0.0.0 failed: Permission denied. Label the port first.
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t ssh_port_t -p tcp 2222How I fit see wetin dey open right now?
sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
sudo nft list table inet firewalld | head -n 40The first two go report wetin firewalld believe. The third one go read the rules wey kernel actually get, for the table wey firewalld dey control. Dem suppose match.
None of this prove anything. Test am from another machine:
nc -zv 203.0.113.20 443No run the test for the server itself. firewalld dey accept everything wey enter through loopback interface, so curl http://localhost:8080 go succeed no matter wetin your rules talk. That test show say the service dey alive. E no tell you anything about the firewall.
Allow web port
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-servicesThe last command suppose list http https together with wetin dey there before. If the site still no answer, e fit be say firewall no be the problem. Rule dey permit packet. But process still need listen for am.
sudo ss -tlnpSocket wey show as 0.0.0.0:443 or *:443 fit accept connections from any address. One wey show as 127.0.0.1:443 dey answer for loopback only, and no firewall rule go ever make am reachable from outside. Ports and listening sockets for Linux explain this difference more.
How I fit close port again?
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reloadThe --permanent rule still apply here, and e dey cause more wahala for this direction. If you remove service only from the runtime, the port go look closed. But the next reload or reboot go open am again from the saved file. Na hole be that wey you no go notice, because the check wey you run pass.
If you remove something wey no dey there before, Warning: NOT_ENABLED: http go print and command still go exit with 0. If you add the same thing twice, Warning: ALREADY_ENABLED: http go print. Both behaviour safe. Misspelled name different: Error: INVALID_SERVICE mean say firewalld no get definition with that name, and nothing change at all.
If your --list-all show cockpit and you no dey use Cockpit web console for port 9090, remove am. Every open port na service wey you must keep patched.
Restrict port to one source address
Rich rules na di long form, for when plain service name no fit express wetin you mean. To restrict SSH to one office address, you need two commands, and na the second one people dey forget.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reloadZone na set of permissions; e no be numbered list wey dey stop for the first match. The rich rule add accept for one address. E deny nobody. While ssh still dey for services: line, the whole internet still fit reach port 22, and the rich rule no change anything wey you fit measure. Remove the broad entry, or the narrow one na just decoration.
For port wey no get service name, specify the port instead.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port port="5432" protocol="tcp" accept'To drop noisy network and keep record, put log element before the action, because na the order wey rich rule language expect.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" log prefix="fw-drop " level="info" limit value="3/m" drop'
sudo firewall-cmd --reload
sudo journalctl -k -g fw-dropThe limit value dey stop flood of packets from filling the journal. Before you lock SSH to one address, make sure say the address stable. Home connection wey get changing IP address fit lock you out the day e change, so test and confirm say your provider's console access dey work before then.
ufw commands and dem firewall-cmd equivalents
Na the same tasks, but different tool. Every --permanent line need one sudo firewall-cmd --reload after am, and na this one thing list like this no fit show.
sudo ufw enablego becomesudo systemctl enable --now firewalldsudo ufw disablego becomesudo systemctl disable --now firewalldsudo ufw status verbosego becomesudo firewall-cmd --list-allsudo ufw allow OpenSSHgo becomesudo firewall-cmd --permanent --add-service=sshsudo ufw allow 443/tcpgo becomesudo firewall-cmd --permanent --add-port=443/tcpsudo ufw delete allow 443/tcpgo becomesudo firewall-cmd --permanent --remove-port=443/tcpsudo ufw allow from 203.0.113.10 to any port 22go become the rich rule wey show abovesudo ufw reloadgo becomesudo firewall-cmd --reloadsudo ufw default deny incomingna already how thepubliczone dey behave, and--set-target=DROPna the silent version of amsudo ufw logging ongo becomesudo firewall-cmd --set-log-denied=all
One difference dey wey make sense to state clearly. ufw dey keep numbered list, and you fit insert rule for position 1. firewalld no get rule numbers, so “put this rule first” no mean anything here. When two firewalld entries look like dem contradict each other, the broad accept rule go win because nothing for the set dey deny the traffic. You go remove the broad entry yourself.
Why Docker container reach dey when firewall look closed?
Because published container port no dey pass through the firewall part wey your zone dey control. docker run -d -p 8080:80 nginx tells Docker make e write im own NAT (network address translation) and forwarding rules. Packet wey enter for 8080 go rewrite and route go the container, so na forwarded packet, e no be packet delivered to the host. The services: and ports: lines for your zone control packets wey go the host. Docker rules control the forward path, and dem accept am.
The result na server wey sudo firewall-cmd --list-all show say no port 8080 dey, but nc -zv 203.0.113.20 8080 from another machine still connect. Check wetin Docker install:
sudo iptables -t nat -L DOCKER -nThe fix dey for the publish flag. Bind the port to loopback, then put reverse proxy for front.
docker run -d -p 127.0.0.1:8080:80 nginxThe container now answer curl http://127.0.0.1:8080 for the server, and nothing from outside fit reach am. Ubuntu users dey face the same problem, as why Docker containers publish ports straight past ufw explain. Rootful Podman, wey Rocky and AlmaLinux ship for their base repositories, dey publish ports with the same NAT approach. So test from another machine instead of trusting the zone list.
Make e survive reboot, and the errors wey you go see
sudo systemctl is-enabled firewalld
sudo systemctl status firewalldenabled and active (running) na wetin you want. Firewall wey dey run but no enable go protect you until the first reboot. Put this check for the list of things wey you go go through for the first ten minutes for a new VPS, together with SSH keys and updates.
Raw nftables commands and firewalld no mix. firewalld dey control one table wey dem call inet firewalld. sudo nft flush ruleset dey delete am, so server go open to everything. But firewall-cmd --list-all still dey show the configuration wey you intend, because firewalld dey report wetin e believe, no be wetin kernel dey hold. sudo firewall-cmd --reload go install the rules again. Use firewall-cmd write rules so dem go return after reload.
Two firewall managers for one server. If you install ufw or iptables-services beside firewalld, two programs go dey write rules without knowing wetin each other dey do. The one wey start last go determine the result. Choose one. For Rocky and AlmaLinux, firewalld na the one wey the distribution support.
The provider firewall for front of the box. Plenty VPS panels get separate network firewall. If --list-all show say port dey open but connection from outside still fail, check the panel before you change anything for the server. The reverse one still dey happen: open panel rule no go help if firewalld reject the packet.
Running firewall-cmd without sudo. Every change need root. Without am, authorization check go reject the request and nothing go change. For quick look, e fit seem like the command no do anything.
Six commands cover most days: --list-all to read the state, --permanent --add-service or --add-port to open something, --permanent --remove-service to close am, --reload to apply the saved file, and --runtime-to-permanent after you finish testing. The zone na public, the flag na --permanent, and the only reliable check come from another machine.
FAQ
Why my firewalld rule disappear after reboot?
The rule enter runtime configuration only. sudo firewall-cmd --add-service=http dey apply immediately and dem discard am for next reload or boot, because nobody touch the saved configuration for /etc/firewalld/zones/public.xml. Add --permanent, then run sudo firewall-cmd --reload. To keep rules wey you don add by hand, run sudo firewall-cmd --runtime-to-permanent. This one copy the live set go the saved file.
Why nothing change after I add rule with --permanent?
Na because --permanent write the file but leave the firewall wey dey run untouched. The port remain closed until sudo firewall-cmd --reload load the saved configuration into the kernel. Compare sudo firewall-cmd --list-services with sudo firewall-cmd --permanent --list-services. If the saved list get entry wey the live list no get, na the reload be wetin you miss.
I suppose use --add-service or --add-port?
Use --add-service when name dey for wetin you run. E show the purpose clearly, and sudo firewall-cmd --info-service=https show exactly which ports the name cover. Use --add-port when nothing define your service, or when e dey listen on non-standard port. The ssh service mean 22/tcp only, so if SSH move go 2222, e need --add-port=2222/tcp plus SELinux label for that port.
Why my Docker container dey reachable when firewall-cmd show the port closed?
Docker own NAT rules rewrite published port and forward am go the container, so the packet never reach the host. A zone service and port list only cover packets wey reach the host. The container fit answer from internet while --list-all show nothing. Publish am to loopback instead with docker run -d -p 127.0.0.1:8080:80 nginx, then put reverse proxy in front of am.
I fit install ufw for Rocky Linux instead of firewalld?
Two firewall managers for one server dey write rules without knowing wetin the other one do. Which rule set survive depend on which service start last. firewalld na the supported tool for Rocky Linux and AlmaLinux. E don already install, and e dey use the same nftables backend wey ufw go use. Learn the default zone and the --permanent flag one time, and you don get the whole tool.