Linux Administration Lesson 29 – Firewall Management | Dataplexa
Section III — Networking, Security & Storage

Firewall Management

In this lesson

Linux firewall architecture ufw firewalld iptables fundamentals Troubleshooting rules

A firewall is the gatekeeper between a Linux server and the network — it inspects every packet and decides whether to allow or drop it based on a set of rules. A properly configured firewall enforces the principle of least privilege at the network layer: only the ports that a server needs to expose are open, and everything else is silently dropped. Linux provides three main tools for managing firewall rules, each suited to different distributions and use cases.

The Linux Firewall Architecture

All Linux firewall tools — regardless of whether you use ufw, firewalld, or iptables — ultimately write rules into the Linux kernel's netfilter framework. Understanding this layered architecture explains why different tools can coexist and conflict, and why a rule added by one tool might be overwritten by another.

ufw Debian/Ubuntu firewalld RHEL / Rocky / Fedora iptables / nftables low-level, all distros translate to rules nftables / iptables rule tables filter · nat · mangle · raw — chains: INPUT OUTPUT FORWARD Linux Kernel — netfilter hooks NF_INET_PRE_ROUTING · NF_INET_LOCAL_IN · NF_INET_LOCAL_OUT · NF_INET_FORWARD Network stack — packet accepted / dropped / modified

Fig 1 — All firewall tools write rules into the kernel's netfilter framework — only use one tool per system

Analogy: The firewall tools are like different dashboards for the same building's security system. Whether you use the phone app, the wall panel, or the master console, you are all arming and disarming the same physical sensors and locks. Running two dashboards simultaneously creates conflicts — one might unlock a door the other just locked. Pick one tool and use it exclusively.

ufw — Uncomplicated Firewall (Debian / Ubuntu)

ufw (Uncomplicated Firewall) is the standard firewall interface on Ubuntu and Debian. It wraps iptables/nftables with a simpler command syntax designed for server administrators who need to manage common firewall rules without learning the full iptables command language. Its default-deny model is the correct starting point for any server.

# Check ufw status
sudo ufw status verbose

# Enable ufw with default-deny inbound, allow outbound
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

# Allow specific services by name (reads from /etc/services)
sudo ufw allow ssh           # port 22/tcp
sudo ufw allow http          # port 80/tcp
sudo ufw allow https         # port 443/tcp

# Allow by port number and protocol
sudo ufw allow 8080/tcp
sudo ufw allow 53/udp

# Allow from a specific IP address
sudo ufw allow from 192.168.1.0/24
sudo ufw allow from 10.0.0.5 to any port 5432

# Allow on a specific interface only
sudo ufw allow in on eth0 to any port 80

# Deny a port (explicit deny — useful to override a broader allow)
sudo ufw deny 23/tcp

# Delete a rule by number
sudo ufw status numbered
sudo ufw delete 3

# Delete a rule by specification
sudo ufw delete allow 8080/tcp

# Reload rules without disabling the firewall
sudo ufw reload

What just happened? The status output shows a well-configured server firewall — three services open to the world (SSH, HTTP, HTTPS) and PostgreSQL restricted to a single trusted IP (10.0.0.5). Note that ufw automatically created IPv6 rules alongside IPv4 rules. The Default: deny (incoming) line confirms that any port not explicitly listed is silently dropped — no rule needed for each individual closed port.

firewalld — RHEL, Rocky Linux, and Fedora

firewalld is the default firewall on RHEL, Rocky Linux, CentOS Stream, and Fedora. It introduces the concept of zones — named trust levels assigned to network interfaces or source IP ranges. Each zone has a set of allowed services, so you can define "this interface is in the public zone where only SSH and HTTPS are allowed" and "this interface is in the internal zone where all services are trusted."

drop

All inbound traffic dropped silently. No response. Most restrictive — use for untrusted networks.

public

Default for internet-facing interfaces. Allows only SSH and DHCPv6 by default. Add services explicitly.

internal

For internal network interfaces. Allows more services — SSH, samba, mdns, ipp-client.

trusted

All inbound connections accepted. Use only for loopback or fully trusted private networks.

# Check firewalld status and active zones
sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones

# List all rules in the default zone
sudo firewall-cmd --list-all

# List all rules in a specific zone
sudo firewall-cmd --zone=public --list-all

# Add a service to the public zone (runtime — lost on reload)
sudo firewall-cmd --zone=public --add-service=https

# Add permanently (survives reboot) — always use --permanent for production
sudo firewall-cmd --zone=public --add-service=https --permanent

# Add a specific port
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent

# Remove a service
sudo firewall-cmd --zone=public --remove-service=telnet --permanent

# Reload to apply permanent rules
sudo firewall-cmd --reload

# Allow access from a specific IP range to a zone
sudo firewall-cmd --zone=internal --add-source=10.0.0.0/24 --permanent

# Block a specific IP (rich rule)
sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="185.220.101.42" reject' --permanent
sudo firewall-cmd --reload

What just happened? The active zones show a well-structured setup: eth0 is in the public zone with only necessary services allowed, and the internal zone is assigned to the 10.0.0.0/24 source range — all traffic from that network gets the more permissive internal policy automatically, without needing to specify the interface. The zone model makes multi-interface servers significantly easier to manage than individual iptables rules.

iptables Fundamentals

iptables is the low-level tool that both ufw and firewalld generate rules for. Understanding it is important because: iptables rules appear in security audit outputs, some automation tools write iptables rules directly, and reading iptables output is the only way to see the complete, unabstracted state of the firewall. On modern systems, iptables is being replaced by nftables, but the concepts are identical.

iptables — Tables, Chains, and Targets
Concept Values Purpose
Tables filter nat mangle raw filter — allow/drop packets. nat — address translation. mangle — modify packet headers.
Chains INPUT OUTPUT FORWARD INPUT — traffic destined for this host. OUTPUT — traffic from this host. FORWARD — traffic routed through this host.
Targets ACCEPT DROP REJECT LOG ACCEPT — let it through. DROP — silently discard. REJECT — discard with an error response. LOG — log and continue.
# View all iptables rules with line numbers and packet/byte counts
sudo iptables -L -n -v --line-numbers

# View only the filter table INPUT chain
sudo iptables -L INPUT -n -v

# View the NAT table
sudo iptables -t nat -L -n -v

# Add a rule — allow inbound TCP port 443
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Insert a rule at position 1 (before other rules)
sudo iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT

# Allow established connections (essential — allows return traffic for outbound connections)
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT

# Drop everything else (set default policy)
sudo iptables -P INPUT DROP

# Delete a specific rule by line number
sudo iptables -D INPUT 5

# Save rules (Debian/Ubuntu)
sudo apt install iptables-persistent -y
sudo netfilter-persistent save

# Save rules (RHEL/Rocky)
sudo service iptables save

What just happened? The iptables output showed the full INPUT chain with packet and byte counters per rule. Rule 2 — the ESTABLISHED,RELATED rule — has already processed 9,821 packets (589KB), confirming it is essential for return traffic. The final DROP rule (line 6) has caught 18 packets — blocked connection attempts. The packet counts are invaluable for confirming a rule is being hit and for quantifying attack volume.

Firewall Troubleshooting

Firewall misconfiguration is a frequent cause of connectivity failures. A systematic approach to firewall troubleshooting confirms whether a dropped packet is the result of a firewall rule, a missing rule, or something else entirely in the network stack.

Step 1 — Confirm the firewall is actually active

Rule mismatches are only relevant if a firewall tool is actually running. Check service status and whether the kernel tables have any rules loaded.

sudo ufw status # or sudo firewall-cmd --state # or sudo iptables -L -n | head -5

Step 2 — Confirm the service is actually listening

Before blaming the firewall, confirm the service is bound and listening. A refused connection when the firewall allows the port usually means the service is down or listening on the wrong address.

sudo ss -tlnp | grep :8080

Step 3 — Test from localhost to isolate the firewall layer

The firewall INPUT chain only applies to traffic arriving from outside. A connection from localhost bypasses INPUT rules. If localhost can reach the service but external cannot, the firewall is definitely the cause.

curl -v http://localhost:8080 # bypasses firewall nc -zv localhost 8080

Step 4 — Use LOG target to see what the firewall is doing

Add a temporary LOG rule just before the DROP rule. The kernel will log every dropped packet to syslog — you can then see exactly what traffic is being blocked and refine your rules.

sudo iptables -I INPUT 5 -j LOG --log-prefix "FW-DROP: " --log-level 4 sudo tail -f /var/log/kern.log | grep "FW-DROP"
# Quick check — temporarily disable the firewall to test if it's the cause
# (on a test server — never do this on production without a maintenance window)
sudo ufw disable        # re-enable with: sudo ufw enable
# or
sudo systemctl stop firewalld   # restart with: sudo systemctl start firewalld

# Check if a specific port would be allowed by the current ufw rules
sudo ufw status | grep 8080

# Trace a packet through iptables rules (requires xtables-addons or kernel support)
# Simpler: add LOG rule before DROP to see what's being dropped
sudo iptables -I INPUT -j LOG --log-prefix "INPUT: " --log-level debug
sudo dmesg | grep "INPUT:"
# Remove the LOG rule when done
sudo iptables -D INPUT -j LOG --log-prefix "INPUT: " --log-level debug

Production Firewall Patterns

Real production servers follow consistent patterns that balance security with operational requirements. The following examples cover the three most common server archetypes — a web server, a database server, and a bastion host — each with its characteristic firewall profile.

Web server
Open: 22/tcp (SSH), 80/tcp, 443/tcp — deny everything else

If SSH access should only come from a management network, restrict port 22 to that CIDR rather than opening it to the world. Consider running SSH on a non-standard port to reduce log noise from automated scanners.

Database server
Open: 22/tcp from management CIDR only, 5432/tcp from app server IPs only

Database ports should never be open to the internet. Restrict to the specific application server IPs or subnet. If app servers change frequently, restrict to the VPC CIDR rather than individual IPs.

Bastion host
Open: 22/tcp from authorised admin IPs only — deny all other inbound

A bastion host's entire purpose is to be the single SSH entry point. Its firewall should be the strictest — restrict SSH to a list of known admin IP addresses or CIDR ranges, enable fail2ban, and run nothing else on the server.

# ── Web server — ufw ──────────────────────────────────────────────
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 10.0.0.0/8 to any port 22 comment "SSH from management network"
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"
sudo ufw enable

# ── Database server — ufw ─────────────────────────────────────────
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 10.0.0.5 to any port 5432 comment "PostgreSQL from app server"
sudo ufw allow from 10.0.0.0/8 to any port 22 comment "SSH from management network"
sudo ufw enable

# ── Database server — firewalld ───────────────────────────────────
sudo firewall-cmd --zone=public --remove-service=dhcpv6-client --permanent
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.5" port port="5432" protocol="tcp" accept' --permanent
sudo firewall-cmd --reload

What just happened? The two firewall profiles show a meaningful security difference — the web server allows SSH from the entire management /8 network (flexible for the admin team), while the database server restricts PostgreSQL to a single IP (10.0.0.5 — the known app server). Adding a comment to each ufw allow rule documents the business reason, making future audits far easier.

Never Mix ufw, firewalld, and iptables on the Same System

All three tools write rules into the same kernel netfilter tables. Using two simultaneously creates conflicts that are extremely difficult to debug — a rule added by ufw may be silently overridden by firewalld on reload, or vice versa. Pick one tool appropriate for your distribution (ufw for Debian/Ubuntu, firewalld for RHEL/Rocky) and disable the others. Check for conflicts with systemctl status ufw firewalld — only one should be active.

Lesson Checklist

I understand that all firewall tools write to the same kernel netfilter tables, and I use only one tool per system — ufw on Debian/Ubuntu, firewalld on RHEL/Rocky
I always start with default deny incoming and add explicit allow rules only for the ports a server needs — I never "open everything and restrict specific ports"
I use firewalld's --permanent flag for all production rules and always run --reload after changes
I can read iptables output including chain policies, packet counters, and match conditions to understand the complete state of the firewall regardless of which tool created the rules
I follow the four-step troubleshooting sequence — check active → check listening → test from localhost → use LOG target — to isolate firewall-caused connectivity failures

Teacher's Note

The localhost isolation test (Step 3 in the troubleshooting sequence) is the single most useful firewall diagnostic technique. It takes five seconds: if curl localhost:8080 works but the external connection fails, the firewall is definitively the problem and you can stop investigating the application. If curl localhost:8080 also fails, the firewall is irrelevant — the service is the problem. This single question eliminates half the possible causes in seconds.

Practice Questions

1. You are configuring the firewall on a new Ubuntu 24.04 server that will run both an nginx web server and an internal monitoring agent that listens on port 9100 (Prometheus node_exporter). The monitoring agent should only be reachable from the monitoring server at 10.0.2.5. Write the complete ufw configuration.

2. A developer reports that their application on port 3000 is accessible from localhost but not from external clients. You have confirmed the service is running and the correct IP is bound. Walk through the diagnostic steps and explain how you would confirm the firewall is the cause and then fix it using ufw.

3. Explain the difference between DROP and REJECT as iptables targets. In what scenario would you prefer REJECT over DROP, and when is DROP the better choice?

Lesson Quiz

1. You add a service to firewalld with sudo firewall-cmd --zone=public --add-service=http (without --permanent). What happens to this rule after a reboot?

2. An iptables INPUT chain with policy DROP is missing the ESTABLISHED,RELATED rule. What breaks?

3. In firewalld, what is a zone and why is it more flexible than managing individual iptables rules per service?

Up Next

Lesson 30 — SELinux Basics

Mandatory access control, SELinux modes, contexts, and resolving AVC denials