Learn Linux - Linux Firewall Management (UFW, Firewalld & Iptables)
Series/Learn Linux/Episode 19
Episode 19 of 31

Learn Linux - Linux Firewall Management (UFW, Firewalld & Iptables)

Building a server's outer defense layer with the Linux firewall: packet filtering concepts, UFW practice on Debian/Ubuntu, Firewalld zones on RHEL/Rocky, down to low-level iptables/nftables, along with the traps that lock you out of your own server.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

After episode 18 where we covered Remote Access Using SSH — hardening sshd_config, key authentication, and tunnels — you can now enter a server securely. But entering securely is only half the journey. The next question: who else is allowed to try to enter? Your server is open to the internet, and every second bots scan ports 22, 80, 443, 3389 — trying millions of password combinations. Without defense, your server is like a house with all doors wide open, relying on the kindness of strangers.

This is where the firewall comes in. It's the gatekeeper that decides which packets may enter, exit, or be forwarded — based on the rules you set. A firewall isn't a "set and forget" tool: it's a policy that must be designed. What do you open? From which sources? To which destination? The answers to these questions are what separate a secure server from a leaking one.

In this episode 19, we'll cover the three firewall tools that dominate the Linux ecosystem: UFW (Ubuntu/Debian), Firewalld (RHEL/Rocky), and iptables/nftables as the low-level foundation behind them all. We'll discuss packet filtering concepts, practice each tool, and — just as importantly — avoid the classic traps that make you locked out of your own server.

Main Discussion

Basic Concepts: Packet Filtering & Why a Server Needs a Firewall

The Linux firewall works at the packet level. Every passing packet is inspected and compared against sequential rules; the first matching rule determines the packet's fate: ACCEPT (allow), DROP (discard silently), or REJECT (refuse with a reply).

There are three main paths (or chains) you must memorize:

ChainTraffic DirectionAnalogy
INPUTPackets entering this machineThe house's entrance guard
OUTPUTPackets leaving this machinePeople leaving the house
FORWARDPackets passing through this machine (routing)A transit city's border post

Why does a server need a firewall, when its services only use three or four ports? Because the firewall enforces a default-deny policy: close everything, open only what's needed. This reduces the attack surface, blocks unused ports, and narrows the impact if a service is hacked. Bots will find doors that are already closed, not doors waiting to be opened.

Important

The golden firewall rule: default-deny — close everything not explicitly opened. A server with an "open everything, close later when there's a problem" policy isn't a secured server; it's just postponing disaster. Start from "everything closed", then open ports one by one with a reason.

UFW: The Friendly Firewall on Debian/Ubuntu

UFW (Uncomplicated Firewall) is built on top of iptables/nftables, but hides the complexity behind easy-to-read syntax. It's the primary choice on Ubuntu/Debian because of its philosophy: uncomplicated.

Check UFW status
sudo ufw status verbose
Enable UFW
sudo ufw enable
Allow SSH before anything else
sudo ufw allow 22/tcp
Allow several service ports at once
sudo ufw allow 80,443/tcp
Allow a port with a range and specific source
sudo ufw allow from 192.168.1.0/24 to any port 5432 proto tcp
Delete a rule
sudo ufw delete allow 80,443/tcp

Warning

The number one trap in the UFW world: running ufw enable before allowing SSH. UFW activates the default deny incoming policy, so the moment you run ufw enable, your SSH connection drops — and you're locked out. The correct order: ufw allow 22/tcp first, then ufw enable. To avoid the drama, enable UFW while keeping another SSH session open as a rescue path.

For well-known services, UFW has built-in service names:

Allow HTTP and HTTPS services
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw allow 'Nginx Full'
Status with rule numbers
sudo ufw status numbered

The status numbered output shows each rule's number — useful for deleting a specific rule with sudo ufw delete <number>.

Firewalld: Elegant Zones on RHEL/Rocky

Firewalld is the default firewall on the RHEL family (Rocky, AlmaLinux, CentOS Stream). Its core concept is zones — sets of rules applied based on network trust. The default public zone is for internet-facing interfaces, and internal/trusted for more trusted networks.

View the default zone and status
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --state
List available zones
sudo firewall-cmd --list-all-zones
Allow the HTTP service in the default zone (permanent)
sudo firewall-cmd --permanent --add-service=http
Allow an alternative SSH port
sudo firewall-cmd --permanent --add-port=2222/tcp
Apply changes without restarting
sudo firewall-cmd --reload
firewall-cmd flagFunction
--permanentSave the rule so it survives reload/reboot
--reloadApply permanent rules without dropping connections
--add-serviceAllow a named service (http, https, ssh)
--add-portAllow a raw port+protocol (80/tcp)
--add-sourceAllow all traffic from a specific IP/netmask
--runtime-to-permanentMake the current runtime rules permanent

Warning

The classic Firewalld trap: adding a rule without --permanent, then running --reload. The runtime rule disappears on reload — you think the firewall is open, but it isn't. The standard rule: almost always use --permanent, then --reload. And remember the order above: open SSH first, then change the zone or default policy.

Changing an interface's zone and adding a new zone:

Set an interface's zone
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
Allow PostgreSQL access only from an internal subnet
sudo firewall-cmd --permanent --zone=internal --add-service=postgresql
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.50.0/24

Comparing UFW and Firewalld at a Glance

sudo ufw status verbose
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw allow from 192.168.50.0/24 to any port 5432 proto tcp

Both tools answer the same question — who may access what — in different styles. UFW is simple and flat; Firewalld is zone-based, making it more suited for servers with many interfaces and different trust levels. Choose based on your distro's ecosystem.

Behind the Scenes: iptables & nftables

UFW and Firewalld are just frontends. The engine actually doing the work is netfilter inside the kernel, controlled via iptables (legacy) or nftables (the modern replacement). Understanding this matters because: (1) sometimes you must deal directly with raw rules, and (2) debugging "weird" rules from UFW/Firewalld always ends up here.

iptables configuration is divided into tables (filter, nat, mangle) and chains (INPUT, OUTPUT, FORWARD). Most daily tasks only touch the filter table.

List all filter rules with details
sudo iptables -L -n -v
Add an ACCEPT rule for port 22 in the INPUT chain
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Block a specific source
sudo iptables -A INPUT -s 203.0.113.0/24 -j DROP
Save the rules to survive reboot
sudo iptables-save > /etc/iptables/rules.v4

Setting the Default Policy

Individual rules matter, but what matters more is the default policy — what the firewall does with packets that match no rule at all. -P sets the default policy on a chain:

Default-deny: DROP everything incoming
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

The rules above state: drop all incoming packets not explicitly allowed, but let outgoing traffic keep flowing. This is the essence of default-deny discussed at the start of the episode — with -P, you apply it as a system policy, not just a collection of rules.

Caution

Changing -P INPUT DROP while there's not yet an ACCEPT rule for your SSH port = session dropped and you're locked out. The safe order: add -A INPUT ... --dport 22 -j ACCEPT first, confirm the rule shows up in iptables -L -n, then change the default policy. And don't forget iptables-save afterward — an unsaved policy disappears at reboot (and could drop your connection again at the worst possible time).

Masquerade (NAT): to make this machine a router/NAT for an internal subnet, you need the nat table — a concept that becomes very useful when episode 21 covers routing and network namespaces:

Enable NAT masquerade on the WAN interface
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables optionFunction
-AAppend (add a rule at the end of the chain)
-IInsert (add a rule at the start of the chain)
-DDelete (remove a rule)
-sSource IP/netmask
-dDestination IP
-pProtocol (tcp, udp, icmp)
--dportDestination port
-jAction (ACCEPT, DROP, REJECT)
-L -n -vList rules, no name resolution, verbose

Every iptables command only changes the runtime configuration. You view rules with iptables -L, add them with -A, then save them with iptables-save. Without saving, everything disappears at reboot.

Tip

-n is very important when debugging: without -n, iptables tries to resolve IPs to hostnames — slow and potentially misleading. Get used to always iptables -L -n -v. And rule order matters: iptables processes top to bottom, the first matching rule wins. A specific -D rule must come before a general -A rule.

nftables is iptables' successor — more consistent syntax and one framework for all tables. New distros already make it the default (which is what UFW uses on modern Ubuntu):

View nftables rules (whatever the frontend)
sudo nft list ruleset
Example of basic nftables rules
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0; policy drop; }
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input ip saddr 203.0.113.0/24 drop

The rules above form a default-deny policy: all incoming packets are dropped except SSH (tcp dport 22 accept) and specifically allowed sources.

Final Verification: Seeing the Firewall Through a Networker's Eyes

After putting the rules together, don't stop there. Verify from a network perspective — exactly like you learned in episode 17:

From the server itself
sudo ss -tulpn | grep -E ":(22|80|443)"
From another machine (client perspective)
nc -zv 192.168.1.10 22
curl -v https://192.168.1.10

nc (netcat) and curl give honest answers from the client side: is the port really reachable from outside, through all firewall layers? If nc succeeds but curl fails, the problem isn't the firewall — it's the service or application (the lessons of episodes 17 and 18).

Common Mistakes (Common Pitfalls)

1. ufw enable without opening SSH first. Locked out of your own server. The order: allow 22/tcpenable.

2. firewall-cmd without --permanent then --reload. Rules vanish instantly. Always --permanent + --reload.

3. A default DROP policy that locks you out. Changing the INPUT policy to DROP without an ACCEPT rule for SSH = a decision you'll regret. Only change the policy after all critical services are already allowed.

4. Opening an SSH port in the firewall but forgetting to change the sshd port (or vice versa). You changed Port 2222 in sshd (episode 18) but the firewall only opens 22 — connections are immediately refused. Always keep these two layers in sync.

5. iptables without iptables-save. Runtime rules vanish at reboot. Or the reverse — you add rules with -A after a more general rule, so the new rule never gets used because the general one already matched first.

6. Testing the firewall only from localhost. Test from another machine. The firewall can look correct inside the server, but real traffic comes from outside. Use nc -zv from an external client.

Conclusion

In this episode 19, you've built the server's outer defense layer: understanding packet filtering concepts (ACCEPT/DROP/REJECT, INPUT/OUTPUT/FORWARD chains), practicing UFW on Debian/Ubuntu with a default-deny policy, managing Firewalld on RHEL/Rocky with zones and --permanent/--reload, and dismantling iptables/nftables as the engine behind all the frontends.

Key points to take home:

  • The firewall is a default-deny policy: close everything, open only what's needed.
  • UFW: allow first, then enable. Firewalld: --permanent first, then --reload.
  • iptables is runtime configuration — always iptables-save for persistence.
  • Verify from the client side (nc -zv, curl) — not just from inside the server.
  • Keep the firewall-opened ports in sync with the ports actually used by services.

In the next episode 20 we'll share files between machines through the topic Network File Sharing (NFS & Samba) & DNS Basics. You'll learn to set up an NFS server, share folders between Linux servers, prepare Samba shares for Windows/macOS clients, and understand the basics of DNS from /etc/hosts to systemd-resolved. See you there!

Learn Linux - Linux Firewall Management (UFW, Firewalld & Iptables) | Learn Linux