Getting a reliable network connection on a headless Raspberry Pi 5 requires moving past outdated tutorials. With the shift to Raspberry Pi OS Bookworm and later releases, wpa_supplicant and dhcpcd have been fully deprecated in favor of NetworkManager. If you are still trying to edit /etc/wpa_supplicant/wpa_supplicant.conf, your configuration will be ignored.

This guide provides a complete, modern Raspberry Pi network setup targeting the Raspberry Pi 5 (8GB variant). We will configure a headless static IP via nmcli, map the physical hardware interfaces, and build a Python-based GPIO network monitor that uses physical LEDs to alert you to packet loss or DHCP failures before you ever need to plug in a monitor.

Hardware Interface & GPIO Pin Mapping

Before writing configuration files or code, you need to understand the physical layer. The Pi 5 changed the power delivery and peripheral routing significantly compared to the Pi 4. Below is the exact hardware specification for the network interfaces and the GPIO pin mapping we will use for the physical status monitor.

Component / Interface Hardware / Chipset Specifications & Pin Details
Wireless LAN Infineon CYW43455 WiFi 6 (802.11ax), Dual-band 2.4/5GHz, Bluetooth 5.2
Gigabit Ethernet Broadcom BCM54213PE 10/100/1000 Mbps, true Gigabit (not shared with USB bus)
Status LED (Connected) GPIO 17 (Header Pin 11) 3.3V logic output, requires 220Ω - 330Ω current-limiting resistor
Status LED (Error/Down) GPIO 27 (Header Pin 13) 3.3V logic output, requires 220Ω - 330Ω current-limiting resistor
I2C Bus (For Extension) GPIO 2 (SDA) / GPIO 3 (SCL) Header Pins 3 & 5, 3.3V logic, requires 4.7kΩ pull-up resistors
Callout Tip: Pi 5 Power Constraints
The Pi 5 requires a 27W USB-C PD power supply to deliver full current to downstream peripherals. If you are using an older 15W Pi 4 supply, the board will limit peripheral current to 600mA. While the onboard WiFi chip will still function, adding external USB-to-Ethernet adapters or unpowered I2C OLED displays may cause brownouts and silent network drops.

Headless Configuration & Static IP Assignment

For a headless Raspberry Pi network setup, you should configure the network before first boot using the Raspberry Pi Imager's advanced settings (the gear icon). However, if the board is already flashed, or you need to change the configuration via an SSH session or serial console, use nmcli (NetworkManager Command Line Interface).

Follow these numbered steps to assign a static IP to the WiFi interface (wlan0):

  1. Scan for available networks to ensure your SSID is visible and note the exact capitalization:
    sudo nmcli device wifi list
  2. Create the connection profile with WPA2/WPA3 security and static IPv4 addressing. Replace the placeholders with your actual network details:
    sudo nmcli connection add type wifi con-name "Pi5_Static" ifname wlan0 ssid "YourSSID" \
      wifi-sec.key-mgmt wpa-psk wifi-sec.psk "YourPassword" \
      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 connection.autoconnect yes
  3. Activate the connection and verify the state:
    sudo nmcli connection up "Pi5_Static"
    nmcli connection show --active
  4. Disable the default DHCP profile to prevent IP conflicts on reboot:
    sudo nmcli connection modify "preconfigured" ipv4.method disabled

For authoritative details on NetworkManager configuration on modern Pi OS, refer to the official Raspberry Pi configuration documentation.

Python GPIO Network Monitor

Network drops on headless embedded systems are notoriously difficult to diagnose after the fact. This Python script runs as a background service, pinging your gateway and driving the GPIO LEDs mapped in the table above. Green indicates a healthy connection; Red indicates a dropped interface or high packet loss.

Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS (64-bit).
Dependencies: gpiozero and lgpio (the modern replacement for the deprecated RPi.GPIO library).

#!/usr/bin/env python3
"""
Raspberry Pi 5 Network Status Monitor
Uses gpiozero with the lgpio pin factory for Pi 5 compatibility.
"""
import os
import time
import subprocess
import socket

# Force gpiozero to use the modern lgpio backend required for Pi 5
os.environ['GPIOZERO_PIN_FACTORY'] = 'lgpio'

from gpiozero import LED

# --- PIN DEFINITIONS ---
# Refer to Hardware Interface Table for physical header pins
PIN_LED_CONNECTED = 17  # Header Pin 11
PIN_LED_ERROR = 27      # Header Pin 13

# Initialize LEDs
led_green = LED(PIN_LED_CONNECTED)
led_red = LED(PIN_LED_ERROR)

# Network target (your router's IP)
GATEWAY_IP = "192.168.1.1"

def check_interface_status():
    """Checks if wlan0 has an IP address assigned via nmcli."""
    try:
        result = subprocess.run(
            ['nmcli', '-g', 'IP4.ADDRESS', 'device', 'show', 'wlan0'],
            capture_output=True, text=True, timeout=5
        )
        return bool(result.stdout.strip())
    except Exception as e:
        print(f"Error checking interface: {e}")
        return False

def ping_gateway(ip, timeout=2):
    """Pings the gateway. Returns True if successful, False otherwise."""
    try:
        # -c 1 (count 1), -W 2 (timeout 2 seconds)
        response = subprocess.run(
            ['ping', '-c', '1', '-W', str(timeout), ip],
            capture_output=True, text=True, timeout=timeout + 2
        )
        return response.returncode == 0
    except subprocess.TimeoutExpired:
        return False
    except Exception as e:
        print(f"Ping error: {e}")
        return False

def main():
    print("Starting Network Monitor...")
    led_green.off()
    led_red.on()  # Red on during initial boot/check
    
    try:
        while True:
            has_ip = check_interface_status()
            
            if not has_ip:
                # Interface is down or DHCP failed
                led_green.off()
                led_red.on()
                print("[ALERT] wlan0 has no IP address.")
            else:
                # Interface has IP, check actual routing/connectivity
                if ping_gateway(GATEWAY_IP):
                    led_green.on()
                    led_red.off()
                else:
                    # IP assigned but gateway unreachable (e.g., DNS/AP isolation issue)
                    led_green.off()
                    led_red.blink(on_time=0.5, off_time=0.5)
                    print("[WARN] IP assigned, but gateway unreachable.")
            
            time.sleep(10) # Check every 10 seconds
            
    except KeyboardInterrupt:
        print("\nMonitor stopped by user.")
    except Exception as e:
        print(f"Fatal error in main loop: {e}")
    finally:
        led_green.off()
        led_red.off()
        print("LEDs cleaned up.")

if __name__ == "__main__":
    main()

For deeper integration with hardware pins and advanced PWM fading effects for the LEDs, consult the GPIO Zero official documentation.

Debugging: Connection Failures & Error Strings

When a headless Raspberry Pi network setup fails, you are usually flying blind. If you plug in a monitor or use a serial console, you will likely encounter one of the following exact error strings. Here is how to decode them.

Exact Error String: Error: Connection activation failed: No suitable device found for this connection.

This is the most common nmcli error when attempting to bring up a WiFi profile. It does not necessarily mean your password is wrong. It means NetworkManager cannot map the software profile to the physical wlan0 hardware state.

The First Three Things to Check:

  1. Check RFKill State: The WiFi radio might be soft-blocked by the kernel. Run rfkill list. If you see Soft blocked: yes under the Wireless LAN section, unblock it with sudo rfkill unblock wifi.
  2. Check Device Management State: Run nmcli device status. If wlan0 shows as unmanaged, NetworkManager is ignoring it. This usually happens if a legacy /etc/network/interfaces file exists and is overriding NM. Delete or rename that file and reboot.
  3. Check 5GHz DFS Channel Eviction: If your router is set to auto-select 5GHz channels, it may have picked a DFS (Dynamic Frequency Selection) channel. The Pi's WiFi chip will detect radar signals (or fail to certify the channel) and silently drop the connection. Log into your router and hard-lock the 5GHz band to a non-DFS channel (e.g., 36, 40, 44, or 48).

Exact Error String: wlan0: Failed to connect to "SSID" - No suitable network found

This occurs in the system journal (journalctl -u NetworkManager) when the Pi can see other networks, but not yours.

Ranked Causes:

  • WPA3-Personal (SAE) Incompatibility: If your router forces WPA3-only, older Pi OS images or specific firmware versions of the CYW43455 chip will fail the handshake. Change your router to "WPA2/WPA3 Transitional" mode.
  • Hidden SSID: NetworkManager requires explicit configuration to scan for hidden networks. You must add wifi-sec.hidden yes to your nmcli connection add command.
  • Country Code Mismatch: The WiFi regulatory domain is unset, preventing the radio from transmitting on certain 5GHz frequencies. Set it via sudo raspi-config (Localisation Options → WLAN Country) or via nmcli radio settings.

Extending and Simplifying the Build

Not every project requires a full Pi 5 with dual LEDs. Here is how to adapt this architecture based on your deployment constraints.

How to Simplify (Cost & Power Reduction)

If you are building a fleet of IoT sensors and the $80+ cost of a Pi 5 is prohibitive, downgrade to the Raspberry Pi Zero 2 W.

  • Hardware changes: Drop the Ethernet PHY (the Zero 2 W lacks it). Use a single bi-color (red/green) common-cathode LED on GPIO 17 and GPIO 27 to save board space.
  • Software changes: The Zero 2 W has only 512MB of RAM. Remove the subprocess ping calls in the Python script and replace them with a lightweight raw socket ICMP implementation to reduce memory overhead and CPU context switching.

How to Extend (Advanced Diagnostics)

If this Pi is acting as a remote network probe or a digital signage controller, visual LEDs aren't enough.

  • Add an I2C OLED Display: Wire a 128x64 SSD1306 OLED to the I2C pins (GPIO 2/3) listed in the hardware table. Modify the Python script to print the exact RSSI (signal strength) and current IP address using the luma.oled library.
  • Implement Watchdog Fallback: Add a physical 5V relay module controlled by GPIO 22. If the Python script detects 5 consecutive ping failures, trigger the relay to physically cut and restore power to an external cellular modem or mesh router, automating the physical layer reset without human intervention.