When building network projects with a Raspberry Pi, the difference between a weekend toy and a reliable 24/7 infrastructure node comes down to hardware selection, power delivery, and explicit software configuration. This guide walks through building a dual-purpose network appliance: a Pi-hole DNS sinkhole paired with a Mosquitto MQTT broker for local IoT telemetry, complete with an I2C OLED status dashboard.
The code and hardware configurations in this guide specifically target the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Lite (64-bit, Bookworm). We will bypass the common pitfalls of headless network nodes by adding a physical status display and addressing the strict security defaults introduced in Mosquitto v2.0+.
Project Spec Sheet & Parts List
Difficulty: Intermediate | Estimated Time: 2 Hours | Cost: ~$115 USD
| Component | Exact Model / Variant | Why This Specific Part |
|---|---|---|
| Compute Board | Raspberry Pi 5 (8GB) | 8GB RAM prevents OOM kills when Pi-hole FTL and Mosquitto spike during heavy DNS queries or MQTT message bursts. |
| Power Supply | Official Raspberry Pi 27W USB-C PD | The Pi 5 requires 5V/5A via USB-C PD to enable full peripheral current limits. Third-party 5V/3A supplies will throttle USB ports and cause brownouts. |
| Cooling | Official Active Cooler | Network daemons cause sustained CPU loads. The Active Cooler keeps the BCM2712 under 60°C without the bulk of a tower cooler. |
| Storage | Samsung PRO Endurance 64GB (A2) | Network nodes write constant logs. Endurance-rated microSD cards prevent filesystem corruption from write-wear. |
| Status Display | SSD1306 128x64 I2C OLED (0.96") | Provides headless IP and daemon status without needing to SSH in or plug in an HDMI monitor. |
| Wiring | Female-to-Female Dupont (20cm) | Standard 2.54mm pitch jumper wires for the I2C bus connection. |
Hardware Assembly & Pin Mapping
The Raspberry Pi 5 retains the standard 40-pin GPIO header layout, but the underlying I2C bus routing remains on I2C1 (BCM GPIO 2 and 3). Wire the SSD1306 OLED display to the Pi 5 using the following pin mapping.
| Pi 5 Pin Name | BCM GPIO | Physical Pin | OLED Pin | Function |
|---|---|---|---|---|
| 3V3 Power | N/A | 1 | VCC | 3.3V logic power for the OLED controller |
| Ground | N/A | 6 | GND | Common ground reference |
| GPIO 2 (SDA1) | 2 | 3 | SDA | I2C Data line |
| GPIO 3 (SCL1) | 3 | 5 | SCL | I2C Clock line |
- Apply the Active Cooler: Peel the adhesive backing off the thermal pads and press the Active Cooler firmly onto the BCM2712 SoC and PMIC. Plug the 4-pin PWM fan connector into the dedicated JST fan header on the Pi 5 board.
- Connect the OLED: Using the Dupont wires, connect the display to the physical pins 1, 3, 5, and 6 as mapped above. Ensure the SDA and SCL lines are not swapped; doing so won't damage the board, but the display will not initialize.
- Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit). In the OS Customisation menu, enable SSH, set your hostname to
netnode, and configure your WiFi or rely on Ethernet. - Boot and Verify I2C: SSH into the Pi and run
sudo raspi-configto enable the I2C interface under Interface Options. Reboot, then runi2cdetect -y 1. You should see3cin the grid, confirming the SSD1306 is responding at its default hex address.
Software Configuration & Python Status Monitor
Install Pi-hole using the official automated installer, which handles the DNSmasq configuration and web server setup. For Mosquitto, the Bookworm repository includes v2.0+, which defaults to a highly restrictive security model that blocks external connections unless explicitly configured.
Configuration Note: After installing Mosquitto (sudo apt install mosquitto mosquitto-clients), you must edit /etc/mosquitto/mosquitto.conf and add listener 1883 0.0.0.0 and allow_anonymous true (or configure ACLs/passwords for production). Without the listener directive, Mosquitto v2.0+ only binds to localhost.
To monitor the node without SSH, we use a Python script leveraging the luma.oled library for the display and paho.mqtt.client to verify the broker is accepting local connections. Install the dependencies:
sudo apt install python3-pip python3-smbus i2c-tools
pip3 install luma.oled paho-mqtt --break-system-packages
Save the following code as network_monitor.py. This script includes explicit error handling for I2C bus faults and MQTT connection refusals.
import time
import socket
import sys
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from luma.core.render import canvas
from PIL import ImageFont
import paho.mqtt.client as mqtt
# --- Pin & Hardware Definitions ---
# I2C Port 1 is standard for Pi 5 SDA1/SCL1 (Pins 3 & 5)
I2C_PORT = 1
OLED_ADDRESS = 0x3C
MQTT_BROKER = "localhost"
MQTT_PORT = 1883
def get_ip_address():
"""Gets the primary IP address by opening a dummy UDP socket."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
except Exception:
ip = "No Network"
finally:
s.close()
return ip
def on_connect(client, userdata, flags, rc, properties=None):
if rc == 0:
client.connected_flag = True
else:
client.bad_connection_flag = True
# Initialize MQTT Client (Paho v2.0+ requires CallbackAPIVersion)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
try:
# Initialize I2C OLED
serial = i2c(port=I2C_PORT, address=OLED_ADDRESS)
device = ssd1306(serial)
font = ImageFont.load_default()
except Exception as e:
print(f"Fatal: Cannot initialize OLED. Check wiring. Error: {e}")
sys.exit(1)
while True:
ip_addr = get_ip_address()
mqtt_status = "Disconnected"
try:
if not client.is_connected():
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()
time.sleep(1) # Allow callback to execute
if client.is_connected():
mqtt_status = "Broker OK"
except ConnectionRefusedError:
mqtt_status = "Err: Refused"
except Exception as e:
mqtt_status = f"Err: {type(e).__name__}"
# Render to OLED
try:
with canvas(device) as draw:
draw.text((0, 0), f"IP: {ip_addr}", font=font, fill="white")
draw.text((0, 20), f"MQTT: {mqtt_status}", font=font, fill="white")
draw.text((0, 40), "Pi-hole: Active", font=font, fill="white")
except OSError as e:
print(f"I2C Write Error: {e}")
time.sleep(5)
Debugging Network & MQTT Failures
When your Raspberry Pi network projects fail, the issue is rarely the code itself; it is almost always power, IP conflicts, or daemon security policies. If the node drops off the network or the OLED shows an error, check these first three things:
- Power Supply Brownout: Run
dmesg | grep -i voltage. If you see "Under-voltage detected", your power supply is failing under the combined load of the CPU, WiFi/Ethernet PHY, and I2C bus. Swap to the official 27W PSU. - DHCP Lease Conflicts: If Pi-hole is acting as your DHCP server, ensure its IP pool does not overlap with your primary router's pool, or disable DHCP on the router entirely. Duplicate IPs will cause intermittent packet loss.
- Mosquitto Listener Binding: If remote IoT devices cannot connect to the broker, verify
/etc/mosquitto/mosquitto.confcontainslistener 1883 0.0.0.0. Restart the service withsudo systemctl restart mosquitto.
Exact Error Strings and Ranked Causes
Error 1: ConnectionRefusedError: [Errno 111] Connection refused
- Cause A (Most Likely): Mosquitto is not running, or crashed due to a malformed config file. Check
sudo systemctl status mosquitto. - Cause B: Mosquitto v2.0+ default security is blocking the connection because the
listenerdirective is missing from the config. - Cause C: UFW or iptables is actively blocking port 1883. Run
sudo ufw allow 1883/tcp.
Error 2: OSError: [Errno 121] Remote I/O error (in Python script)
- Cause A: Loose Dupont wire on the SDA or SCL line. The I2C bus lacks pull-up resistors on the Pi; rely on the OLED module's onboard pull-ups and ensure a tight physical connection.
- Cause B: The I2C address is incorrect. Some SSD1306 clones use
0x3Dinstead of0x3C. Verify withi2cdetect -y 1.
Error 3: NXDOMAIN or DNS Timeout on client devices
- Cause A: The Pi-hole FTL service crashed. Check
pihole status. - Cause B: The Pi lost its upstream DNS configuration. Verify
/etc/resolv.confor the Pi-hole web UI upstream DNS settings.
Extending and Simplifying the Build
To Simplify: If you don't want to wire an I2C OLED, you can strip the hardware down to just the Pi and Ethernet. Replace the Python script with a simple cron job that emails or pushes a notification via Pushover if the Pi-hole API (/admin/api.php?summary) fails to respond. You can also use the official Pi-hole documentation to configure Telegram alerts natively.
To Extend: Transform this node into a full smart-home hub by adding a Zigbee coordinator. Plug a Sonoff Zigbee 3.0 USB Dongle Plus-P into a USB 2.0 port (using a short USB extension cable to avoid 2.4GHz WiFi interference). Install Zigbee2MQTT via Docker. The MQTT broker we configured will seamlessly bridge the Zigbee telemetry to your home automation platform like Home Assistant. For advanced storage, the Raspberry Pi 5 PCIe lane allows you to add an NVMe HAT, eliminating microSD card wear entirely.
FAQ: Common Raspberry Pi Network Project Questions
Which Raspberry Pi board variant is best for network projects in 2026?
For dedicated network infrastructure (routers, NAS, MQTT brokers), the Raspberry Pi 5 (4GB or 8GB) is the current standard due to its true Gigabit Ethernet PHY and PCIe Gen 2 support for NVMe storage. However, if your project involves distributed, low-power sensor nodes rather than a central server, the Raspberry Pi Zero 2 W remains the most cost-effective choice, provided you can tolerate its 100Mbps USB-tied Ethernet limitations and 512MB RAM constraint.
Why does my MQTT broker drop connections when the Pi reboots?
By default, Mosquitto does not persist session states or retained messages across reboots unless configured to do so. To fix this, you must enable persistence in /etc/mosquitto/mosquitto.conf by adding persistence true and persistence_location /var/lib/mosquitto/. Ensure the mosquitto user has write permissions to that directory, otherwise, the daemon will silently fail to save the database and drop all retained topics on reboot.
Can I run Pi-hole and a DHCP server on the same Raspberry Pi?
Yes, Pi-hole includes a built-in DHCP server (dnsmasq). However, you must disable the DHCP server on your primary ISP router first. Running two DHCP servers on the same VLAN will result in a race condition where clients randomly receive IP addresses with conflicting gateways, leading to intermittent internet outages. If your router's DHCP cannot be disabled, you must isolate the Pi-hole on a separate VLAN or use it strictly for DNS forwarding without enabling its DHCP module.






