To secure a Raspberry Pi IoT device you need to close the easy doors attackers look for: password logins, open ports, outdated packages, services running as root and unencrypted traffic. This checklist walks through each layer with the exact commands for Raspberry Pi OS Bookworm, finishing with a TLS-encrypted MQTT connection that needs no inbound ports at all. Work through it once, then bake the result into your image so every new device starts hardened.
Threat Model for a Pi in the Field
A Pi gateway is a small Linux server sitting in a place you do not fully control: a customer's network, a factory floor, a roadside cabinet. The realistic threats are not exotic. Automated scanners try default and weak passwords over SSH. Unpatched services get exploited by known vulnerabilities. A compromised application running as root takes over the whole device. Credentials left in a script end up in a Git repository or a support ticket. Unencrypted MQTT traffic is read or modified on a shared network. Someone with physical access pulls the SD card and reads it.
Each item in the checklist below targets one of those. None of them require special hardware.
The Checklist at a Glance
| # | Area | Action | Protects against |
|---|---|---|---|
| 1 | Accounts | Unique user, strong password, no shared images with known credentials | Default credential attacks |
| 2 | SSH | Keys only, no root login, optional port restriction | Password brute force |
| 3 | Firewall | Deny inbound by default, allow SSH from management networks only | Exposed services |
| 4 | Updates | Unattended security upgrades, regular full upgrades | Known vulnerabilities |
| 5 | Services | Disable what you do not use | Unnecessary attack surface |
| 6 | Least privilege | Dedicated service user, systemd sandboxing | Privilege escalation |
| 7 | Secrets | Credentials in a root-owned environment file, not in code | Credential leaks |
| 8 | MQTT | TLS on port 8883, unique credentials per device | Eavesdropping, impersonation |
| 9 | Monitoring | Logins, failed auth, health over MQTT | Undetected compromise |
| 10 | Physical | Enclosure, disabled unused interfaces, storage considerations | Tampering |
1. Accounts
Current Raspberry Pi OS images no longer ship with the old default pi user and password; you create a user in Raspberry Pi Imager or on first boot. Choose a non-obvious username and a long password, and never distribute a golden image where every device shares the same credentials. If you build images for a fleet, generate per-device credentials at provisioning time.
# check which accounts can log in
grep -vE "nologin|false" /etc/passwd
# lock the password of any account that should only use keys
sudo passwd -l someuser
2. SSH with Keys Only
Copy your public key to the Pi, confirm you can log in with it, then disable password authentication with a drop-in file so package updates do not overwrite your change.
# on your workstation
ssh-keygen -t ed25519 -C "admin@workstation"
ssh-copy-id admin@gateway-01.local
# on the Pi
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 3
EOF
sudo sshd -t && sudo systemctl reload ssh
Keep your current session open and test a new login in a second terminal before closing it. If you do not need SSH at all on deployed devices, disable it with sudo systemctl disable --now ssh and use a maintenance procedure that enables it temporarily.
3. Firewall
An MQTT gateway only makes outbound connections, so it needs almost no inbound rules. ufw keeps the configuration readable:
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.10.0/24 to any port 22 proto tcp # management subnet only
sudo ufw limit 22/tcp
sudo ufw enable
sudo ufw status verbose
Replace the subnet with your management network. For stricter environments, set outgoing to deny as well and allow only DNS, NTP, package mirrors and your MQTT broker on port 8883. Add fail2ban if SSH must be reachable from wider networks.
4. Updates
Most real-world compromises exploit vulnerabilities that already have fixes. Enable automatic security updates and schedule regular full upgrades:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
sudo unattended-upgrade --dry-run --debug | tail -n 20
Include your Python dependencies in the routine. Pin versions in a requirements.txt, test upgrades on one device first and then roll them out to the fleet.
5. Disable Unused Services
Every running service is something to patch and something to attack. List what is enabled and turn off what your gateway does not need:
systemctl list-unit-files --state=enabled --type=service
sudo systemctl disable --now avahi-daemon cups 2>/dev/null
sudo systemctl disable --now bluetooth # only if you do not use BLE
On headless devices you can also disable Wi-Fi or Bluetooth at the hardware level in config.txt with dtoverlay=disable-wifi or dtoverlay=disable-bt when the device uses Ethernet only.
6. Least Privilege for Your Application
Run the MQTT client as a dedicated system user with no login shell and only the groups it needs (for example gpio or dialout). Then let systemd sandbox it:
sudo useradd --system --home /opt/gateway --shell /usr/sbin/nologin gateway
sudo usermod -aG gpio gateway
[Service]
User=gateway
Group=gateway
EnvironmentFile=/etc/gateway/mqtt.env
ExecStart=/opt/gateway/.venv/bin/python -u /opt/gateway/gateway.py
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/gateway
ProtectKernelTunables=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
ProtectSystem=strict makes the whole filesystem read-only for the service except the paths you list, so a compromised script cannot modify system files or plant persistence. Run systemd-analyze security gateway.service to see which further options apply. The complete unit, including restart policies, is in running a Python MQTT script on boot.
7. Keep Secrets out of Code
Store broker credentials in an environment file readable only by root (systemd reads it before dropping privileges):
sudo mkdir -p /etc/gateway
sudo tee /etc/gateway/mqtt.env > /dev/null <<'EOF'
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
EOF
sudo chmod 600 /etc/gateway/mqtt.env
Keep the file out of version control and out of any image you share. If a device is lost or retired, revoke its credentials on the broker side.
8. Secure the MQTT Connection
Plain MQTT on port 1883 sends credentials and data in clear text. Use TLS on port 8883. ConnectMQ brokers present a Let's Encrypt certificate, which the Pi already trusts through its system CA bundle, so no certificate files are needed:
import os
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=os.environ["MQTT_CLIENT_ID"])
client.username_pw_set(os.environ["MQTT_USERNAME"], os.environ["MQTT_PASSWORD"])
client.tls_set() # verifies the server certificate and hostname
client.connect(os.environ["MQTT_HOST"], int(os.environ["MQTT_PORT"]), keepalive=60)
client.loop_forever()
Never call tls_insecure_set(True) in production; it disables hostname checking and defeats the purpose. Make sure the clock is correct, because certificate validation fails if the Pi boots with a wrong date and NTP has not synced yet. Use unique credentials for every device: on ConnectMQ, client IDs are bound to the issued credentials, and repeated failed logins from an address lead to IP blocking. The Python MQTT TLS guide covers certificate options in more depth, and MQTT security best practices addresses the broker side. Because the device only connects outbound, the firewall in step 3 stays closed.
Remember that the public MQTT test broker is readable by everyone. It is for testing only; production devices belong on a private broker.
9. Monitoring
A hardened device still needs eyes on it. Review journalctl -u ssh for unexpected logins, publish health and security events (failed SSH attempts, service restarts, unexpected reboots) to an MQTT topic, and alert on anomalies. A retained Last Will status topic tells you immediately when a device goes offline, which can be the first sign of tampering.
10. Physical Security
If someone can remove the storage, they can read everything on it, including credentials. Use a lockable enclosure, consider booting from internal storage on Compute Module based devices, disable unused USB and HDMI where your hardware allows it, and treat any device that has been out of your control as untrusted until reimaged. The Raspberry Pi documentation explains the relevant configuration options.
These steps pair well with the Raspberry Pi reliability tips, and together form the baseline for any gateway built from the Raspberry Pi MQTT tutorial.
Connect Securely with ConnectMQ
A hardened device deserves a broker built for production. ConnectMQ is a managed MQTT broker with TLS on 8883, secure WebSocket on 8084, per-device credentials and flood protection. Create a free Serverless broker at app.connectmq.com, no credit card needed, and connect your first hardened Pi over TLS in minutes.
Frequently Asked Questions
Do I need to open any ports for MQTT on the Raspberry Pi?
No. An MQTT client connects outbound to the broker, so the Pi needs no inbound ports for MQTT. Only allow inbound SSH from trusted management networks, if at all.
Is port 1883 ever acceptable?
Only for local testing with non-sensitive data. Anything that crosses an untrusted network should use TLS on port 8883 so credentials and payloads are encrypted.
Should every device have its own MQTT credentials?
Yes. Unique credentials let you revoke a single lost or compromised device without touching the rest of the fleet, and they make logs meaningful.
Does disk encryption help on a Raspberry Pi?
It protects data if the storage is stolen, but the device then needs a way to unlock at boot without a person present, which is hard to do securely on a Pi. For most gateways, minimising stored secrets and revocable credentials is more practical.



