
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Your production box listens on SSH, HTTP, and HTTPS, yet a single flat iptables script cannot express who should reach each service. firewalld: Zone-Based Firewalling solves that by grouping interfaces and IP sources into trust zones, then applying different rules per zone. On Ubuntu 22/24 and RHEL-family servers I maintain for Linux system administration in Nepal, firewalld replaced hand-edited iptables chains with a runtime you can change without dropping active sessions. This guide walks through zone selection, permanent rules, rich rules, and patterns that survive real deploys.
What is firewalld zone-based firewalling and why use it?
Firewalld is the default firewall manager on RHEL, AlmaLinux, Rocky Linux, and Fedora. Many Ubuntu 22/24 production hosts run it alongside UFW or instead of raw iptables when teams want zone semantics. A zone is a named policy container—not a physical network segment.
Each zone defines default behaviour plus optional allowances. An interface bound to public might allow HTTP and HTTPS while blocking everything else. The same machine’s VPN interface in trusted can accept all traffic. Sources (CIDR blocks) can also map to zones, which matters when traffic arrives through one interface but originates from different networks.
I reach for zones when a client runs Laravel, WordPress, or API workloads on a single VPS. The public face needs tight rules. Admin access from a fixed office IP belongs in a stricter or looser zone depending on policy. Zone-based rules survive reboots when written to the permanent configuration, which beats brittle shell scripts in /etc/rc.local.
Firewalld sits above nftables (or iptables on older stacks). You edit zones with firewall-cmd. The daemon compiles zone policy into kernel rules. That separation keeps day-two changes safe for teams who also manage Ansible playbooks for PHP server provisioning and want idempotent firewall tasks.
How do firewalld zones differ from each other?
Firewalld ships predefined zones ordered by trust. Only one zone is the default for traffic that does not match an interface or source binding. Understanding the built-in zones prevents accidentally placing a database port in the wrong profile.
| Zone | Default target | Typical use | Web server pattern |
|---|---|---|---|
| drop | DROP incoming; outbound allowed | Silenced interfaces | Unused NIC, honeypot research |
| block | REJECT incoming with ICMP | Visible denial | Staging host you want probed to fail fast |
| public | DROP; explicit allows only | Untrusted networks | Default WAN on VPS—HTTP, HTTPS, rate-limited SSH |
| external | DROP; masquerade enabled | NAT gateway | Small office router forwarding to LAN |
| dmz | DROP; limited access | Semi-isolated apps | Reverse proxy tier, no direct DB |
| work | DROP; some trust | Corporate LAN | Internal GitLab runner |
| home | DROP; broader trust | Home lab | Dev machine with Samba |
| internal | DROP; more services | Private subnets | MySQL/Redis reachable only inside VPC |
| trusted | ACCEPT all | Fully trusted paths | WireGuard admin; use sparingly |
The default zone on a fresh install is often public. That is correct for internet-facing Laravel or WordPress hosts. Promoting everything to trusted because rules feel inconvenient is a mistake I still see on inherited servers. One open zone undoes weeks of hardening work.
Custom zones are supported. Name them after roles—web, db-replica, deploy—when predefined labels confuse the team. Keep the count small. Operational clarity beats granular naming on a five-person crew.
How do you configure firewalld zones on a production Linux server?
Install and enable the service first. On RHEL-family systems firewalld is usually present. On Ubuntu you add the package explicitly.
sudo apt update
sudo apt install firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --state Verify active zones and interface bindings before changing anything. Document the output so you can roll back if SSH disappears.
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all
sudo firewall-cmd --list-all --zone=public Assign interfaces to zones
Bind the primary NIC to public for a standard web stack. Use the permanent flag so the binding survives reboot.
sudo firewall-cmd --zone=public --change-interface=eth0 --permanent
sudo firewall-cmd --reload Replace eth0 with your device name from ip -br link. Cloud images often use ens3 or enp0s3. A wrong interface name fails silently until you notice the zone list unchanged.
Allow services and ports per zone
Prefer named services when they exist. They track standard port definitions in /usr/lib/firewalld/services/.
- Add HTTP and HTTPS to the public zone:
sudo firewall-cmd --zone=public --add-service=http --permanentand the same forhttps. - Restrict SSH to a management source using a rich rule instead of global
sshservice if your provider allows source filtering. - Reload:
sudo firewall-cmd --reload. - Confirm runtime and permanent views match:
sudo firewall-cmd --zone=public --list-all.
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.50/32" service name="ssh" accept' --permanent
sudo firewall-cmd --reload For Laravel queues or Redis on non-standard ports, add explicit port/protocol pairs:
sudo firewall-cmd --zone=internal --add-port=6379/tcp --permanent
sudo firewall-cmd --reload Map source addresses to zones
When admin traffic arrives on the same interface as public web traffic, source-based zoning is the right tool. A monitoring subnet might land in internal while the rest of the world stays in public.
sudo firewall-cmd --zone=internal --add-source=10.10.0.0/24 --permanent
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.10.0.0/24" port port="9100" protocol="tcp" accept' --permanent
sudo firewall-cmd --reload This pattern pairs well with Prometheus Alertmanager monitoring on private RFC1918 space. Never expose node_exporter to the open internet just because the dashboard is pretty.
What is the difference between runtime and permanent firewalld rules?
Firewalld maintains two layers. Runtime rules apply immediately but vanish on reload unless copied to permanent storage. Permanent rules live under /etc/firewalld/ and load at boot.
Always test with runtime first when you are one SSH session away from lockout:
sudo firewall-cmd --zone=public --add-service=http
sudo firewall-cmd --zone=public --list-services Once confirmed, promote the same change:
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload The --reload step flushes runtime state and rebuilds from permanent files. That is why a forgotten permanent typo can drop your session mid-reload. Use sudo firewall-cmd --panic-on only when you understand recovery via console—most VPS providers offer a web VNC or serial console for that rescue path.
For infrastructure-as-code teams, XML zone files in /etc/firewalld/zones/ can be templated through Ansible. Pair that with Ansible Vault for secrets so playbooks never commit office IP lists in plain text. On sister sites I deploy with Deployer 7 and GitLab CI—such as Notary Kathmandu—firewall baselines are applied before the first dep deploy hits production.
How should you harden firewalld zones on web and API servers?
Zone-based firewalling is not a substitute for application security. It reduces blast radius when PHP-FPM, Redis, or MySQL binds too broadly. These patterns recur on hosts I harden under support and maintenance contracts.
Public web tier
- Keep WAN in
public. Allowhttp,https, and nothing else by default. - Move SSH to key-only auth and restrict by source IP rich rule—not
0.0.0.0/0unless you accept daily brute-force noise. - Block database ports on
publiceven if MySQL listens only on localhost—defence in depth costs little. - Enable fail2ban for auth logs; firewalld and fail2ban coexist cleanly on Ubuntu 22/24.
Internal services and queues
Laravel Horizon, Redis, and PostgreSQL 18 should not answer on the public interface. Bind them to 127.0.0.1 or a private VPC address. If Redis must serve app nodes on a subnet, use an internal zone with explicit source CIDR and port.
sudo firewall-cmd --new-zone=app-internal --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=app-internal --add-source=10.20.0.0/16 --permanent
sudo firewall-cmd --zone=app-internal --add-port=6379/tcp --permanent
sudo firewall-cmd --reload API rate limiting belongs in nginx or the application layer—see API rate limiting and abuse prevention. Firewalld handles network trust boundaries; your Laravel middleware handles request semantics.
Logging and auditing
Rich rules can log dropped traffic for later review:
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port port="3306" protocol="tcp" log prefix="mysql-probe" drop' --permanent
sudo firewall-cmd --reload Check journalctl -u firewalld or /var/log/messages depending on distro. Correlate spikes with testing and optimization reviews when mystery latency appears after rule changes.
How does firewalld compare to UFW and raw iptables?
Teams on Ubuntu often ask whether to keep UFW or switch. Both wrap netfilter; the choice is workflow and semantics.
| Tool | Model | Best for | Learning curve |
|---|---|---|---|
| firewalld | Zones, services, dynamic runtime | RHEL stacks, multi-role servers, Ansible zones | Medium; concepts pay off at scale |
| UFW | Simple allow/deny rules | Single-purpose VPS, quick Laravel deploys | Low |
| iptables/nft | Direct chain editing | Custom NAT, complex forwarding, debug | High |
I keep firewalld on homogenous fleets where domain registration and hosting clients run mixed web and mail roles. UFW remains fine for a single WooCommerce 11.1 shop on a small droplet. Do not run firewalld and UFW as active managers simultaneously—they fight over the same kernel hooks.
Official references worth bookmarking: the firewalld project documentation and the Red Hat firewall configuration guide. For nftables internals, see the nftables wiki.
Before migrating from Apache-only to split nginx tiers, read Apache to nginx migration steps. Firewall zones should be updated in the same maintenance window as port and proxy changes. Split-brain configs cause the classic “works on curl localhost, fails from browser” ticket.
Generate strong deploy keys and rotation schedules with the password generator tool on this site. Firewall rules protect the perimeter; credential hygiene protects the session either way.
Key Takeaways
- Assign each interface and source CIDR to exactly one zone—
publicfor WAN, tighter zones for admin and internal traffic. - Test with runtime
firewall-cmdfirst, then write--permanentrules and--reloadonce SSH access is confirmed. - Never expose MySQL, Redis, or PostgreSQL ports on the public zone; bind locally or use internal zones with source restrictions.
- Rich rules beat global SSH allows when you can pin admin access to office or VPN CIDR blocks.
- Pick one firewall manager—firewalld or UFW—not both active on the same host.
- Automate zone XML through Ansible for fleets; manual drift causes weekend outages.
People Also Ask
What is the default firewalld zone?
The default zone catches traffic that does not match any interface or source binding. On most server installs it is public, which denies unsolicited incoming connections except explicitly allowed services. Confirm with sudo firewall-cmd --get-default-zone and change only when you understand the blast radius.
Does firewalld work on Ubuntu?
Yes. Install the firewalld package on Ubuntu 22/04 or 24/04, enable the systemd unit, and stop managing the same rules through UFW. Many production Laravel and WordPress 7.1 hosts on Ubuntu use firewalld when teams want zone semantics identical to their RHEL staging boxes.
How do I open a port temporarily in firewalld?
Run sudo firewall-cmd --zone=public --add-port=8080/tcp without --permanent. The rule lasts until reload or reboot. For lasting change, repeat with --permanent and run sudo firewall-cmd --reload.
Can firewalld and fail2ban work together?
They complement each other. Firewalld defines static trust boundaries by zone. fail2ban reacts to log patterns and bans abusive IPs dynamically. Install both on internet-facing SSH and mail ports; just ensure fail2ban’s backend targets the same firewall framework you actively use.
Ship safer servers with zone-based firewalling
firewalld: Zone-Based Firewalling gives you a vocabulary—public, internal, trusted—that matches how production traffic actually arrives. Start with a documented baseline, restrict SSH by source, keep databases off the WAN, and promote runtime tests to permanent rules only after validation. If you want help baselining Ubuntu VPS hosts that run web development stacks or enterprise applications, review the Adventure Third Pole Trek deployment work and customer reviews, then contact us for a firewall audit on your next release window.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

