When firewalld blocks HTTP traffic even though port 80 is open: the services vs ports trap

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., http maps to 80/tcp, https to 443/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:

  1. firewall-cmd --add-port=80/tcp --permanent → zone now knows about TCP 80.
  2. A rich rule in the same zone says: rule family="ipv4" source address="10.0.0.0/24" service name="http" reject.
  3. A packet from 10.0.0.42 hits 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

  1. Look at the zone’s rules

    firewall-cmd --zone=public --list-all
    

    Scan for any rich rules or services that might clash.

  2. Inspect the service definition

    firewall-cmd --service=http --info
    

    This tells you exactly which ports and protocols the http service covers. If you’re running something on a non‑standard port (say, http-alt on 8080), the http service won’t match.

  3. Peek at the raw nftables ruleset

    nft list ruleset | grep -i http
    

    Look for a drop or reject action on tcp dport 80. That’s the real packet‑filtering logic.

  4. Test from a client

    curl -I http://your-server/
    

    A Connection timed out or Connection refused screams firewall block. A 404 or any HTTP status means the packet hit your web server.

  5. Check SELinux (if it’s on)

    getsebool httpd_can_network_connect
    

    SELinux 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