firewalld groups rules into zones. Each zone is a trust level with its own list of allowed services and ports, and every incoming packet is handled by exactly one zone. Once you understand how a packet picks its zone, you can do useful things such as opening SSH only to your office IP while the web ports stay public.
Before you start
- A Linux VPS running AlmaLinux or Rocky Linux 8 or 9 (firewalld is the default there). It also works on Ubuntu and Debian if you installed firewalld instead of UFW.
- Root or a sudo user, and firewalld running. If it is not set up yet, follow Set up firewalld on AlmaLinux and Rocky Linux first.
- Your own public IP address. Search "what is my IP" from the computer you manage the server from.
- The browser console in your VPS provider's control panel, in case you block your own SSH session.
Warning: Several steps below move SSH access between zones. Always add SSH to the new zone and test it before you remove it from the old one. If you lock yourself out, log in through the browser console and run firewall-cmd --reload or fix the zone.
How a packet picks a zone
firewalld checks in this order:
- Source: if the sender's IP matches a source bound to a zone, that zone handles the packet.
- Interface: otherwise, the zone bound to the network interface (for example
eth0) handles it. - Default zone: if the interface has no zone, the default zone is used. On most servers that is
public.
Source bindings win over interface bindings. That is what makes per-IP access possible.
See the current state:
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zonesTypical output:
public
public
interfaces: eth0The built-in zones
List them with sudo firewall-cmd --get-zones. The ones you will use most:
- public: the usual default. Only the services you add are allowed. On RHEL clones it starts with
ssh,dhcpv6-clientand oftencockpit. - trusted: accepts all traffic. Use it only for sources you fully control, such as a private network between your own servers.
- drop: silently drops all incoming packets with no reply. Good for blocklists.
- block: rejects incoming packets with an ICMP error, so the sender knows it was refused.
- internal, home, work, dmz, external: preset trust levels mainly meant for desktops and routers.
externalhas masquerading enabled.
Inspect one zone, or all of them:
sudo firewall-cmd --zone=public --list-all
sudo firewall-cmd --list-all-zones | lessRuntime and permanent rules
Every change goes into either the runtime config (active now, lost on reload or reboot) or the permanent config (saved to disk, applied on reload).
- Without
--permanent, a change is live immediately but temporary. - With
--permanent, it is saved but not live untilfirewall-cmd --reload.
A safe habit is to test in runtime, then save the working state with:
sudo firewall-cmd --runtime-to-permanentBuild a custom admin zone
This example creates a zone called admin that allows SSH only from 203.0.113.10, while web traffic stays open in public.
- Create the zone and reload so it exists at runtime:
sudo firewall-cmd --permanent --new-zone=admin
sudo firewall-cmd --reload- Bind your IP (or a range such as
203.0.113.0/24) and allow SSH in the new zone:
sudo firewall-cmd --permanent --zone=admin --add-source=203.0.113.10/32
sudo firewall-cmd --permanent --zone=admin --add-service=ssh
sudo firewall-cmd --reloadIf SSH runs on a custom port, use --add-port=2222/tcp instead of --add-service=ssh.
- Make sure the web ports are open to everyone in
public:
sudo firewall-cmd --permanent --zone=public --add-service=http --add-service=https
sudo firewall-cmd --reload- Open a new SSH session from
203.0.113.10and confirm it works.
- Only now remove SSH from the public zone:
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reloadFrom now on SSH answers only to the IPs in admin. If your home IP changes, you will need the browser console to add the new one, so consider adding a second fixed address such as a VPN server.
Move an interface or change the default zone
To bind an interface to a different zone:
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
sudo firewall-cmd --reloadOn servers managed by NetworkManager the zone can also be stored in the connection profile. Check it with nmcli connection show eth1 | grep zone if a change does not stick.
To change the default zone:
sudo firewall-cmd --set-default-zone=publicThis takes effect immediately and is saved.
Check it works
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=admin --list-all
sudo firewall-cmd --zone=public --list-alladmin should show your source IP and ssh. public should show eth0 with http and https. From a different network, such as a phone hotspot, an SSH attempt should time out while the website still loads.
Troubleshooting
- Change works until reboot, then disappears: it was runtime only. Run
sudo firewall-cmd --runtime-to-permanentor repeat it with--permanentand reload. --new-zonezone is "INVALID_ZONE": you have not reloaded since creating it. Runsudo firewall-cmd --reload.- SSH still open to everyone:
sshis still listed inpublic, or another zone with SSH is bound to the interface. Check--list-all-zones. - Locked out after removing SSH from public: your IP changed or the source was mistyped. Log in through the browser console, then fix it with
--add-source.
Next steps
- Fine-grained per-IP rules inside one zone: Write firewalld rich rules.
- Block large lists of addresses: Block thousands of IPs with firewalld ipsets.
- Get back in if something goes wrong: Locked out by your firewall?.
