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.
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.
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 idea | Zones: 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=http | allow http or allow 80/tcp |
| Only from one network | put the network in a zone as a source | allow from 192.168.1.0/24 to any port 22 |
| Takes effect | right away, but gone after a reload unless you use --permanent (then --reload) | right away, and saved |
| Special rules | rich rules: rule source address="…" drop | deny 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.
# 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.
# 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 numberedAdd 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:
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.
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
- Rate limits:
sudo ufw limit OpenSSHallows SSH but blocks an address that connects 6 times in 30 seconds. On Rocky, a rich rule can do the same:rule service name="ssh" limit value="3/m" accept. - Logging:
sudo ufw logging onwrites[UFW BLOCK]lines; on Rocky,sudo firewall-cmd --set-log-denied=allwritesREJECTlines. Read them withsudo journalctl -k(and on Ubuntu also/var/log/ufw.log).
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?
✓ First match wins. Use sudo ufw insert 1 deny from 203.0.113.45.
2. On Rocky you ran sudo firewall-cmd --add-service=http (no --permanent), then rebooted. Is port 80 open?
✓ Add --permanent and --reload, or save the runtime rules with --runtime-to-permanent.
3. Why allow SSH only from your own network if you already use keys?
✓ Defence in depth: each layer covers for the day another one fails.