Raspberry Pi

Raspberry Pi Zero 2 W MQTT Sensor Node: Compact, Low-Power and Reliable

Build a small, headless Raspberry Pi Zero 2 W sensor node that reads a BME280, publishes environment and health data over MQTT 5, uses less power and survives months of unattended operation.

Tiny Raspberry Pi Zero 2 W with a BME280 sensor in a small enclosure sending readings to the cloud in a raspberry pi zero 2 w mqtt sensor node
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A Raspberry Pi Zero 2 W MQTT node gives you a full Linux computer the size of a stick of gum, which makes it a great fit for sensor nodes that need Python, TLS and remote access but not a full-size board. In this guide you will set up a headless Zero 2 W, trim its power use, read a BME280 environmental sensor and publish readings plus device health over MQTT 5, with the hardening needed to run unattended for months.

Why the Zero 2 W Makes a Good Sensor Node

The Zero 2 W has a quad-core 64-bit Arm Cortex-A53, 512 MB of RAM, 2.4 GHz Wi-Fi and Bluetooth, and the standard 40-pin GPIO header. That is enough to run Python, paho-mqtt with TLS, and a couple of sensors with plenty of headroom, while drawing noticeably less power than a Pi 4 or Pi 5.

It is not a microcontroller, however. It cannot deep sleep between readings, and it boots Linux from an SD card. If your node has to run on batteries for months, an ESP32 is the better tool; our Raspberry Pi vs ESP32 comparison explains the trade-off. The Zero 2 W shines where mains or USB power is available and you value Linux tools, SSH access and Python over the last milliwatt.

Parts List

Part Purpose
Raspberry Pi Zero 2 W The node itself
Quality 5 V 2.5 A micro USB supply Undervoltage is the most common cause of crashes
A2-rated microSD card, 16 GB or more OS and application
BME280 breakout (I2C) Temperature, humidity, pressure
Soldered 40-pin header or solder-on wires The Zero 2 W ships without a header

Headless Setup with Raspberry Pi OS Lite

Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit). In the advanced options set the hostname, enable SSH with a public key, enter your Wi-Fi network and country, and create your user. The Lite image has no desktop, which saves RAM, storage writes and power. Boot the Zero, wait a minute, then connect:

ssh pi@zero-node-01.local
sudo apt update && sudo apt full-upgrade -y
sudo raspi-config nonint do_i2c 0
sudo apt install -y python3-venv i2c-tools

Remember that the Zero 2 W only supports 2.4 GHz Wi-Fi. If it never appears on the network, check that your access point broadcasts a 2.4 GHz band and that the Wi-Fi country is set correctly.

Reducing Power and SD Card Wear

Add these lines to /boot/firmware/config.txt and reboot:

# Disable Bluetooth if you do not need it
dtoverlay=disable-bt
# Turn off the activity LED
dtparam=act_led_trigger=none

Then keep the system journal in RAM so routine logging does not wear the SD card:

sudo mkdir -p /etc/systemd/journald.conf.d
printf "[Journal]\nStorage=volatile\nRuntimeMaxUse=16M\n" | \
  sudo tee /etc/systemd/journald.conf.d/volatile.conf
sudo systemctl restart systemd-journald
sudo systemctl disable --now bluetooth hciuart 2>/dev/null || true

One power setting is a deliberate trade-off. Wi-Fi power saving reduces consumption but can delay incoming packets enough to cause keepalive timeouts and reconnects. If you see frequent disconnects in your logs, disable it with sudo iw wlan0 set power_save off and make it permanent through NetworkManager. For a node that only publishes, leaving power saving on is often fine.

Wiring the BME280

BME280 Zero 2 W
VIN 3.3 V (physical pin 1)
GND GND (physical pin 6)
SCL GPIO3 / SCL (physical pin 5)
SDA GPIO2 / SDA (physical pin 3)

Run i2cdetect -y 1. You should see the sensor at address 0x76 or 0x77. Note which one; the script defaults to 0x76.

Installing the Python Environment

mkdir -p ~/node && cd ~/node
python3 -m venv .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" adafruit-circuitpython-bme280

The Raspberry Pi Zero 2 W MQTT Script

This script uses MQTT 5 so each reading can carry a message expiry interval: if a subscriber with a persistent session is offline for longer than five minutes, stale readings are dropped instead of delivered late. It also publishes a health message with CPU temperature, Wi-Fi signal and uptime, which is invaluable once the node is hidden in a cupboard. Save as node.py:

#!/usr/bin/env python3
"""Pi Zero 2 W sensor node: BME280 + health metrics over MQTT 5."""
import json
import os
import time
import uuid

import board
import paho.mqtt.client as mqtt
from adafruit_bme280 import basic as adafruit_bme280
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"zero2w-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/4b9e27/zero-node-01")
INTERVAL = int(os.getenv("PUBLISH_INTERVAL", "60"))
BME_ADDRESS = int(os.getenv("BME280_ADDRESS", "0x76"), 16)

i2c = board.I2C()
bme = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=BME_ADDRESS)

expiry = Properties(PacketTypes.PUBLISH)
expiry.MessageExpiryInterval = 300


def cpu_temp():
    with open("/sys/class/thermal/thermal_zone0/temp") as f:
        return round(int(f.read()) / 1000, 1)


def wifi_rssi():
    try:
        with open("/proc/net/wireless") as f:
            for line in f:
                if line.strip().startswith("wlan0:"):
                    return float(line.split()[3].rstrip("."))
    except (OSError, ValueError, IndexError):
        pass
    return None


def uptime_s():
    with open("/proc/uptime") as f:
        return int(float(f.read().split()[0]))


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection refused: {reason_code}")
        return
    print(f"Connected as {CLIENT_ID}")
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id=CLIENT_ID,
    protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=2, max_delay=120)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()

client.connect(BROKER, PORT, keepalive=60)
client.loop_start()

try:
    while True:
        now = int(time.time())
        reading = {
            "temperature": round(bme.temperature, 2),
            "humidity": round(bme.relative_humidity, 1),
            "pressure": round(bme.pressure, 1),
            "ts": now,
        }
        health = {
            "cpu_temp": cpu_temp(),
            "rssi": wifi_rssi(),
            "uptime": uptime_s(),
            "ts": now,
        }
        client.publish(f"{PREFIX}/env", json.dumps(reading), qos=1, properties=expiry)
        client.publish(f"{PREFIX}/health", json.dumps(health), qos=0, properties=expiry)
        print(reading, health)
        time.sleep(INTERVAL)
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()

Test it in the foreground with python node.py, then watch from another machine:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-4b9e27 -V mqttv5 \
  -t 'connectmq/demo/4b9e27/#' -v

The free public MQTT broker supports MQTT 5 and is fine for this check, but everything on it is publicly readable, so use a random prefix like 4b9e27.

Running Unattended

A sensor node is only useful if it comes back by itself after a power cut or crash. Create /etc/systemd/system/sensor-node.service:

[Unit]
Description=Zero 2 W MQTT sensor node
After=network-online.target
Wants=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/node
EnvironmentFile=/home/pi/node/node.env
ExecStart=/home/pi/node/.venv/bin/python node.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Put your connection settings in /home/pi/node/node.env and restrict it with chmod 600:

MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_PREFIX=site1/office/zero-node-01

Enable with sudo systemctl enable --now sensor-node. For more about service files and logs, see running a Python MQTT script on boot.

Updating Nodes in the Field

Once a node is mounted on a wall, you will still want to change its code. Keep the application in a git repository, and update with git pull followed by sudo systemctl restart sensor-node over SSH. Because the connection settings live in node.env rather than in the script, the same code runs on every node and only the environment file differs. Before pushing a change to all nodes, test it on one and watch its health and status topics for a day. A node that stops publishing health messages but still shows online is usually stuck in a sensor read, which is a hint to add timeouts around hardware access.

Hardening Checklist

  1. Use an official or high-quality supply; run vcgencmd get_throttled and expect throttled=0x0.
  2. Keep the journal in RAM and avoid writing files in the publish loop.
  3. Enable the hardware watchdog with RuntimeWatchdogSec=15 in /etc/systemd/system.conf so a frozen kernel reboots.
  4. Mount the BME280 on a short cable away from the board; the Zero warms the air around it.
  5. Publish health data, and set the last will so the status topic shows offline when the node disappears.
  6. Use TLS and per-device credentials before the node leaves your desk.

Scaling to Many Nodes

One Zero 2 W is a project; twenty of them is a fleet. Give every node its own credentials and client ID, a predictable topic such as site1/<room>/<node>/env, and the same image. A hosted MQTT broker removes the need to run and secure your own server. With ConnectMQ, each node's client ID is bound to its credentials, the portal's devices view lists nodes by their topic hierarchy, and a threshold alert rule can warn you when a room gets too humid. The free Serverless plan allows up to 25 connections, which covers a small fleet plus a dashboard.

If you would rather use simpler sensors, the Raspberry Pi DS18B20 MQTT guide works unchanged on the Zero 2 W, and the Raspberry Pi MQTT tutorial covers the publish and subscribe fundamentals behind this node.

Get Started with ConnectMQ

Put your Zero 2 W nodes on a cloud MQTT broker they can reach from any network. Sign up for ConnectMQ for free, no credit card needed, create a broker, and paste the generated username, password and client ID into node.env. Your node connects over TLS on port 8883 with MQTT 5, and every reading shows up in the live topic explorer.

Frequently Asked Questions

Can a Raspberry Pi Zero 2 W run on batteries?

Only for short periods, because it cannot deep sleep and draws power continuously. For long battery life use a microcontroller such as an ESP32 and keep the Zero 2 W for powered locations.

Is 512 MB of RAM enough for an MQTT sensor node?

Yes. A Python script with paho-mqtt, TLS and a sensor library uses a small fraction of it on Raspberry Pi OS Lite, leaving room for a second service.

Why does my Zero 2 W keep disconnecting from MQTT?

The usual causes are a weak power supply, a poor 2.4 GHz signal or aggressive Wi-Fi power saving. Check vcgencmd get_throttled, the RSSI in the health message, and try disabling power save.

Does the Zero 2 W support MQTT 5?

Yes. MQTT 5 is a client library feature, and paho-mqtt 2.x supports it fully, including message expiry and user properties.

Related articles

Pico W vs Pico 2 W for MQTT: RP2040 vs RP2350 Performance, Memory and TLS

The Pico 2 W keeps the Pico W's footprint and Wi-Fi chip but swaps the RP2040 for the faster RP2350. This comparison looks at what that means for MQTT devices: CPU, memory, TLS, security features, software support, and which board to buy for which project.

8 min read

Raspberry Pi RFID MQTT Access Logging with the RC522 Reader

Wire an RC522 RFID reader to a Raspberry Pi, read tags in Python, check them against a local allowlist and publish every access attempt as an MQTT event that loggers, dashboards and alerting tools can consume.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.