Raspberry Pi: update it, key it, lock it
420-302-VA · WEEK 3 · FALL 2026

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

Incoming connections hit the UFW filter first. Port 22 SSH and port 5900 VNC pass through to their services; every other port is refused before any program sees it. Outgoing traffic from the Pi is allowed. Raspberry Pi sshd · listens on 22 wayvnc · listens on 5900 everything else UFW to port 22 (ssh) to port 5900 (vnc) to any other port outgoing traffic (apt, git, browsing) is allowed by default →
Default deny incoming, allow outgoing. The filter runs before any program: a refused connection never reaches a service, listening or not.

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

TrafficDecisionWhy
Incoming, port 22 (SSH)AllowYou administer the Pi over SSH; from Week 4 you will work over it constantly
Incoming, port 5900 (VNC)AllowThe remote desktop you enable on the next page
Incoming, anything elseDeny (default)Nothing else is offered on purpose this week; MQTT's port arrives in Week 9, added deliberately then
Outgoing, everythingAllow (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?
Enabling applies the default-deny policy immediately. Without the port 22 rule, your current session and every new one are refused, and you must go to the Pi's own keyboard to fix it.
The firewall is active, yet apt update and git push still work. Why?
Those are outgoing connections that the Pi starts, and the outgoing default is allow. The deny rule applies to incoming connections that others start toward the Pi.
What is the difference between a service not running and a port denied by UFW?
Both look like "cannot connect" from outside. Inside the Pi, 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.