If you are running a headless Raspberry Pi as a local server, MQTT broker, or sensor node, relying on DHCP is a gamble. Your router will eventually reassign its IP address, breaking your SSH shortcuts and dashboard automations. The direct answer for modern setups: to set a static IP on a Raspberry Pi running OS Bookworm or newer, you must use nmcli (NetworkManager). The legacy dhcpcd.conf method is deprecated and will silently fail on current images.
The exact command to assign a static IP (e.g., 192.168.1.50) to your 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
Below, we will walk through the hardware build for a headless Pi 5 sensor node, map the I2C pins, write a robust Python telemetry server bound to that static IP, and debug the exact network errors that trip up most makers.
The Bookworm Shift: Why Old Tutorials Fail
If you have been working with Raspberry Pis for a few years, your muscle memory probably points you toward /etc/dhcpcd.conf. Stop right there. Starting with Raspberry Pi OS Bookworm (the default for Pi 4 and Pi 5 in 2024–2026), the networking daemon switched from dhcpcd to NetworkManager.
If you edit dhcpcd.conf on a fresh Pi 5 image, the system will simply ignore it. NetworkManager controls the interfaces via nmcli in the terminal or nmtui for a text-based UI. This shift aligns the Pi with standard enterprise Linux distributions (like Fedora and Ubuntu), but it catches thousands of hobbyists off guard when their static IP configurations vanish on reboot.
Network Parameters & CIDR Reference Table
Before locking in your IP, you need to match your router's subnet. Most home routers use a /24 subnet, but if you are running a segmented IoT VLAN, you might be using a smaller pool. Use this table to ensure your static IP and subnet mask align with your gateway.
| CIDR Notation | Subnet Mask | Total IPs | Usable Hosts | Typical Use Case |
|---|---|---|---|---|
| /24 | 255.255.255.0 | 256 | 254 | Standard Home LAN (192.168.1.x) |
| /25 | 255.255.255.128 | 128 | 126 | Small Office / Guest Network |
| /26 | 255.255.255.192 | 64 | 62 | IoT VLAN / Smart Home Segment |
| /27 | 255.255.255.224 | 32 | 30 | Isolated Sensor Subnet |
Hardware Build: Pi 5 Headless Sensor Node
To demonstrate binding a service to our new static IP, we are building a headless environmental monitor. We will use the Raspberry Pi 5 (4GB variant) due to its PCIe lane and updated I2C clock stretching capabilities, paired with a Bosch BME280 I2C sensor for temperature, humidity, and barometric pressure.
Parts List
- Board: Raspberry Pi 5 (4GB RAM) with active cooler
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652)
- Wiring: 4x silicone jumper wires (female-to-female)
- Power: Official 27W USB-C PD Power Supply
Pin Mapping Table
The Pi 5 retains the standard 40-pin header layout, but its I2C bus performance is vastly improved. Wire the BME280 to the primary I2C bus as follows:
| Pi 5 Physical Pin | GPIO / Function | BME280 Breakout Pin | Wire Color (Typical) |
|---|---|---|---|
| 1 | 3V3 Power | VIN | Red |
| 6 | GND | GND | Black |
| 3 | GPIO 2 (SDA1) | SDA | Yellow |
| 5 | GPIO 3 (SCL1) | SCL | Blue |
Step-by-Step: Configuring the Static IP
Connect to your Pi via SSH or a monitor. Ensure your Ethernet cable is plugged in (or adapt these commands for Wi-Fi by replacing the connection name).
- Identify your active connection name:
Runnmcli con show --active. Look under the "NAME" column. For Ethernet, it is usuallyWired connection 1. For Wi-Fi, it will be your SSID name. - Apply the static IP configuration:
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
Note: Adjust the gateway (192.168.1.1) to match your specific router's IP. - Restart the connection to apply changes:
sudo nmcli con up "Wired connection 1"
Warning: If you are doing this over SSH, your session will drop immediately. Reconnect using the new static IP. - Verify the assignment:
ip -4 addr show eth0(orenp1s0on some Pi 5 kernel builds). You should seeinet 192.168.1.50/24.
Python Telemetry Server (Code & Pin Definitions)
Now that the Pi 5 is locked to 192.168.1.50, we need a service to serve data. The following Python script reads the BME280 via I2C and hosts a lightweight socket server strictly bound to our static IP. This prevents the service from accidentally binding to a fallback APIPA address (169.254.x.x) if the network drops.
sudo raspi-config and install the required libraries: pip install smbus2 RPi.bme280.
import socket
import smbus2
import bme280
import sys
import time
import json
# --- Hardware & Pin Definitions ---
I2C_BUS_ID = 1 # /dev/i2c-1 (Pi 5 default for GPIO 2/3)
SDA_PIN = 2 # Physical Pin 3
SCL_PIN = 3 # Physical Pin 5
BME_ADDRESS = 0x77 # Adafruit BME280 default (0x76 for some generic clones)
# --- Network Definitions ---
STATIC_IP = "192.168.1.50"
PORT = 8080
def init_sensor():
"""Initialize I2C bus and load BME280 calibration parameters."""
try:
bus = smbus2.SMBus(I2C_BUS_ID)
calibration_params = bme280.load_calibration_params(bus, BME_ADDRESS)
return bus, calibration_params
except FileNotFoundError:
print(f"[FATAL] I2C bus /dev/i2c-{I2C_BUS_ID} not found. Is I2C enabled in raspi-config?")
sys.exit(1)
except OSError as e:
print(f"[FATAL] Cannot reach BME280 at address {hex(BME_ADDRESS)}. Check SDA/SCL wiring. Error: {e}")
sys.exit(1)
def start_server():
bus, cal_params = init_sensor()
# Create TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
# Bind STRICTLY to the static IP, not 0.0.0.0
sock.bind((STATIC_IP, PORT))
sock.listen(5)
print(f"[INFO] Telemetry server listening on http://{STATIC_IP}:{PORT}")
except socket.error as e:
print(f"[FATAL] Failed to bind to {STATIC_IP}:{PORT}. Is the static IP active? Error: {e}")
sys.exit(1)
while True:
conn, addr = sock.accept()
try:
# Read sensor data
raw_data = bme280.sample(bus, BME_ADDRESS, cal_params)
payload = {
"temperature_c": round(raw_data.temperature, 2),
"humidity_pct": round(raw_data.humidity, 2),
"pressure_hpa": round(raw_data.pressure, 2),
"host_ip": STATIC_IP
}
# Send HTTP response
http_response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + json.dumps(payload)
conn.sendall(http_response.encode('utf-8'))
except Exception as e:
print(f"[ERROR] Sensor read or network transmission failed: {e}")
finally:
conn.close()
if __name__ == "__main__":
start_server()
Debugging: Ranked Causes for Connection Failures
When configuring static IPs on embedded Linux, things go wrong. Here are the exact error strings you will encounter, ranked by frequency, and how to fix them.
1. Error: unknown connection 'Wired connection 1'
- Cause: You typed the wrong connection profile name. On Pi 5, the Ethernet interface might be named
eth0orenp1s0, and the NetworkManager profile might be named something else. - Fix: Run
nmcli con showto list all profiles. Use the exact string from the "NAME" column in yournmcli con modcommand.
2. Error: ssh: connect to host 192.168.1.50 port 22: Network is unreachable
- Cause: Subnet mismatch. Your host PC is on
192.168.0.xbut you assigned the Pi to192.168.1.x, or you set the gateway incorrectly. - Fix: Check your router's actual subnet. If your router is
192.168.0.1, your Pi's IP must be192.168.0.50/24with the gateway set to192.168.0.1.
3. Error: RTNETLINK answers: File exists
- Cause: IP conflict. Another device on the network (or a secondary interface on the Pi itself) already holds
192.168.1.50. - Fix: Ping the target IP from another machine before assigning it. If it replies, pick a different IP outside your router's DHCP pool (e.g., use .50 if the DHCP pool is .100-.254).
First Three Things to Check When It Fails
If your Pi drops off the network after applying the nmcli commands, plug in a monitor and keyboard, log in locally, and check these three items in order:
- Verify the Gateway: Run
ip route. Ensure the default route points to your actual router IP. If it is missing, youripv4.gatewayparameter was typed wrong. - Check DNS Resolution: Run
ping 8.8.8.8(tests routing) followed byping google.com(tests DNS). If the first works but the second fails, youripv4.dnsparameter is missing or malformed. - Inspect NetworkManager Logs: Run
journalctl -u NetworkManager -n 50. This will reveal if the daemon is rejecting your configuration due to a syntax error or a conflicting DHCP lease.
Extending and Simplifying the Build
How to Simplify: Router-Side DHCP Reservation
If you do not want to manage network configurations on the Pi itself, the simplest alternative is a DCP Reservation (or Static Lease) in your router's admin panel. You map the Pi's MAC address (find it via ip link show eth0) to 192.168.1.50 inside the router. The Pi continues to request an IP via DHCP, but the router always hands it the exact same address. This is foolproof for simple home networks, though less ideal for industrial deployments where the Pi might be moved between different subnets.
How to Extend: Adding MQTT and TLS
To scale this sensor node for a whole-home automation network, replace the raw Python socket server with an MQTT publisher using the paho-mqtt library. Push the BME280 JSON payload to a topic like home/sensors/pi5/bme280. For security, generate a self-signed certificate and configure Mosquitto on the Pi to require TLS encryption, ensuring your environmental data cannot be sniffed by rogue devices on the local Wi-Fi.
For more advanced sensor integrations, refer to the Adafruit BME280 documentation for wiring variations and I2C address shifting.






