Security

firewalld zones explained: public, trusted, drop and custom zones

Learn what firewalld zones do, how traffic is matched to a zone by interface or source IP, and how to build a custom admin zone so only your IPs can reach SSH.

IntermediateLinux server5 min read

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:

  1. Source: if the sender's IP matches a source bound to a zone, that zone handles the packet.
  2. Interface: otherwise, the zone bound to the network interface (for example eth0) handles it.
  3. 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:

Bash
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones

Typical output:

public
public
  interfaces: eth0

The 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-client and often cockpit.
  • 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. external has masquerading enabled.

Inspect one zone, or all of them:

Bash
sudo firewall-cmd --zone=public --list-all
sudo firewall-cmd --list-all-zones | less

Runtime 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 until firewall-cmd --reload.

A safe habit is to test in runtime, then save the working state with:

Bash
sudo firewall-cmd --runtime-to-permanent

Build 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.

  1. Create the zone and reload so it exists at runtime:
Bash
   sudo firewall-cmd --permanent --new-zone=admin
   sudo firewall-cmd --reload
  1. Bind your IP (or a range such as 203.0.113.0/24) and allow SSH in the new zone:
Bash
   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 --reload

If SSH runs on a custom port, use --add-port=2222/tcp instead of --add-service=ssh.

  1. Make sure the web ports are open to everyone in public:
Bash
   sudo firewall-cmd --permanent --zone=public --add-service=http --add-service=https
   sudo firewall-cmd --reload
  1. Open a new SSH session from 203.0.113.10 and confirm it works.
  1. Only now remove SSH from the public zone:
Bash
   sudo firewall-cmd --permanent --zone=public --remove-service=ssh
   sudo firewall-cmd --reload

From 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:

Bash
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
sudo firewall-cmd --reload

On 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:

Bash
sudo firewall-cmd --set-default-zone=public

This takes effect immediately and is saved.

Check it works

Bash
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=admin --list-all
sudo firewall-cmd --zone=public --list-all

admin 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-permanent or repeat it with --permanent and reload.
  • --new-zone zone is "INVALID_ZONE": you have not reloaded since creating it. Run sudo firewall-cmd --reload.
  • SSH still open to everyone: ssh is still listed in public, 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

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.