The 2026 Standard: NetworkManager vs. Legacy wpa_supplicant
If you are setting up WiFi on Raspberry Pi 5 (or Pi 4) running Raspberry Pi OS Bookworm or newer, the most critical thing to know is that wpa_supplicant.conf is dead. The OS now uses NetworkManager as the default network stack. Attempting to drop a legacy wpa_supplicant file into the boot partition will silently fail, leaving your headless Pi stranded without an IP address.
NetworkManager offers vastly superior roaming, WPA3-SAE support, and enterprise TLS handling, but it requires a different command-line approach via nmcli. Before we wire up the debug pins, let us look at the hardware realities of the Pi WiFi chipsets across recent generations.
| Board Variant | WiFi SoC | Bus Interface | Bands & Max PHY Rate | NetworkManager Support |
|---|---|---|---|---|
| Pi 3B+ | Cypress CYW43455 | SDIO | 2.4GHz / 5GHz (433 Mbps) | Legacy (wpa_supplicant default) |
| Pi Zero 2 W | Infineon CYW43439 | SDIO | 2.4GHz only (72 Mbps) | Supported (Bookworm update) |
| Pi 4 Model B | Cypress CYW43455 | SDIO | 2.4GHz / 5GHz (433 Mbps) | Native (Bookworm+) |
| Pi 5 (4GB/8GB) | Infineon CYW43455 | SDIO (Dedicated) | 2.4GHz / 5GHz (433 Mbps) | Native (Default stack) |
Note: While the Pi 5 features a PCIe 2.0 x1 connector for NVMe and HATs, the onboard WiFi chip remains on a dedicated SDIO bus. Do not attempt to route PCIe lanes to the internal wireless module.
Parts List & GPIO Pin Mapping for Debugging
When deploying headless, a physical status indicator and a serial debug fallback are mandatory. This build targets the Raspberry Pi 5 8GB running Raspberry Pi OS (Bookworm) 64-bit.
Bill of Materials
- Board: Raspberry Pi 5 8GB (with active cooler)
- Power: Official 27W USB-C PD Power Supply (Critical: 3rd-party 5V/3A phone chargers will cause PCIe/SDIO brownouts, killing WiFi)
- Indicator: 5mm Green LED + 330Ω 1/4W Resistor
- Debug: USB-to-TTL Serial Cable (CP2102 or PL2303 chipset)
Pin Mapping Table
| Function | Pi 5 GPIO / Pin | Wiring Destination | Notes |
|---|---|---|---|
| WiFi Status LED | GPIO 17 (Pin 11) | 330Ω Resistor → LED Anode | Active-high output |
| LED Ground | GND (Pin 9) | LED Cathode | Common ground |
| UART TX (Debug) | GPIO 14 (Pin 8) | USB-TTL RX (White/Green) | Enable via raspi-config |
| UART RX (Debug) | GPIO 15 (Pin 10) | USB-TTL TX (Green/White) | Do not cross TX to TX |
| Serial Ground | GND (Pin 6) | USB-TTL GND (Black) | Required for logic reference |
Step-by-Step: Headless WiFi Setup via nmcli
If you are configuring the Pi via a monitor and keyboard, or through the serial debug cable, use the following nmcli sequence to connect to a WPA2/WPA3 network.
- Scan for available networks:
nmcli device wifi list
Look for your SSID. Note the BSSID and channel. If your 5GHz network does not appear, see the DFS debugging section below. - Connect to the network:
nmcli device wifi connect "Your_SSID_Name" password "Your_Password" name "HomeWiFi"
Thenameparameter sets the NetworkManager connection profile name, which can differ from the SSID. - Force WPA3-SAE (if supported by router):
nmcli connection modify "HomeWiFi" wifi-sec.key-mgmt sae
nmcli connection up "HomeWiFi" - Verify the active connection:
nmcli connection show --active
Ensure the TYPE is802-11-wirelessand STATE isactivated.
Python WiFi Monitor & Auto-Recovery Script
NetworkManager handles reconnections automatically, but SDIO bus glitches or router-side lease expirations can leave the interface in a hung state. This Python script monitors the connection via nmcli and drives the GPIO 17 LED. If the connection drops, it blinks the LED and forces a NetworkManager restart.
import subprocess
import time
import sys
from gpiozero import LED
# PIN DEFINITIONS
WIFI_STATUS_LED_PIN = 17
wifi_led = LED(WIFI_STATUS_LED_PIN)
PROFILE_NAME = "HomeWiFi" # Must match the 'name' used in nmcli setup
def get_wifi_status():
"""Queries NetworkManager for active WiFi state."""
try:
result = subprocess.run(
['nmcli', '-t', '-f', 'TYPE,STATE', 'connection', 'show', '--active'],
capture_output=True, text=True, check=True, timeout=5
)
for line in result.stdout.strip().split('\n'):
if '802-11-wireless' in line and 'activated' in line:
return True
return False
except subprocess.CalledProcessError as e:
print(f"nmcli execution error: {e.stderr}", file=sys.stderr)
return False
except FileNotFoundError:
print("Fatal: nmcli not found. Is NetworkManager installed?", file=sys.stderr)
sys.exit(1)
def restart_network_manager():
"""Attempts to recover a hung SDIO/WiFi state."""
print("[WARN] WiFi dropped. Attempting NetworkManager restart...")
try:
subprocess.run(['sudo', 'systemctl', 'restart', 'NetworkManager'], check=True, timeout=15)
time.sleep(5) # Wait for interface to re-initialize
subprocess.run(['nmcli', 'connection', 'up', PROFILE_NAME], check=True, timeout=10)
except subprocess.CalledProcessError:
print("[ERROR] Recovery failed. Hardware reset may be required.", file=sys.stderr)
if __name__ == "__main__":
print(f"Starting WiFi Monitor on GPIO {WIFI_STATUS_LED_PIN}...")
try:
while True:
if get_wifi_status():
if not wifi_led.is_lit:
wifi_led.on()
else:
wifi_led.toggle() # Blink when disconnected
# Trigger recovery if off for more than a few cycles
# (Simplified here to trigger immediately on detected drop)
restart_network_manager()
time.sleep(2)
except KeyboardInterrupt:
wifi_led.off()
print("\nMonitor stopped. LED off.")
Debugging: Exact Error Strings & Ranked Causes
When nmcli fails, it returns specific D-Bus error codes from NetworkManager. Here is how to decode the exact strings you will see in the terminal.
Error: Connection activation failed: (7) Secrets were required, but not provided.
- Cause 1 (Most Likely): Incorrect PSK (password). NetworkManager hides the exact mismatch to prevent brute-force enumeration.
- Cause 2: WPA3-SAE downgrade attack detected. If your router enforces WPA3 but the Pi is attempting WPA2, the handshake aborts.
- Fix: Delete the profile (
nmcli connection delete "HomeWiFi") and re-enter credentials. Ensurewifi-sec.key-mgmtmatches the router.
Error: Connection activation failed: (4) Active connection removed before it was initialized.
- Cause 1 (Most Likely): 5GHz DFS Channel Radar Delay. If your router is on a DFS channel (52-144), the Pi WiFi chip must listen for radar signals for 60 seconds before transmitting. NetworkManager times out waiting for the association.
- Cause 2: Power brownout. The Pi 5 WiFi chip spikes in current during TX initialization. An inadequate power supply causes the SDIO bus to reset mid-handshake.
- Fix: Change your router to a non-DFS 5GHz channel (36, 40, 44, 48) or use 2.4GHz. Verify you are using the official 27W Pi 5 power supply.
Error: No network with SSID 'MyNetwork' found.
- Cause 1: Hidden SSID. NetworkManager does not probe for hidden networks by default to save airtime.
- Cause 2: Unescaped spaces in the SSID string during CLI entry.
- Fix: For hidden networks, run:
nmcli connection modify "HomeWiFi" wifi.hidden yes. For spaces, always wrap the SSID in strict double quotes.
The First Three Things to Check When WiFi Fails
If your Pi 5 is completely blind to networks, run through this physical and logical checklist before reflashing the OS.
- Check the Power Supply Negotiation: Run
vcgencmd pmic_read_adapters(or checkdmesg | grep -i undervolt). The Pi 5 requires a 5V/5A USB-C PD negotiation to unlock full current limits. If it falls back to 5V/3A, the OS will throttle the CPU and restrict peripheral power, frequently causing the CYW43455 chip to drop off the SDIO bus. - Verify Regulatory Domain (Country Code): The WiFi chip will refuse to transmit on 5GHz if the country code is unset, as it cannot know which DFS rules apply. Set it via
sudo raspi-config(Localisation Options → WLAN Country) or vianmcli general set wifi-reg-domain US(replace US with your ISO code). - Inspect SDIO Bus Errors in dmesg: Run
dmesg | grep -i mmc1. The WiFi chip is typically onmmc1. If you seemmc1: timeout waiting for hardware interrupt, you have a physical hardware fault, a thermal throttle issue (ensure the active cooler is mounted), or a corrupted firmware blob in/lib/firmware/cypress/.
Extending the Build: Fallback Access Point Mode
For remote deployments (e.g., garden sensors, off-grid cameras), you need a way to connect to the Pi if your main router goes down or you change your home WiFi password. You can configure NetworkManager to create a fallback Access Point (AP) that automatically activates when the primary connection fails.
Create the AP profile with a shared IPv4 method, which turns the Pi into a NAT router for any device that connects to it:
nmcli connection add type wifi ifname wlan0 con-name FallbackAP ssid "Pi5-Recovery" \
mode ap ipv4.method shared ipv4.addresses 192.168.44.1/24 \
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "recover123" \
connection.autoconnect yes connection.autoconnect-priority -10
How it works: The negative priority (-10) ensures NetworkManager always prefers your main "HomeWiFi" profile (which defaults to priority 0). If "HomeWiFi" fails to activate after its timeout period, NetworkManager will automatically spin up "FallbackAP". You can then connect your phone to "Pi5-Recovery", SSH into 192.168.44.1, and fix your primary credentials without needing a physical monitor or serial cable.
For deeper configuration options, consult the official NetworkManager nmcli documentation and the Raspberry Pi 5 hardware specifications.






