Security basics · Lesson 3 · 35 min

Firewalls in depth

In Linux Basics, lesson 14 you opened the firewall so a website could be seen. Now it's time to use the firewall the way professionals do: as a list of exactly who may reach what, with everything else refused. Even if a service is left running by mistake (like the test server in lesson 1), a good firewall keeps it out of reach. That's defence in depth.

You will learn

  • Default deny: refuse everything, then allow what's needed
  • How firewalld (zones) and ufw (ordered rules) think differently
  • Letting SSH in only from your own network, without locking yourself out
  • Blocking one troublemaker, rate limits, and logging what gets blocked

Default deny

A good firewall starts from "no". Every incoming connection is refused unless a rule says otherwise. Then you add only what the server is for: SSH for you, the website for everyone. Anything else that happens to be listening simply can't be reached.

Rocky / RHEL: firewalld
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all

The public zone refuses everything except the services listed in it. Out of the box: ssh, cockpit and dhcpv6-client, and it's already running.

Ubuntu / Debian: ufw
sudo ufw status verbose
sudo ufw default deny incoming
sudo ufw default allow outgoing

ufw is installed but off until you enable it. Its defaults are already deny incoming, allow outgoing.

Two firewalls, two ways of thinking

firewalld (Rocky)ufw (Ubuntu)
Main ideaZones: each zone is a set of allowed services. A connection is handled by the zone its source address (or network card) belongs to.A list of rules, checked from the top. The first rule that matches wins; if none match, the default policy applies.
Allow a service--add-service=httpallow http or allow 80/tcp
Only from one networkput the network in a zone as a sourceallow from 192.168.1.0/24 to any port 22
Takes effectright away, but gone after a reload unless you use --permanent (then --reload)right away, and saved
Special rulesrich rules: rule source address="…" dropdeny from …, insert 1 …, limit

SSH only from your own network

Your laptop is on the home network, 192.168.1.0/24. Nobody on the internet needs to reach SSH, so why let them try? Bots that can't even connect can't guess anything.

Rocky / RHEL
# the internal zone already allows ssh: send the home network there
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.1.0/24
# and stop offering ssh (and the cockpit web console) to everyone else
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --permanent --zone=public --remove-service=cockpit
sudo firewall-cmd --reload
sudo firewall-cmd --get-active-zones

From now on everything from the home network is handled by the internal zone. So a service you want at home and on the internet (like the website) must be added to both zones.

Ubuntu / Debian
# the rule FIRST, then switch the firewall on
sudo ufw allow from 192.168.1.0/24 to any port 22
sudo ufw enable
sudo ufw status numbered
Don't saw off your own branch

Add the new way in before removing the old one, and stay logged in while you test from a second window, just like with SSH settings. On Ubuntu, enabling ufw with no SSH rule cuts off your own connection. On Rocky, removing ssh from public before your network is in internal does the same after the reload.

Order matters (ufw)

ufw stops at the first matching rule. Suppose SSH is allowed for everyone (rule 1) and you then add deny from 203.0.113.45 (rule 2). That address can still reach SSH, because rule 1 matched first! To block it, the deny has to come first:

Rocky / RHEL: a rich rule
sudo firewall-cmd --permanent --zone=public \
  --add-rich-rule='rule family="ipv4" source address="203.0.113.45" drop'
sudo firewall-cmd --reload

Rich rules are checked before the zone's normal services, so no ordering tricks needed.

Ubuntu / Debian: insert at the top
sudo ufw insert 1 deny from 203.0.113.45
sudo ufw status numbered

Put a wrong rule in by mistake? sudo ufw delete 3 removes rule number 3.

drop and deny ignore the packet, so the other side waits and times out. reject answers "no" straight away. Dropping gives bots less information; rejecting is friendlier on your own network.

Runtime vs permanent (firewalld)

firewalld keeps two copies of its rules: the runtime rules it's using right now, and the permanent ones it loads at boot or on --reload. That gives you a safe way to experiment: change the runtime rules (no --permanent), test, and if everything still works, save them with sudo firewall-cmd --runtime-to-permanent. If you lock yourself out, a reboot brings back the old permanent rules.

Slowing down and watching

Try it: a firewall with a purpose 🧱

The club's web server is running. Make the firewall say exactly this: the website for everyone, SSH only from the home network 192.168.1.0/24, and one noisy address (203.0.113.45) blocked completely. Then watch it work. (timewarp 30m lets half an hour of internet traffic hit the server.)

Quick check

1. ufw has rule 1 allow 22/tcp and rule 2 deny from 203.0.113.45. Can 203.0.113.45 reach SSH?

2. On Rocky you ran sudo firewall-cmd --add-service=http (no --permanent), then rebooted. Is port 80 open?

3. Why allow SSH only from your own network if you already use keys?

Finished the missions and the quiz? Mark it done to track your progress.