Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

firewalld: Zone-Based Firewalling

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 Zone Modeleth0 WANPublic IPtun0 VPNAdmin tunnelSource IP203.0.113.0/24public zonehttp, https, sshtrusted zoneaccept allinternal zonemysql from VPNnftableskernel rulesetruntime + permanent
firewalld: Zone-Based Firewalling maps interfaces and source CIDRs into zones before nftables applies allow or deny rules.

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.

ZoneDefault targetTypical useWeb server pattern
dropDROP incoming; outbound allowedSilenced interfacesUnused NIC, honeypot research
blockREJECT incoming with ICMPVisible denialStaging host you want probed to fail fast
publicDROP; explicit allows onlyUntrusted networksDefault WAN on VPS—HTTP, HTTPS, rate-limited SSH
externalDROP; masquerade enabledNAT gatewaySmall office router forwarding to LAN
dmzDROP; limited accessSemi-isolated appsReverse proxy tier, no direct DB
workDROP; some trustCorporate LANInternal GitLab runner
homeDROP; broader trustHome labDev machine with Samba
internalDROP; more servicesPrivate subnetsMySQL/Redis reachable only inside VPC
trustedACCEPT allFully trusted pathsWireGuard 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/.

  1. Add HTTP and HTTPS to the public zone: sudo firewall-cmd --zone=public --add-service=http --permanent and the same for https.
  2. Restrict SSH to a management source using a rich rule instead of global ssh service if your provider allows source filtering.
  3. Reload: sudo firewall-cmd --reload.
  4. 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.

Packet Evaluation FlowIncomingMatch source?Source zoneMatch interface?Interface zoneApply zone rulesACCEPTor DROPNo match falls back to default zone
firewalld evaluates source bindings first, then interface zones, then applies that zone’s service and rich-rule policy.

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. Allow http, https, and nothing else by default.
  • Move SSH to key-only auth and restrict by source IP rich rule—not 0.0.0.0/0 unless you accept daily brute-force noise.
  • Block database ports on public even 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.

Production Web Server ZonesInternetpublic zone:80 :443 onlyUbuntu VPSApache + PHP 8.5Laravel 13 appRedis localhostMySQL 9.7deploy zoneCI runner IPOffice IPSSH rich ruleGitLab CIDeployer SSHBlocked:3306 :6379from WANTypical Laravel VPS: public web, restricted admin, no DB on WAN
A production Laravel host using firewalld: Zone-Based Firewalling to expose HTTP while blocking database ports from the internet.

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.

ToolModelBest forLearning curve
firewalldZones, services, dynamic runtimeRHEL stacks, multi-role servers, Ansible zonesMedium; concepts pay off at scale
UFWSimple allow/deny rulesSingle-purpose VPS, quick Laravel deploysLow
iptables/nftDirect chain editingCustom NAT, complex forwarding, debugHigh

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.

Choose Your Firewall ToolNew Linux server?Multi-NIC / VPNUse firewalldZone-Based rulesSingle public IPUFW is fineSimple allow listAnsible fleet?Template zone XMLNeed NAT?Consider nftablesDocument rules before first deploy
Decision guide: firewalld: Zone-Based Firewalling wins when interfaces, VPNs, or source CIDRs need different trust levels on one host.

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—public for WAN, tighter zones for admin and internal traffic.
  • Test with runtime firewall-cmd first, then write --permanent rules and --reload once 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

Firewalld assigns each network interface or source IP to a named zone, then applies that zone’s allowed services, ports, and rich rules to incoming traffic—so one server can treat the internet and a VPN subnet differently.

The default zone is usually public. It handles traffic that does not match any interface or source binding and drops unsolicited inbound connections except explicitly allowed services. Confirm with sudo firewall-cmd --get-default-zone.

Run sudo firewall-cmd --zone=public --add-port=8080/tcp without --permanent. The rule applies immediately and lasts until reload or reboot.

Firewalld ships predefined zones ordered by trust. Public drops by default and allows only explicit services—correct for WAN-facing Laravel or WordPress hosts. Trusted accepts all traffic and suits WireGuard admin paths used sparingly. Internal permits more services on private subnets where MySQL or Redis might be reachable. Block rejects with ICMP; drop silently denies. External supports masquerade for NAT gateways. DMZ suits semi-isolated tiers like reverse proxies. Promoting everything to trusted because rules feel inconvenient undoes hardening work. Custom zones named after roles—web, db-replica—help when predefined labels confuse small teams.

Install firewalld, enable the systemd unit, and verify with sudo firewall-cmd --state. Document sudo firewall-cmd --get-active-zones and --list-all before changing anything so you can roll back if SSH disappears. Bind the primary NIC to public with --change-interface and --permanent, replacing eth0 with your actual device from ip -br link—cloud images often use ens3 or enp0s3, and a wrong name fails silently. Add http and https services to public, restrict SSH via rich rules when possible, then sudo firewall-cmd --reload. Confirm runtime and permanent views match with --list-all after every change.

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.

Runtime rules apply immediately but vanish on reload unless copied to permanent storage. Permanent rules live under /etc/firewalld/ and load at boot. Test with runtime first when one SSH session away from lockout: add a service without --permanent, confirm with --list-services, then repeat with --permanent and reload. The reload step flushes runtime state and rebuilds from permanent files—a typo there can drop your session mid-reload. For infrastructure-as-code teams, XML zone files in /etc/firewalld/zones/ can be templated through Ansible with Vault for office IP lists.

All three wrap netfilter; the choice is workflow and semantics. Firewalld offers zones, named services, and a dynamic runtime—best for RHEL stacks, multi-role servers, and Ansible-managed fleets. UFW suits single-purpose VPS quick deploys with a lower learning curve. Raw iptables or nftables fits custom NAT, complex forwarding, and debugging but demands more expertise. Firewalld wins when interfaces, VPNs, or source CIDRs need different trust levels on one host. 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.

They complement each other cleanly on Ubuntu 22/24. 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. Ensure fail2ban’s backend targets the same firewall framework you actively use so dynamic bans and zone rules do not conflict.

Keep WAN in public with http and https only. Restrict SSH via source IP rich rules—not open 0.0.0.0/0 unless you accept daily brute-force noise. Block database ports on public even when MySQL listens only on localhost. Bind Laravel Horizon, Redis, and PostgreSQL 18 to 127.0.0.1 or private VPC addresses; if Redis must serve app nodes, use an internal zone with explicit source CIDR and port. API rate limiting belongs in nginx or Laravel middleware—firewalld handles network trust boundaries only. Enable fail2ban alongside firewalld. Rich rules can log dropped probes to journalctl for later review.

Prefer a rich rule over globally allowing the ssh service when your provider supports source filtering. Example: sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.50/32" service name="ssh" accept' --permanent, then reload. Pair SSH key-only authentication with this network restriction. Office or VPN CIDR blocks beat exposing SSH to the entire internet. Document the rule before reloading so console access via your VPS provider’s VNC or serial console is ready if something goes wrong.

No. Pick one active firewall manager. Both firewalld and UFW hook into the same netfilter layer and will fight over kernel rules, producing split-brain configs and tickets where curl localhost works but browser access fails. On Ubuntu, install firewalld explicitly and stop managing duplicate rules through UFW. Homogeneous fleets benefit from consistent tooling—firewalld on mixed web and mail roles, UFW only when a single-purpose host does not need zone semantics.

Use --add-source when admin or monitoring traffic arrives on the same interface as public web traffic. Example: sudo firewall-cmd --zone=internal --add-source=10.10.0.0/24 --permanent, then reload. A monitoring subnet can land in internal while the rest of the world stays in public. Pair source zoning with rich rules for specific ports—such as Prometheus node_exporter on 9100/tcp—rather than exposing exporters to the open internet. Firewalld evaluates source bindings first, then interface zones, then applies that zone’s service and rich-rule policy.

Firewalld evaluates source bindings first, then interface zones, then applies that zone’s service and rich-rule policy. This matters when admin traffic and public web traffic share one NIC: mapping a CIDR to internal changes trust before the interface’s public zone rules apply. Understanding this order prevents placing database ports in the wrong profile and explains why source-based zoning is the right tool when traffic arrives through a single interface from different networks.

Firewalld sits above nftables on current stacks, or iptables on older ones. You edit zones with firewall-cmd; the daemon compiles zone policy into kernel rules. That separation keeps day-two changes safer for teams running Ansible playbooks for PHP server provisioning who want idempotent firewall tasks. For nftables internals beyond zone editing, the nftables wiki documents the underlying chains firewalld generates at runtime and reload.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: