KB
Security & Hardening

Linux Firewall Configuration: firewalld and UFW

5 min read1084 words15 code blocks

At a glance#

  • Purpose: Configure host firewalls on Linux servers using firewalld (RHEL family) and UFW (Debian/Ubuntu).
  • Applies to: RHEL/Rocky/AlmaLinux 7+ and Ubuntu/Debian 18.04+.
  • Risk: High — a wrong rule locks you out of a remote server instantly.
  • Time: 30 minutes.

Overview#

Both tools are front ends to netfilter. Use whichever ships with the distribution rather than mixing them:

firewalldUFW
Default onRHEL, Rocky, AlmaLinuxUbuntu, Debian
ModelZones with assigned interfacesSimple ordered rule list
Persistence--permanent then --reloadImmediate and persistent
Backendnftables (modern) / iptablesiptables / nftables
Warning: Every command below can lock you out of a remote server. Before you start, open a second SSH session and leave it connected, and have console or IPMI access available. Add the SSH rule before enabling the firewall, never after.

Before you start#

  • Root or sudo access.
  • A list of ports the server genuinely needs to expose.
  • A second SSH session already open.
  • Console access as a fallback.

Record what is currently listening, so you know what actually needs allowing:

bash
sudo ss -tulpn

firewalld (RHEL family)#

Basics#

bash
sudo systemctl enable --now firewalld
sudo firewall-cmd --state
sudo firewall-cmd --list-all
sudo firewall-cmd --get-active-zones

The permanent/runtime split#

This trips people up constantly. firewalld holds two rule sets:

  • Runtime — active now, lost on reload or reboot.
  • Permanent — written to disk, applied on reload.
bash
# runtime only - disappears on reload
sudo firewall-cmd --add-service=http

# permanent - not active until reloaded
sudo firewall-cmd --permanent --add-service=http

# apply permanent rules to the running firewall
sudo firewall-cmd --reload
Note: The runtime-only form is genuinely useful. Add a risky rule to runtime, test it, and if it locks you out, a reboot or reload reverts it. Only commit with --permanent once proven.

Common operations#

bash
# by service name
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=https

# by port
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --add-port=9000-9100/tcp

# remove
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --permanent --remove-port=8080/tcp

# what services are defined
sudo firewall-cmd --get-services

sudo firewall-cmd --reload

Restricting a port to specific sources#

Rich rules handle "only the office may reach this":

bash
# allow MySQL from one subnet only
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4" source address="192.168.10.0/24"
  port port="3306" protocol="tcp" accept'

# block a specific host entirely
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4" source address="203.0.113.55" drop'

# log and reject
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4" source address="198.51.100.0/24"
  port port="22" protocol="tcp" log prefix="SSH-BLOCKED " level="warning" reject'

sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules

Zones#

bash
# what zone is an interface in
sudo firewall-cmd --get-zone-of-interface=eth0

# move an interface to a different zone
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1

# add a source network to a zone
sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.10.0/24

sudo firewall-cmd --reload
ZoneTypical use
publicDefault — internet-facing
internalTrusted LAN
trustedEverything allowed
dropEverything dropped silently

UFW (Ubuntu / Debian)#

Basics#

bash
sudo ufw status verbose
sudo ufw status numbered

Enabling safely#

Order matters. Allow SSH first:

bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable
Warning: Running ufw enable before allowing SSH drops your session immediately and needs console access to recover. UFW warns about this — do not dismiss it.

Common operations#

bash
# by service name
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https

# by port
sudo ufw allow 8080/tcp
sudo ufw allow 9000:9100/tcp

# from a specific source
sudo ufw allow from 192.168.10.0/24 to any port 3306 proto tcp

# on a specific interface
sudo ufw allow in on eth1 to any port 5432

# deny
sudo ufw deny from 203.0.113.55

# rate limit - blocks an IP after 6 connections in 30 seconds
sudo ufw limit ssh

ufw limit ssh is worth applying on any internet-facing host; it stops most brute-force noise without extra tooling.

Removing rules#

bash
sudo ufw status numbered
sudo ufw delete 3

# or by specification
sudo ufw delete allow 8080/tcp

Logging#

bash
sudo ufw logging on
sudo ufw logging medium
sudo tail -f /var/log/ufw.log

Verification#

Confirm the ruleset:

bash
# firewalld
sudo firewall-cmd --list-all

# ufw
sudo ufw status verbose

Then test from another machine — testing from the server itself bypasses the firewall entirely and proves nothing:

bash
# from a different host
nc -zv server.example.com 22
nc -zv server.example.com 443
nc -zv server.example.com 3306    # should fail if restricted

nmap -Pn -p 1-1000 server.example.com

Finally, open a new SSH session before closing your existing one. That is the only real proof you have not locked yourself out.

Confirm the rules survive a reboot:

bash
sudo reboot
# after it returns
sudo firewall-cmd --list-all     # or: sudo ufw status verbose

Rollback#

If you lose access, connect via console and:

bash
# firewalld - drop runtime changes back to the last permanent set
sudo firewall-cmd --reload

# or stop the firewall entirely while you fix it
sudo systemctl stop firewalld

# ufw
sudo ufw disable

Then correct the rules and re-enable.

Note: Stopping the firewall is an emergency measure, not a fix. Re-enable it as soon as the rule is corrected — a server left with no firewall after a late-night incident is how hosts get compromised.

Troubleshooting#

SymptomCause and fix
Locked out after enablingSSH not allowed before enabling. Recover via console, add the rule, re-enable.
Rule added but no effect (firewalld)--permanent used without --reload. Run firewall-cmd --reload.
Rules gone after reboot (firewalld)Added to runtime only. Re-add with --permanent.
Port open in the firewall but still unreachableNothing listening, or bound to 127.0.0.1 only. Check ss -tulpn.
Works locally, fails remotelyUpstream network firewall, or the service binds only to loopback.
firewalld and iptables rules conflictBoth are managing netfilter. Use one; disable iptables.service.
UFW allows traffic that should be deniedRule order — first match wins. Check ufw status numbered.
Docker containers bypass UFWDocker writes its own iptables rules. Set iptables: false in daemon.json or manage exposure via Docker.
Service unreachable despite correct rules on RHELSELinux may block the port binding. Check with sudo ausearch -m avc -ts recent.