Securing CentOS servers with firewalld rules you can trust
System administrators across Sydney, Melbourne and Brisbane rely on CentOS Stream hosts running in Equinix and NextDC facilities, often fronting apps that exchange traffic through Australia's NBN and domestic peering. Hardening those boxes with a coherent firewall ruleset is not optional — the Australian Cyber Security Centre's Essential Eight guidance treats network segmentation and controlled traffic flow as baseline hygiene. firewalld, the default zone-based firewall on RHEL-derived distributions, lets administrators express policy without writing raw netfilter chains by hand.
The trick with firewalld is treating zones as semantic containers rather than free-form rule lists. Each interface or source address belongs to a zone, and every zone carries its own permitted services, ports and rich rules. Once that mental model clicks, migrating a server from a permissive default install to a locked-down policy becomes a matter of moving an interface into the right zone and documenting the exceptions.
Understanding zones and how CentOS uses them
A zone bundles permitted services and a default trust level. The bundled public zone blocks unsolicited inbound traffic, allowing only replies to outbound connections plus commonly needed services such as SSH. dmz permits only explicitly added ports, trusted accepts everything and works for back-end links between controlled hosts, and internal represents a LAN environment. block drops incoming packets silently while permitting outbound replies.
When a CentOS host has multiple NICs — say, one facing the internet and another connecting to a private peering port in a NextDC cage — bind each interface to a deliberately chosen zone. Inspect assignments with firewall-cmd --get-active-zones, then move an interface with --zone=dmz --change-interface=eth1. No interface should sit in public unless it actually needs to serve the open internet.
Installing and starting firewalld
firewalld is usually preinstalled on CentOS Stream. Confirm with rpm -q firewalld; if absent, install it via sudo dnf install firewalld. Enable and start the daemon so the ruleset survives reboots, which matters for hosts enrolled in ACSC-aligned configuration management. Run sudo systemctl enable --now firewalld and verify with systemctl status firewalld.
List active zones and rules with firewall-cmd --list-all. The default zone for new interfaces is normally public, a reasonable starting point for internet-facing servers. Resist leaving that output untrimmed in production: a clean, commented ruleset tells the next on-call engineer exactly what is allowed. Brushing up on the linux-commands that drive these checks pays off whenever a service regression appears.
Adding services and ports for everyday workloads
firewalld ships with predefined service definitions for SSH, HTTP, HTTPS, DNS and SMTP. Prefer those over raw port numbers, because the predefined entry links a service name to its port, protocol and any associated modules. To allow HTTPS on the public zone, run sudo firewall-cmd --zone=public --add-service=https --permanent and reload with firewall-cmd --reload. Without --permanent, the change lives only in runtime and disappears after a reboot.
For services that lack a predefined entry — a self-hosted Mattermost instance or a custom gRPC API — create a new service definition in /etc/firewalld/services/. Each .xml file describes a short name, a description and one or more <port> entries. Reload after dropping the file in place, then add the service to the desired zone so bespoke ports stay out of imperative sequences that are easy to forget.
Building rich rules for scoped access
Rich rules give administrators the same flexibility as raw iptables without leaving the firewalld abstraction. They shine when an Australian business needs to lock an admin port to a specific office or carrier IP range. To permit MySQL from the AARNet research network on the internal zone, write a rule that matches both source address and destination port:
sudo firewall-cmd --zone=internal --add-rich-rule='rule family=ipv4 source address=203.0.113.0/24 port port=3306 protocol=tcp accept'
Combine --add-rich-rule with --permanent so the rule survives a reboot, then verify with firewall-cmd --zone=internal --list-rich-rules. When tightening rules for web server configuration front-ends, mirror the source-restriction pattern on the management plane and keep application traffic on a narrowly defined zone.
Port forwarding on a single public address
Behind a single public IPv4 address, a typical Sydney or Perth rack houses several virtualised services sharing inbound traffic. firewalld supports port forwarding and masquerading, redirecting inbound requests on one port to an internal destination. To forward HTTP from the public interface to a backend on the LAN, mark the public zone for masquerade and add a forward rule:
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=10.0.0.20 --permanent
sudo firewall-cmd --reload
This pattern is invaluable for organisations consolidating services onto fewer addresses, an increasingly common need as IPv4 allocations in Australia grow scarce. When securing the public-facing flow, pair every forward with a matching rich rule that restricts access by source address, and audit open forwards quarterly.
Logging, auditing and Essential Eight alignment
firewalld can log denied packets, which is invaluable when diagnosing whether a failure is policy or application. Enable logging with firewall-cmd --set-log-denied=all and inspect /var/log/messages or journald for AUDIT and DROP lines. Pair the logging with a daily diff against the checked-in /etc/firewalld/ tree, so drift between servers is obvious during review.
For organisations pursuing ISO 27001 or the Australian Signals Directorate's Essential Eight, document each zone, its binding interface and the rationale for every service or rich rule. A small table per host in the internal wiki is enough. Review the document quarterly and after every change request, matching the cadence ACSC recommends for systems on the open internet.
| Zone | Default behaviour for inbound traffic | Typical use on a CentOS host |
|---|---|---|
| trusted | Accept all | Private back-end link between two hosts |
| public | Allow only replies and listed services | Internet-facing NIC serving a public website |
| dmz | Allow only explicitly added ports | Front-end NIC isolating web traffic |
| internal | Most services trusted | LAN side of a server in an office network |
| block | Silently drop unsolicited packets | Hardened internet NIC on a bastion host |
Living with a hardened host day to day
A firewalld ruleset is not a one-off artefact. Back it up by versioning /etc/firewalld/ alongside other configuration in Ansible or Git, and rehearse restoration on a clone before any production rollout.
Commands worth memorising during incident response:
firewall-cmd --list-all-zonesfor an at-a-glance view of every zonefirewall-cmd --panic-onto drop all packets instantly during a live incidentfirewall-cmd --runtime-to-permanentto lock in ad-hoc runtime changesfirewall-cmd --info-zone=publicfor a focused view of one zone's rules
Pre-flight checks before going live:
- Confirm the default zone is appropriate for each NIC
- Verify each allowed service is still needed and documented
- Test that reloads do not drop long-lived database connections
- Ensure
--permanentand runtime rulesets match before declaring done
Treat every firewalld edit as code: write it, review it, push it through the same pipeline that delivers application code, and let the next engineer rebuild a working ruleset from version control. Australian data centres benefit because this discipline survives staff handovers, hosting provider changes and mid-cycle patching windows.