Project Overview & Spec Sheet
If you have tried to follow older tutorials to set a static IP on a Raspberry Pi, you have likely hit a wall. The release of Raspberry Pi OS 'Bookworm' (Debian 12) completely replaced the legacy dhcpcd networking daemon with NetworkManager. Editing /etc/dhcpcd.conf will do absolutely nothing on a modern Pi 5 or updated Pi 4.
This guide provides the exact, up-to-date terminal commands to lock in a static IP using nmcli, paired with a practical embedded hardware build: a GPIO-driven network status monitor that gives you instant visual feedback if your headless Pi drops off the network.
| Parameter | Value |
|---|---|
| Target Board Variant | Raspberry Pi 5 (4GB) or Pi 4 Model B (Running Bookworm / Debian 12+) |
| Difficulty Rating | Intermediate (OS networking + basic GPIO wiring) |
| Estimated Time | 45 minutes |
| Network Stack | NetworkManager (nmcli) |
Parts List
- Compute: Raspberry Pi 5 (4GB RAM) or Raspberry Pi 4 Model B
- Power: Official 27W USB-C PD Power Supply (for Pi 5) or 15W (for Pi 4)
- Storage: 64GB microSD card (A2 application class, e.g., SanDisk Extreme)
- Indicators: 1x 5mm Green LED, 1x 5mm Red LED
- Passives: 2x 330Ω through-hole resistors
- Wiring: Half-size breadboard, 4x male-to-female jumper wires
The Bookworm Shift: NetworkManager vs. dhcpcd
Before touching the terminal, it is critical to understand why the ecosystem changed. For years, the Raspberry Pi relied on dhcpcd to handle IP assignments. However, dhcpcd struggles with modern roaming, complex VPN routing, and cellular failovers. NetworkManager is the industry standard for Linux desktops and servers, and the Raspberry Pi Foundation adopted it as the default in Bookworm.
When you configure a static IP now, you are not editing a global text file. You are modifying a specific connection profile stored in NetworkManager's database. This means you must target the connection by its exact string name, not just the hardware interface (like eth0).
Step-by-Step: Setting the Static IP via nmcli
Boot your Pi, connect via SSH or a local terminal, and follow these exact steps. We will assign 192.168.1.50 on a standard home subnet.
- Identify your active connection name.
Run the following command to list all profiles:
Look under the 'NAME' column. For a wired Ethernet connection, it is usuallynmcli connection showWired connection 1. For Wi-Fi, it will be your SSID name. - Assign the static IP and subnet mask.
NetworkManager requires CIDR notation (e.g.,/24), not a dotted subnet mask.nmcli connection modify "Wired connection 1" ipv4.addresses 192.168.1.50/24 - Set the default gateway.
This is your router's IP address.nmcli connection modify "Wired connection 1" ipv4.gateway 192.168.1.1 - Define DNS servers.
Without this, your Pi will have an IP but won't resolve domain names.nmcli connection modify "Wired connection 1" ipv4.dns "1.1.1.1,8.8.8.8" - Switch the IPv4 method to manual.
This tells NetworkManager to stop asking the router for a DHCP lease.nmcli connection modify "Wired connection 1" ipv4.method manual - Apply the changes.
Restart the connection profile to apply the new IP immediately.nmcli connection up "Wired connection 1"
ssh pi@192.168.1.50).
Hardware Build: Network Status GPIO Indicator
When running a headless Pi in a server closet or outdoor enclosure, you rarely have a monitor attached. Wiring a physical network status LED saves you from plugging in a laptop to diagnose a DHCP timeout or a dropped cable.
Pin Mapping Table
| Component | GPIO Pin (BCM) | Physical Pin | Wiring Note |
|---|---|---|---|
| Green LED (Anode) | GPIO 17 | Pin 11 | Connect via 330Ω resistor |
| Red LED (Anode) | GPIO 27 | Pin 13 | Connect via 330Ω resistor |
| Both LEDs (Cathode) | GND | Pin 9 | Shared ground rail |
Wire the anodes (long legs) of the LEDs to the resistors, then to the respective GPIO pins. Wire the cathodes (short legs) directly to the breadboard's ground rail, and connect that rail to Pin 9 on the Pi.
Python Network Monitor Daemon
This Python script uses the gpiozero library (pre-installed on Raspberry Pi OS) to monitor the active IP address of the eth0 interface. If it detects a valid, non-link-local IP, the green LED stays solid. If the network drops or falls back to an APIPA address (169.254.x.x), the red LED blinks.
#!/usr/bin/env python3
import socket
import fcntl
import struct
import time
import logging
from gpiozero import LED
# --- Pin Definitions ---
GREEN_LED_PIN = 17
RED_LED_PIN = 27
# --- Hardware Setup ---
green_led = LED(GREEN_LED_PIN)
red_led = LED(RED_LED_PIN)
# --- Logging Setup ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def get_interface_ip(ifname):
"""Fetch the IP address of a specific network interface using socket ioctl."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# SIOCGIFADDR is the system call to get interface address
ip_packed = fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15].encode('utf-8'))
)
return socket.inet_ntoa(ip_packed[20:24])
except OSError as e:
logging.warning(f"Interface '{ifname}' not found or down: {e}")
return None
def is_valid_network(ip_addr):
"""Check if IP is valid and not an APIPA/link-local fallback."""
if ip_addr is None:
return False
if ip_addr.startswith('169.254.'):
logging.error('Detected APIPA fallback IP. DHCP/Static config failed.')
return False
return True
def main():
logging.info('Network Monitor Daemon started.')
interface = 'eth0' # Change to 'wlan0' for Wi-Fi monitoring
try:
while True:
current_ip = get_interface_ip(interface)
if is_valid_network(current_ip):
green_led.on()
red_led.off()
else:
green_led.off()
red_led.blink(on_time=0.5, off_time=0.5, background=False)
# Note: background=False blocks, so we use a manual loop for blink if we need continuous logging
# For a simple daemon, a manual sleep loop is cleaner:
red_led.off()
red_led.on()
time.sleep(0.5)
red_led.off()
time.sleep(0.5)
continue
time.sleep(5) # Poll every 5 seconds
except KeyboardInterrupt:
logging.info('Shutting down daemon.')
finally:
green_led.off()
red_led.off()
if __name__ == '__main__':
main()
dialout and gpio groups, or run the script via sudo if using legacy RPi.GPIO (though gpiozero handles standard user permissions gracefully via the lgpio backend).
Debugging: Failures and Error Strings
NetworkManager is strict about syntax. If you make a typo, it will fail silently or throw a specific error. Here is how to debug the most common roadblocks.
The Exact Error String
Error: unknown connection 'eth0'
Ranked Causes & Fixes
- Cause 1: Confusing Interface Name with Connection Name.
Fix:eth0is the hardware interface. NetworkManager uses profile names. Runnmcli connection showand use the exact string from the NAME column (e.g.,"Wired connection 1"). - Cause 2: Missing Quotes Around Strings with Spaces.
Fix: If your connection is namedWired connection 1, you must wrap it in quotes in the terminal, otherwisenmclithinks "Wired" is the connection and "connection" is an invalid parameter. - Cause 3: Typo in the UUID.
Fix: If you prefer using UUIDs instead of names to avoid space issues, copy the exact UUID fromnmcli connection showand use theuuidflag.
The First Three Things to Check When It Fails
If you applied the config but cannot reach the Pi, check these three items immediately:
- Router DHCP Conflict: Did you assign
192.168.1.50but your router's DHCP pool is already handing out that IP to a phone or TV? Log into your router and either reserve the IP via MAC address or shrink the DHCP pool to.100 - .254. - CIDR Notation Mismatch: Did you type
192.168.1.50/255.255.255.0? NetworkManager will reject this. It must be/24. - Gateway Reachability: Ping your gateway from the Pi (
ping 192.168.1.1). If it fails, your subnet mask or gateway IP is typed incorrectly.
Extending and Simplifying the Build
How to Simplify: If you are running the Raspberry Pi Desktop environment and prefer GUIs, you can bypass the terminal entirely. Open the application menu, navigate to Preferences → Advanced Network Configuration (which launches nm-connection-editor). From there, you can click the gear icon next to your Ethernet connection, go to the IPv4 tab, switch to 'Manual', and type in your addresses using a visual form.
How to Extend: To make this a true IoT deployment, swap the 5mm LEDs for an I2C OLED display (like the SSD1306 128x64). You can modify the Python script to print the exact IP address, subnet mask, and uptime directly to the screen. Alternatively, integrate the paho-mqtt library into the Python daemon to publish a 'network_status' payload to a Home Assistant MQTT broker whenever the IP changes or drops.
Frequently Asked Questions
How do I set a static IP on Raspberry Pi 5 Bookworm?
You must use the nmcli command-line tool, as the legacy dhcpcd.conf method is deprecated and ignored in Bookworm. Identify your connection name with nmcli connection show, then use nmcli connection modify to set the ipv4.addresses, ipv4.gateway, and ipv4.dns, finally setting ipv4.method manual and restarting the connection.
Why did my static IP disappear after rebooting Raspberry Pi OS?
This almost always happens because you edited /etc/dhcpcd.conf on a Bookworm system. Since NetworkManager controls the network stack on boot, it overrides or ignores the old dhcpcd file. You must apply the static IP directly to the NetworkManager connection profile using nmcli so it persists in the system's network database.
Can I set a static IP via the desktop GUI instead of terminal?
Yes. If you have the Raspberry Pi desktop installed, open the 'Advanced Network Configuration' tool from the Preferences menu. Select your active connection, navigate to the IPv4 Settings tab, change the Method from 'Automatic (DHCP)' to 'Manual', and input your Address, Netmask, and Gateway in the provided fields.
Does setting a static IP affect my Wi-Fi connection?
No, NetworkManager treats Ethernet and Wi-Fi as separate connection profiles. Modifying 'Wired connection 1' will not touch your Wi-Fi profile. If you want a static IP on Wi-Fi, you must run the exact same nmcli modify commands, but target the SSID name of your Wi-Fi network instead of the wired connection name.
For more details on NetworkManager syntax, refer to the official Raspberry Pi OS networking documentation and the NetworkManager nmcli manual.






