Skip to content

Firewalls — firewalld and ufw

A host firewall controls which network traffic is allowed in and out. This page explains the Linux firewall landscape, then shows practical management with firewalld (RHEL/AlmaLinux) and ufw (Ubuntu), finishing with a common "SSH + HTTP + HTTPS only" recipe.

Tested on

firewalld 1.2 on AlmaLinux 9.4; ufw 0.36 on Ubuntu 22.04. Both are front-ends over the kernel's packet-filtering framework.

The Linux firewall landscape

All Linux firewalling happens in the kernel via netfilter. The tools are just layers on top:

netfilter            ← kernel packet-filtering framework (the actual engine)
   └── nftables       ← modern rule syntax / userspace tool (nft)
        └── iptables  ← legacy tool, now a compatibility shim over nftables
   firewalld / ufw    ← high-level front-ends that generate the rules for you

You rarely write nft/iptables rules by hand on a modern system. firewalld (RHEL family) and ufw (Debian/Ubuntu) manage the rules with simple, persistent commands. Use the front-end your distro ships and avoid mixing it with hand-written rules.

firewalld (RHEL / AlmaLinux)

firewalld organises rules into zones. A zone is a trust level applied to one or more interfaces; the default is usually public. Within a zone you allow named services (e.g. ssh, http) or raw ports.

Check state and zones

# Is the firewall running?
sudo firewall-cmd --state
# running

# Which zones are active, and on which interfaces?
sudo firewall-cmd --get-active-zones
public
  interfaces: enp1s0
# See everything allowed in the default zone
sudo firewall-cmd --list-all
public (active)
  target: default
  interfaces: enp1s0
  services: cockpit dhcpv6-client ssh
  ports:
  ...

Permanent vs runtime

firewalld keeps a runtime config (lost on reload/reboot) and a permanent config (on disk). The standard pattern is to add a rule permanently, then reload:

# Allow the HTTP and HTTPS services permanently
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Apply the permanent rules to the running firewall
sudo firewall-cmd --reload

Tip

firewall-cmd --get-services lists every predefined service name (which maps to known ports). Use a service name when one exists; it's clearer than a raw port number.

Open a raw port

# Allow a custom TCP port (e.g. an app on 8080)
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Rich rules (fine-grained control)

Rich rules let you scope a rule to a source address, log it, or rate-limit it:

# Allow SSH only from the office subnet
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
sudo firewall-cmd --reload

Masquerading / NAT (brief)

When a host routes traffic for other machines (e.g. a gateway or VM host), enable masquerade so outbound packets are rewritten with the host's IP:

sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --reload

Removing rules

# Mirror the --add-* commands with --remove-*
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --permanent --remove-port=8080/tcp
sudo firewall-cmd --reload

ufw (Ubuntu)

Ubuntu's Uncomplicated Firewall wraps the same kernel machinery with a simpler syntax.

# Set sane defaults: block all incoming, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (the OpenSSH app profile maps to port 22)
sudo ufw allow OpenSSH

# Allow web ports
sudo ufw allow 80,443/tcp

# Turn the firewall on (enables at boot too)
sudo ufw enable

Allow SSH before enabling

Always ufw allow OpenSSH (or firewall-cmd --add-service=ssh) before enabling/locking down the firewall on a remote box. Otherwise you cut your own connection. Keep a second session open — see the SSH page.

# Review the active rules with their numbers
sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing)

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
80,443/tcp                 ALLOW IN    Anywhere
# Remove a rule (by name or by number from `status numbered`)
sudo ufw delete allow 80,443/tcp

Raw nft / iptables (for awareness)

You may still encounter direct rule tools — handy for reading what a front-end produced or working on a minimal system:

# nftables: list the full active ruleset
sudo nft list ruleset

# iptables (legacy syntax, shimmed over nftables): list rules with packet counts
sudo iptables -L -n -v

Don't combine hand-written nft/iptables rules with firewalld/ufw on the same host — let one tool own the ruleset to avoid conflicting or overwritten rules.

Common recipe: allow SSH + HTTP + HTTPS only

A typical web server should accept only these three inbound services.

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services
# http https ssh
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status verbose

Result

Inbound connections succeed only on 22, 80, and 443; everything else is dropped. Outbound traffic (updates, DNS, etc.) remains unrestricted.

Verify your work

# firewalld: confirm only the intended services are allowed
sudo firewall-cmd --list-all

# ufw: confirm status and rules
sudo ufw status verbose

# From another machine, confirm an allowed port is open and a blocked one is not
nc -vz <server-ip> 443    # should succeed
nc -vz <server-ip> 3306   # should time out / be refused

Expected: allowed services/ports appear in the listing, an external nc to 443 succeeds, and a blocked port (e.g. 3306) does not connect.

Summary

  • All Linux firewalling rides on kernel netfilter; nftables (with the legacy iptables shim) is the rule engine, and firewalld/ufw are the practical front-ends.
  • firewalld (RHEL/AlmaLinux) uses zones, services, and ports; add rules with --permanent --add-service/--add-port, then --reload. Rich rules scope by source; --add-masquerade enables NAT.
  • ufw (Ubuntu) is simpler: ufw default deny incoming, ufw allow OpenSSH, ufw allow 80,443/tcp, ufw enable.
  • Always permit SSH before locking down a remote host, and keep a second session open.
  • The web-server recipe allows only SSH + HTTP + HTTPS; verify with --list-all/ufw status and an external nc probe.

See SSH for securing remote access and the Server Hardening Checklist for the full lockdown.

Test yourself