To set a static IP on a Raspberry Pi 5 running Raspberry Pi OS Bookworm, you must use NetworkManager via the nmcli command-line tool. The legacy dhcpcd daemon is deprecated and removed in modern Pi OS releases. The exact command to assign a static IP of 192.168.1.50 on the primary Ethernet interface is:

sudo nmcli con mod "Wired connection 1" ipv4.addresses 192.168.1.50/24 ipv4.gateway 192.168.1.1 ipv4.dns "1.1.1.1,8.8.8.8" ipv4.method manual
sudo nmcli con up "Wired connection 1"

This guide walks through the hardware integration for a headless Pi 5 sensor node, the exact nmcli configuration steps, and a Python verification script to confirm your network and I2C bus are stable before deploying to a remote enclosure.

The Bookworm Shift: NetworkManager vs. dhcpcd

If you have been working with Raspberry Pis for a few years, your muscle memory likely reaches for /etc/dhcpcd.conf. Stop. Raspberry Pi OS Bookworm (Debian 12) transitioned the default network stack to NetworkManager. Attempting to install or enable dhcpcd on a fresh Bookworm image will result in package conflicts and broken routing tables.

Raspberry Pi OS Network Stack Comparison
Feature Bullseye (Legacy) Bookworm (Current 2026)
Primary Daemon dhcpcd NetworkManager
Config File /etc/dhcpcd.conf /etc/NetworkManager/system-connections/
CLI Tool ifconfig / ip nmcli / nmtui
Interface Naming eth0, wlan0 Connection profiles ("Wired connection 1")
GUI Applet lxplug-network nm-applet (Wayfire/Wayland)
⚠️ Bench Note: NetworkManager identifies interfaces by connection profile names, not hardware labels. If you plug in a USB-to-Ethernet adapter, it will generate a new profile (e.g., "Wired connection 2") rather than just mapping to eth1. Always verify your active profile name with nmcli con show --active before modifying parameters.

Hardware Build: Pi 5 Environmental Sensor Node

To verify our static IP configuration and ensure the I2C bus isn't locking up during network state changes, we are building a basic environmental node. This targets the Raspberry Pi 5 (8GB variant), utilizing the dedicated RTC battery header and the standard 40-pin GPIO.

Parts List

  • Board: Raspberry Pi 5 (8GB RAM) with active cooler
  • Sensor: Bosch BME280 I2C breakout (3.3V logic)
  • Indicator: 5mm Green LED with 330Ω current-limiting resistor
  • Power: 27W USB-C PD power supply (official Pi 27W unit recommended to avoid brownout warnings on the Pi 5)
  • Wiring: 4x female-to-female jumper wires, 1x half-size breadboard

Pin Mapping Table

Component Component Pin Pi 5 GPIO (Physical Pin) Function
BME280 VIN / VCC 3V3 (Pin 1) Power (3.3V)
BME280 GND GND (Pin 6) Ground
BME280 SCL GPIO 3 (Pin 5) I2C Clock
BME280 SDA GPIO 2 (Pin 3) I2C Data
Status LED Anode (+) GPIO 17 (Pin 11) Digital Output (via 330Ω resistor)
Status LED Cathode (-) GND (Pin 9) Ground

Step-by-Step: Configuring the Static IP via nmcli

Boot your Pi 5, open a terminal (or SSH in via the current DHCP address), and follow these steps. Ensure you substitute 192.168.1.50 with your desired IP, and 192.168.1.1 with your actual router gateway.

  1. Identify the active connection profile:
    nmcli con show --active
    Look for the name under the "NAME" column. For the built-in Gigabit Ethernet port, it is almost always Wired connection 1.
  2. Assign the static IPv4 address and subnet mask:
    sudo nmcli con mod "Wired connection 1" ipv4.addresses 192.168.1.50/24
    The /24 is the CIDR notation for a 255.255.255.0 subnet mask. Do not omit it, or NetworkManager will reject the command.
  3. Set the default gateway:
    sudo nmcli con mod "Wired connection 1" ipv4.gateway 192.168.1.1
  4. Define DNS servers:
    sudo nmcli con mod "Wired connection 1" ipv4.dns "1.1.1.1 8.8.8.8"
    Space-separated values work best here. Using Cloudflare and Google ensures external resolution if your local router's DNS forwarder hangs.
  5. Switch the IPv4 method from auto (DHCP) to manual:
    sudo nmcli con mod "Wired connection 1" ipv4.method manual
  6. Apply the changes by restarting the connection:
    sudo nmcli con up "Wired connection 1"
    If you are connected via SSH over Ethernet, your session will drop immediately. Reconnect using the new static IP.
💡 Pro Tip: If you prefer a visual interface over memorizing nmcli flags, type sudo nmtui in the terminal. This launches a text-based UI that edits the exact same NetworkManager configuration files but handles the CIDR and syntax validation for you.

Python Verification Script

Once your Pi 5 is back online at the static IP, deploy this Python script. It verifies the network route, checks the I2C bus for the BME280 sensor, and blinks the GPIO 17 LED to provide a physical "heartbeat" indicating the node is healthy. This targets the Pi 5's standard gpiozero and smbus2 libraries.

#!/usr/bin/env python3
"""
Pi 5 Static IP & Sensor Node Verification Script
Targets: Raspberry Pi 5 (Bookworm)
Dependencies: gpiozero, smbus2
"""

import socket
import time
import sys
from gpiozero import LED
from smbus2 import SMBus

# --- PIN & HARDWARE DEFINITIONS ---
STATUS_LED_PIN = 17
I2C_BUS_ID = 1
BME280_I2C_ADDR = 0x76  # Change to 0x77 if your breakout has the alternate jumper

# Network targets to verify routing
TARGET_HOST = "8.8.8.8"
TARGET_PORT = 53

# Initialize GPIO
status_led = LED(STATUS_LED_PIN)

def check_network_route():
    """Verifies outbound network connectivity without relying on DNS."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(3.0)
        sock.connect((TARGET_HOST, TARGET_PORT))
        sock.close()
        return True
    except socket.error as e:
        print(f"[NETWORK FAIL] Route check failed: {e}")
        return False

def read_bme280_chip_id():
    """Reads the chip ID register (0xD0) to verify I2C communication."""
    try:
        with SMBus(I2C_BUS_ID) as bus:
            chip_id = bus.read_byte_data(BME280_I2C_ADDR, 0xD0)
            if chip_id == 0x60:
                return True
            else:
                print(f"[I2C WARN] Unexpected Chip ID: {hex(chip_id)}")
                return False
    except FileNotFoundError:
        print("[I2C FAIL] I2C interface not enabled. Run 'sudo raspi-config'.")
        return False
    except OSError as e:
        print(f"[I2C FAIL] Device not found at {hex(BME280_I2C_ADDR)}. Check wiring. Error: {e}")
        return False

def main():
    print("Starting Pi 5 Node Verification...")
    
    net_ok = check_network_route()
    i2c_ok = read_bme280_chip_id()
    
    if net_ok and i2c_ok:
        print("[SUCCESS] Network and I2C bus are healthy.")
        # Heartbeat blink
        for _ in range(5):
            status_led.on()
            time.sleep(0.2)
            status_led.off()
            time.sleep(0.2)
        status_led.on() # Leave LED solid ON to indicate ready state
    else:
        print("[CRITICAL] Node verification failed. Check logs above.")
        # Error blink pattern
        status_led.blink(on_time=0.1, off_time=0.1, n=10, background=False)
        sys.exit(1)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nScript interrupted. Cleaning up GPIO.")
        status_led.off()
        sys.exit(0)

Debugging: Exact Errors and Ranked Causes

When migrating from older Pi OS versions or typing nmcli commands manually, you will inevitably hit syntax or daemon conflicts. Here are the exact error strings you will see, and how to fix them.

1. "Unit dhcpcd.service could not be found."

  • Cause: You are running Raspberry Pi OS Bookworm, but following a pre-2024 tutorial that tells you to restart the DHCP client daemon after editing /etc/dhcpcd.conf.
  • Fix: Abandon the dhcpcd approach entirely. Use the nmcli steps outlined above. If you absolutely require dhcpcd for a legacy enterprise deployment, you must explicitly install it via sudo apt install dhcpcd5 and disable NetworkManager, though this is highly discouraged on the Pi 5.

2. "Error: unknown connection 'eth0'."

  • Cause: NetworkManager uses profile names, not kernel interface names. eth0 is the hardware interface; "Wired connection 1" is the NetworkManager profile managing it.
  • Fix: Run nmcli con show to list all profiles. Replace eth0 in your command with the exact string in the NAME column, wrapped in quotes.

3. "Error: invalid prefix '24'" or "Error: invalid IPv4 address"

  • Cause: You forgot the CIDR suffix on the IP address, or you used a space instead of a slash (e.g., 192.168.1.50 24 instead of 192.168.1.50/24).
  • Fix: Ensure the address is formatted strictly as IP/MASK. For a standard home subnet, the mask is almost always /24.

The First Three Things to Check When It Fails

  1. Verify the Gateway: Ping your router (ping 192.168.1.1). If it fails, your subnet mask or gateway IP is wrong. Check your router's DHCP table to confirm its actual LAN IP.
  2. Check for IP Conflicts: If the network drops intermittently, another device on your LAN might already be using 192.168.1.50. Run arping -I eth0 192.168.1.50 from another machine to see if a different MAC address replies.
  3. Inspect NetworkManager Logs: Run journalctl -u NetworkManager -n 50 --no-pager. This will reveal if the interface is failing to bind to the IP due to a carrier link drop (bad Ethernet cable) or a configuration parsing error.

Extending and Simplifying the Build

Once your Pi 5 is locked to a static IP and the Python verification script confirms hardware health, you can scale this node for production environments.

How to Extend

  • Add MQTT Telemetry: Install paho-mqtt via pip. Modify the Python script to publish the BME280 temperature and humidity payloads to a local Mosquitto broker every 60 seconds. Because your IP is static, you can reliably bind this Pi to your home automation dashboard (like Home Assistant) without DNS resolution delays.
  • Implement PoE (Power over Ethernet): For remote attic or garage deployments, swap the standard Pi 5 base for a setup utilizing the official Raspberry Pi PoE+ HAT. This delivers both data and up to 25.5W of power over a single Cat6 cable, eliminating the need for a local 120V/230V AC outlet and USB-C brick.

How to Simplify

  • Router-Side DHCP Reservation: If you don't actually need the Pi to manage its own IP (e.g., you are moving the Pi between different physical networks), skip nmcli entirely. Leave the Pi on DHCP (ipv4.method auto), and configure your router to reserve 192.168.1.50 specifically for the Pi 5's MAC address. This keeps the Pi OS image portable while achieving the same functional result on your home network.

For deeper reading on Debian 12 networking standards, refer to the Debian NetworkManager Wiki and the official Raspberry Pi Configuration Documentation.