Stage 4 of 6 · Lab, on the Pi · about 20 minutes
Firewall
Your Pi now offers services to the network. UFW, the Uncomplicated Firewall, is the door policy: refuse every incoming connection except the ones you named. Four commands set it up; one warning keeps you from locking yourself out.
Ports, services, firewall
A service (like sshd) listens on a numbered port. Without a firewall, anything that listens is reachable by anyone on the network. With UFW's default-deny policy, reachability becomes a decision you make per port. See which programs are listening right now:
$ ss -tlnp # t: TCP, l: listening, n: numeric ports, p: program names
Addresses, ports and state: how the filter can be strict yet not break anything
Two numbers route every connection on a network. The IP address finds the machine; the port finds the program on it. When the lab PC opens ssh lastname-pi.local, your PC picks a random high "ephemeral" port for its end (say 51834) and connects to the Pi's port 22. The pair of address:port endpoints identifies that one conversation among thousands.
UFW is stateful: it remembers the connections the Pi itself started. That is what makes "deny incoming, allow outgoing" livable. When apt or git reaches out, the answers streaming back are recognised as replies to an existing conversation and let through; they are not "incoming connections" in the firewall's sense. Only new conversations started from outside hit the deny rule. Without state tracking, a default-deny firewall would also strangle every download.
One more piece of honesty about names: UFW does not filter anything itself. It is a friendly front end that writes rules for netfilter, the packet filter inside the Linux kernel (driven by nftables on current systems). "Uncomplicated" is the whole point: the same policy written directly in nftables is several times longer. When a later course or job shows you raw nftables or iptables rules, you are looking at the layer under today's four commands.
Read more
Concept level: Cloudflare's What is a firewall?. Practice level: DigitalOcean's UFW essentials is a well-maintained cookbook of common rules (allow from one address only, rate-limit SSH, and more). Reference level: the ufw manual page.
The rule plan
| Traffic | Decision | Why |
|---|---|---|
| Incoming, port 22 (SSH) | Allow | You administer the Pi over SSH; from Week 4 you will work over it constantly |
| Incoming, port 5900 (VNC) | Allow | The remote desktop you enable on the next page |
| Incoming, anything else | Deny (default) | Nothing else is offered on purpose this week; MQTT's port arrives in Week 9, added deliberately then |
| Outgoing, everything | Allow (default) | The Pi must reach apt repositories, GitHub and the network time service |
Set the rules and enable
Order matters if you are connected over SSH
Enabling a default-deny firewall before allowing port 22 cuts off your own SSH session, and the next connection is refused too. Always allow ssh first, enable second. At the Pi's own keyboard the mistake costs nothing; over SSH it costs you the session. UFW prints a warning about exactly this when you enable it over SSH.
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow ssh # same as: sudo ufw allow 22/tcp
$ sudo ufw allow 5900/tcp # VNC, for the next page
$ sudo ufw enable
Firewall is active and enabled on system startup
That last line means UFW also comes back after every reboot; you set this once per installation. Now prove the two paths still work: from the lab PC, ssh username@lastname-pi.local must connect exactly as before.
Read the status
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
5900/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
5900/tcp (v6) ALLOW IN Anywhere (v6)
- Status: active and the Default: line are the two things to verify: deny incoming, allow outgoing.
- Each rule appears twice: once for IPv4, once for IPv6. That is normal.
- This output is part of your hand-in: save it with
sudo ufw status verbose > ~/iot/week3/evidence/ufw.txt.
Change rules later
$ sudo ufw status numbered # list rules with numbers
$ sudo ufw delete 2 # remove rule number 2 (it confirms first)
$ sudo ufw delete allow 5900/tcp # or remove by naming the rule
$ sudo ufw disable # turn the firewall off (diagnostic only; turn it back on)
Course habit from here on: when a new service arrives (MQTT in Week 9, the Flask web interface in Week 11), the lab page tells you which port to allow, and you add exactly that rule. If a connection mysteriously fails in a later week, sudo ufw status is the second thing you check, right after the cable.
Deeper reference, same tool: the Ubuntu community UFW guide (UFW is identical on Raspberry Pi OS and Ubuntu).
Checklist for this stage
Check yourself
Why must allow ssh come before enable when you work over SSH?
The firewall is active, yet apt update and git push still work. Why?
What is the difference between a service not running and a port denied by UFW?
ss -tlnp answers it: if nothing listens on the port, the service is the problem; if it listens but outsiders are refused, the firewall rule is missing.Week 9 adds MQTT on port 1883. What exactly will you do?
sudo ufw allow 1883/tcp, then check status verbose. Default deny stays; you add doors one at a time, on purpose.