MQTT & Wi-Fi: the bench becomes a network
420-302-VA · WEEK 9 · FALL 2026

Stage 3 of 6 · Lab · about 20 minutes

The broker

Mosquitto is the open-source MQTT broker, small enough that the Pi runs it without noticing. Installing it takes one command; making it reachable takes understanding, because its modern defaults are locked down on purpose, and you will hit two walls that every real deployment hits. This page is those walls, and the door through each.

Install: the apt cycle returns

Over SSH on the Pi, the Week 3 ritual, update then install:

you@lastname-pi:~ $ sudo apt update
you@lastname-pi:~ $ sudo apt install -y mosquitto mosquitto-clients

Two packages on purpose: mosquitto is the broker itself; mosquitto-clients brings mosquitto_pub and mosquitto_sub, the command-line clients that will be your stethoscope all term, the fastest way to see what is really flowing, independent of any code you wrote.

Mosquitto is a service

Installing did more than copy files: Mosquitto registered as a service (a daemon, in Unix speak), a program the operating system supervises rather than one you launch in a terminal. Week 2's boot chain ended with "the init system starts services"; today you own one. The supervisor is systemd, and systemctl is how you speak to it:

you@lastname-pi:~ $ sudo systemctl enable mosquitto   # start at every boot
you@lastname-pi:~ $ systemctl status mosquitto        # is it running right now?
● mosquitto.service - Mosquitto MQTT Broker
     Active: active (running) since Mon 2026-11-02 14:52:10 EST; 1min ago

active (running) in the status output is the fact that matters. Two more verbs complete the kit: sudo systemctl restart mosquitto (needed after every configuration change, below) and sudo journalctl -u mosquitto -e to read the service's log when something is wrong, the broker's version of a traceback. Enabled means the broker survives reboots unattended: from today, your Pi serves MQTT the moment power arrives, term-long infrastructure, no hands.

The two walls

Try to connect from any other machine right now and you will fail, by design. Since version 2.0, Mosquitto ships secure-by-default: with no listener configured it binds to loopback only (reachable solely from the Pi itself), and any listener you do open refuses anonymous clients unless told otherwise. Week 3's smallest-attack-surface principle, implemented by the vendor; your job is to open exactly as much as the bench needs, in a configuration file, on the record. Mosquitto reads drop-in files from /etc/mosquitto/conf.d/, so create one:

you@lastname-pi:~ $ sudo nano /etc/mosquitto/conf.d/bench.conf
# bench.conf: open the standard port to the bench LAN, no credentials.
# Scoped decision for a supervised classroom network. See Week 9, pubsub.html#security.
listener 1883
allow_anonymous true
you@lastname-pi:~ $ sudo systemctl restart mosquitto
you@lastname-pi:~ $ systemctl status mosquitto   # confirm: active (running), not failed

Restart, then re-check status, every time

Configuration is read at start-up only: an edited file does nothing until the restart. And a typo in the file makes the restart fail, taking the broker down entirely, so the status check after every restart is not politeness, it is how you find out. If status shows failed, read sudo journalctl -u mosquitto -e: Mosquitto names the bad line.

The firewall: allow before use

Wall three would have been the firewall, except Week 3 trained you for it: UFW currently allows SSH and nothing else, so MQTT's port is closed to the network no matter what Mosquitto listens on. Same discipline, new port:

you@lastname-pi:~ $ sudo ufw allow 1883
you@lastname-pi:~ $ sudo ufw status verbose
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
1883                       ALLOW IN    Anywhere

Your Pi's open-port inventory is now two lines long, and you can justify every line, which is the entire Week 3 standard. (Production would also scope who may reach 1883; on the bench LAN, "anywhere" is the LAN.)

Prove it: the loopback test

Before any ESP32 or Python enters the picture, prove the broker with the command-line clients alone, two SSH terminals to the Pi (your Week 3 skills make this cheap):

# Terminal A: subscribe, verbosely (-v prints the topic with each message), and wait
you@lastname-pi:~ $ mosquitto_sub -t "bench/test" -v
# Terminal B: publish one message to that topic
you@lastname-pi:~ $ mosquitto_pub -t "bench/test" -m "hello bench"

Terminal A prints bench/test hello bench: a complete publish-broker-subscribe round trip, on one machine. Two refinements worth thirty seconds each: re-run terminal A with the filter "bench/#" and publish to bench/anything/else, watching the wildcard theory come true; and keep a mosquitto_sub -t "#" -v habit in your pocket, the firehose that shows everything crossing the broker, which is the single most useful debugging move of the next six weeks. From another machine on the bench network (the clients install on most systems), the same subscribe with -h naming the Pi's address proves network reach; the real cross-machine proof arrives with the node, next page.

The path of a message

You have now built every checkpoint a message must clear. Hold this picture; it is also the troubleshooting map for the whole week:

A message's path: the ESP32 node, across Wi-Fi, to the Pi's firewall where UFW must allow port 1883, then to Mosquitto's configured listener, then out to subscribers. Each hop is labelled with the stage that builds it and the wall that can block it. ESP32 node next page: Wi-Fi up Wi-Fi 2.4 GHz, same LAN UFW gate allow 1883 else: timeout Listener bench.conf else: refused Subs fan-out Orange checkpoints = this page's two walls; the troubleshoot ladder reads their different failures.
Every checkpoint, one picture. A connection that times out never reached the broker (firewall, network, wrong address); a connection refused reached the Pi and found no listener willing (configuration wall). Learning to read which failure you have is half of network debugging.

Checklist for this stage

Check yourself

What exactly did sudo systemctl enable mosquitto change, and what did it not change?
It registered the service to start automatically at every boot. It did not start it now (installation already had; otherwise start does) and it did not configure anything about how Mosquitto listens.
A classmate's node gets "connection refused" from the Pi. Which wall is that, and why not the firewall?
The listener wall: refusal means a machine answered and actively declined, so the packet got through UFW to the Pi, but no listener accepted on 1883, missing or broken bench.conf, or Mosquitto not restarted/failed. A firewall block drops packets silently, which shows up as a timeout, not a refusal.
Why does editing bench.conf have no effect until a restart, and what must you check right after restarting?
Mosquitto reads configuration once, at start-up. After sudo systemctl restart mosquitto, check systemctl status mosquitto: a typo in the file makes the restart fail and the broker stay down, and the status line (plus journalctl -u mosquitto -e) is where that fact surfaces.
Defend allow_anonymous true on this Pi in two sentences, using Week 3's language.
It is a scoped, documented opening of exactly what the bench lesson needs, on a supervised local network carrying light levels: smallest sufficient attack surface, on the record in bench.conf. The same line in production would be negligence; there, authentication, TLS on 8883 and topic ACLs replace it.
Which single command shows every message crossing the broker, and when would you reach for it?
mosquitto_sub -t "#" -v: the firehose. Reach for it whenever publishers and subscribers disagree about what is flowing; it shows the broker's ground truth, including the exact topic strings, where typos hide.