Why firewalld blocks HTTP even when port 80 is open
I’ve run Fedora, CentOS, and RHEL for the past two decades, and firewalld is the default manager on those distros. It hides the nitty‑gritty nftables/iptables behind zones, services, and ports. That abstraction is handy, but it can also mask a subtle mismatch that ends up dropping HTTP traffic even though you’ve “opened” port 80. The culprit is almost always a services vs. ports trap.
Zones, services, and ports – a quick refresher
- Zones are trust levels (
public,internal,trusted, etc.). Each zone has its own rule set. - Services are pre‑defined bundles of ports, protocols, and optional metadata (e.g.,
httpmaps to80/tcp,httpsto443/tcp). They live in/usr/lib/firewalld/services/. - Ports are explicit port/protocol pairs that you can add or remove independently of services.
Running firewall-cmd --list-all gives you a snapshot of what’s active in a zone:
$ firewall-cmd --zone=public --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: dhcpv6-client ssh
ports: 80/tcp
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Notice how http is missing, but 80/tcp is there. If somewhere else a rule blocks the http service, the packet will still be dropped, even though the port rule says “allow”.
The services vs. ports trap
Firewalld’s default target for a zone is default – “allow all traffic that matches a rule, otherwise drop”. Add a port but forget the matching service, or let a higher‑priority rule deny the service, and you’ll get a silent drop.
Typical sequence:
firewall-cmd --add-port=80/tcp --permanent→ zone now knows about TCP 80.- A rich rule in the same zone says:
rule family="ipv4" source address="10.0.0.0/24" service name="http" reject. - A packet from
10.0.0.42hits the port rule (so it’s not dropped by that rule), but the deny rule for the service fires, and the packet is rejected.
Because the port rule is “allow”, you might think the traffic should pass, but the service rule wins.
How to hunt down the issue
-
Look at the zone’s rules
firewall-cmd --zone=public --list-allScan for any
rich rulesorservicesthat might clash. -
Inspect the service definition
firewall-cmd --service=http --infoThis tells you exactly which ports and protocols the
httpservice covers. If you’re running something on a non‑standard port (say,http-alton 8080), thehttpservice won’t match. -
Peek at the raw nftables ruleset
nft list ruleset | grep -i httpLook for a
droporrejectaction ontcp dport 80. That’s the real packet‑filtering logic. -
Test from a client
curl -I http://your-server/A
Connection timed outorConnection refusedscreams firewall block. A404or any HTTP status means the packet hit your web server. -
Check SELinux (if it’s on)
getsebool httpd_can_network_connectSELinux can block outbound traffic from
httpd, but it doesn’t affect inbound HTTP unless you’re doing reverse proxy tricks.
Fixing the misconfiguration
The cleanest fix is to add the service instead of, or in
See also
- Keep a 1 GB VPS from dying: a single cron job that purges /var/log/syslog after 30 days
- Stuck in emergency mode after adding an NTFS mount to /etc/fstab: how to fix it
- How to Stop Podman From Creating 0777 Permissions on Bind‑Mounted Directories When Running as a Non‑Root User
- Using ACLs to give a group write access to /var/www/html without changing ownership
- How to make sudo leave a trace: a single auditd rule and logrotate config