1 of 2

FirewalldLesson 1 of 2

Zones, services and ports

Understand firewalld's model, open a service or port, and make the change permanent without breaking your own access.

  • firewall-cmd
  • --add-service
  • --add-port
  • --permanent
  • --reload
  • --runtime-to-permanent
  • --list-all
  • --get-active-zones
  • --set-default-zone

45 min5 exercises

After this lesson you can

  • Read the active zone and its rules, and tell which are runtime and which are permanent.
  • Allow and remove a service or a port, runtime-only and permanently, and apply them with --reload.
  • Change the default zone and move an interface between zones.

The idea

firewalld manages the kernel’s packet filter (nftables on RHEL 10) through a simpler model. You do not write packet rules; you decide what is allowed in for each part of the network.

Zones

A zone is a trust level with its own list of allowed services and ports. Network interfaces and source addresses are assigned to zones; a packet is judged by the rules of its zone.

ZoneMeaning (default behaviour)
dropSilently discard everything incoming
blockReject everything incoming with a message
publicUntrusted network; only selected services (ssh, cockpit, dhcpv6-client). The default zone
internal, work, homeMore trusted networks; a few more services
trustedAllow everything

Each interface belongs to exactly one zone; an interface not otherwise assigned uses the default zone.

Services and ports

  • A service is a named bundle of ports and protocols: ssh is TCP 22, http is TCP 80, https is TCP 443, nfs is TCP 2049. firewall-cmd --get-services lists them and --info-service=NAME shows what one contains.
  • A port rule is PORT/PROTOCOL such as 8081/tcp for something without a service name.

Prefer services when one exists (clearer, and covers multiple ports), and a port when it doesn’t.

Runtime and permanent

firewalld keeps two configurations:

ConfigurationChanged withSurvives reload / reboot
RuntimeCommands without --permanentNo
PermanentCommands with --permanentYes, but not active until --reload

The classic mistake is adding a rule without --permanent (works now, gone after a reboot) or with it and no --reload (saved, but not yet active). Two safe patterns:

  1. firewall-cmd --permanent --add-service=http then firewall-cmd --reload
  2. firewall-cmd --add-service=http (test it) then firewall-cmd --runtime-to-permanent

Nothing is allowed until you say so, except what the zone already includes. Remember the service itself must be running and listening: the firewall only decides whether packets reach it.

Try it

sudo systemctl enable --now firewalld
firewall-cmd --state
firewall-cmd --list-all
running
public (default, active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

Allow a service

sudo dnf install -y httpd
echo 'hello from node1' | sudo tee /var/www/html/index.html
sudo systemctl enable --now httpd
curl -s -m3 http://node1 || echo "blocked"     # from node2
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
curl -s http://node1                             # from node2
blocked
success
success
hello from node1

A blocked packet is dropped without a reply, so curl waits and times out (-m3 limits the wait to 3 seconds). If instead you see Connection refused, the firewall let the packet through and nothing is listening: check ss -tlnp.

Open and close a port

sudo firewall-cmd --permanent --add-port=8081/tcp
sudo firewall-cmd --reload
firewall-cmd --list-ports
sudo firewall-cmd --permanent --remove-port=8081/tcp
sudo firewall-cmd --reload
success
success
8081/tcp
success
success

Runtime versus permanent

sudo firewall-cmd --add-service=https
firewall-cmd --list-services
sudo firewall-cmd --permanent --list-services
sudo firewall-cmd --runtime-to-permanent
success
cockpit dhcpv6-client http https ssh
cockpit dhcpv6-client http ssh
success

Zones for interfaces

firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
sudo firewall-cmd --permanent --zone=internal --add-service=nfs
sudo firewall-cmd --reload
firewall-cmd --get-active-zones
public
  interfaces: eth0
success
success
success
internal
  interfaces: eth1
public
  interfaces: eth0

firewall-cmd --set-default-zone=internal changes the zone for all interfaces without an explicit assignment, immediately and permanently. If a connection is managed by NetworkManager, the zone is best stored in the connection: nmcli connection modify NAME connection.zone internal.

Going further

firewall-cmd --list-all-zones shows every zone, and --get-zones lists the names. --add-service accepts a timeout for temporary rules: --add-service=http --timeout=5m. --panic-on drops all traffic including established connections (and --panic-off ends it). firewall-cmd --check-config validates permanent files, and the files themselves live in /etc/firewalld/zones/ as XML. firewall-cmd --direct and nft list ruleset expose the underlying nftables rules, useful for debugging but not for normal configuration. Custom services are described in /etc/firewalld/services/NAME.xml.

Practice

0 of 5 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    Is the firewall on, and what does it allow?

    Check that firewalld is running and enabled at boot. Show the default zone, the active zones and what the active zone allows.

    Check your result

    firewall-cmd --state prints running. firewall-cmd --get-default-zone prints a zone (usually public). --list-all shows services: cockpit dhcpv6-client ssh or similar.

    Hint

    systemctl is-enabled firewalld, firewall-cmd --get-default-zone, --get-active-zones, --list-all.

    Solution
    sudo systemctl enable --now firewalld
    firewall-cmd --state
    firewall-cmd --get-default-zone
    firewall-cmd --get-active-zones
    firewall-cmd --list-all
  2. Practice

    Open a web server

    Install and start httpd. From another host curl http://node1 fails. Allow the http service in the default zone permanently so it also applies right now, and prove it from node2.

    Check your result

    firewall-cmd --list-services shows http (runtime) and --permanent --list-services shows it too. curl -s http://node1 | head -1 from node2 returns the page.

    Hint

    sudo firewall-cmd --permanent --add-service=http then sudo firewall-cmd --reload. Without --permanent the rule disappears at reload or reboot.

    Solution
    sudo dnf install -y httpd
    echo 'hello from node1' | sudo tee /var/www/html/index.html
    sudo systemctl enable --now httpd
    sudo firewall-cmd --permanent --add-service=http
    sudo firewall-cmd --reload
    firewall-cmd --list-services
    # on node2
    curl -s http://node1
  3. Practice

    Open a non-standard port

    A custom application listens on TCP port 8081 (simulate with python3 -m http.server 8081 & from /tmp). Open that port permanently in the default zone, test it from node2, then close it and confirm it is blocked again.

    Check your result

    firewall-cmd --list-ports shows 8081/tcp while open; curl http://node1:8081 works from node2. After --remove-port and reload, it times out.

    Hint

    --add-port=8081/tcp. Specify the protocol. A blocked port gives a timeout, a closed port with nothing listening gives Connection refused.

    Solution
    (cd /tmp && python3 -m http.server 8081 >/dev/null 2>&1 &)
    sudo firewall-cmd --permanent --add-port=8081/tcp
    sudo firewall-cmd --reload
    firewall-cmd --list-ports
    # from node2: curl -sI http://node1:8081 | head -1
    sudo firewall-cmd --permanent --remove-port=8081/tcp
    sudo firewall-cmd --reload
  4. Practice

    Runtime versus permanent

    Add the https service without --permanent, list runtime and permanent services to see the difference, then save the runtime configuration permanently in one command without retyping it. Finally, reload and show what happens to a runtime-only rule.

    Check your result

    --list-services shows https; --permanent --list-services does not. After --runtime-to-permanent both do. A runtime-only rule vanishes on --reload.

    Hint

    firewall-cmd --runtime-to-permanent writes the whole current runtime config to disk.

    Solution
    sudo firewall-cmd --add-service=https
    firewall-cmd --list-services
    sudo firewall-cmd --permanent --list-services
    sudo firewall-cmd --runtime-to-permanent
    sudo firewall-cmd --permanent --list-services
    sudo firewall-cmd --remove-service=https --permanent
    sudo firewall-cmd --reload
  5. Challenge

    Put a network card in a different zone

    Move the second interface eth1 (storage network) into the internal zone permanently and allow nfs there only, leaving the public zone without NFS. Show both zones' rules and the interface assignment.

    Check your result

    firewall-cmd --get-active-zones lists internal with eth1. --zone=internal --list-services includes nfs and --zone=public --list-services does not.

    Hint

    --permanent --zone=internal --change-interface=eth1 (or via NetworkManager: nmcli con mod storage-net connection.zone internal), then --zone=internal --add-service=nfs.

    Solution
    sudo nmcli connection modify storage-net connection.zone internal
    sudo nmcli connection up storage-net
    sudo firewall-cmd --permanent --zone=internal --add-service=nfs
    sudo firewall-cmd --reload
    firewall-cmd --get-active-zones
    firewall-cmd --zone=internal --list-services
    firewall-cmd --zone=public --list-services