The 2026 Reality: NetworkManager vs. Legacy wpa_supplicant

If you are following a Raspberry Pi WiFi config tutorial from 2022 or earlier, it is likely broken. The Raspberry Pi Foundation completely deprecated wpa_supplicant and dhcpcd in Raspberry Pi OS 'Bookworm' and carried that change forward into 'Trixie'. Editing /etc/wpa_supplicant/wpa_supplicant.conf will silently fail on modern images because the daemon is no longer managing the wlan0 interface.

The modern standard is NetworkManager, controlled via the nmcli (command line) or nmtui (terminal UI) utilities. NetworkManager handles WPA3, enterprise 802.1X, and seamless roaming between access points natively, which the old stack struggled with.

ScenarioOS VersionToolVerdict
Desktop GUI setupBookworm / Trixienm-appletUse the top-right system tray icon.
Headless IoT / ServerBookworm / TrixienmcliDefault Pick: Use nmcli for 95% of headless builds.
Legacy Pi Zero W (Buster)Buster / Bullseyewpa_cliUse wpa_supplicant.conf (only for outdated OS).
Complex Visual TerminalBookworm / TrixienmtuiUse when typing long WPA2-Enterprise passwords via SSH.

Hardware & Parts List: Target Board and Debug GPIOs

This guide targets the Raspberry Pi 5 (8GB) equipped with the dual-band 802.11ac WiFi module. While the Pi 4 Model B and Pi Zero 2 W share the same NetworkManager software stack, the Pi 5's PCIe architecture and power requirements demand specific hardware considerations for stable RF performance.

Required Components

ComponentExact Variant / SpecNotes
MicrocontrollerRaspberry Pi 5 (8GB RAM)Requires active cooling for sustained WiFi throughput.
Power SupplyOfficial 27W USB-C PD PSUUnder-voltage causes WiFi chip brownouts and disconnects.
Status LED5mm Green Diffused LEDWired to GPIO 17 for connection status.
Current Limiter330Ω 1/4W ResistorProtects GPIO 17 from overcurrent.
Fallback Button6x6mm Tactile SwitchWired to GPIO 27 for hardware WiFi reset.

GPIO Pin Mapping for Network Debugging

When running headless, you cannot see the desktop network applet. Mapping physical GPIO pins to network states allows you to debug connection drops from across the room.

Physical PinBCM GPIOFunctionWiring Target
11GPIO 17WiFi Status LEDAnode (via 330Ω resistor)
13GPIO 27Hardware Reset TriggerTactile Switch (to GND)
9 / 14GNDCommon GroundLED Cathode & Switch

Step-by-Step Headless Raspberry Pi WiFi Config

Assuming you are SSH'd into your Pi 5 or using a serial console, follow these exact steps to configure a persistent WiFi connection using nmcli.

Bench Tip: Always scan for networks first to verify the exact SSID string and security type. Hidden SSIDs require an extra flag in NetworkManager.
  1. Scan for available networks:
    nmcli device wifi rescan
    nmcli device wifi list
  2. Connect to the network and save the profile:
    nmcli device wifi connect 'Your_SSID_Name' password 'YourPassword123' name 'HomeWiFi'
    Note: The name parameter creates a reusable profile name, distinct from the SSID.
  3. Verify the connection is set to auto-start on boot:
    nmcli connection modify 'HomeWiFi' connection.autoconnect yes
  4. Set connection priority (crucial for multi-AP environments):
    nmcli connection modify 'HomeWiFi' connection.autoconnect-priority 10
    Higher numbers connect first. Set your 5GHz network to 10 and 2.4GHz fallback to 0.
  5. Reload and activate:
    nmcli connection reload
    nmcli connection up 'HomeWiFi'

Python Fallback Script: Auto-Reconnect and GPIO Status

NetworkManager handles standard reconnections, but in high-interference environments (like a workshop with VFD motors), the WiFi chip can lock up and require a soft reset. The following Python script monitors the wlan0 state via nmcli, drives the GPIO 17 status LED, and uses the GPIO 27 tactile button to force a NetworkManager interface restart if the network hangs.

Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS Bookworm.
Dependencies: sudo apt install python3-gpiozero (pre-installed on desktop, may need manual install on Lite).

import subprocess
import time
import sys
from gpiozero import LED, Button
from signal import pause

# --- Pin Definitions ---
STATUS_LED = LED(17)       # Physical Pin 11
RESET_BTN = Button(27, pull_up=True, bounce_time=0.2)  # Physical Pin 13

PROFILE_NAME = 'HomeWiFi'  # Must match the 'name' set in nmcli

def get_wifi_state():
    """Queries nmcli for the exact state of the wlan0 interface."""
    try:
        result = subprocess.run(
            ['nmcli', '-t', '-f', 'GENERAL.STATE', 'device', 'show', 'wlan0'],
            capture_output=True, text=True, check=True
        )
        # Output looks like: GENERAL.STATE:100 (connected)
        state_str = result.stdout.strip()
        if '100' in state_str or 'connected' in state_str:
            return 'connected'
        return 'disconnected'
    except subprocess.CalledProcessError:
        return 'error'
    except Exception as e:
        print(f'Unexpected nmcli error: {e}')
        return 'error'

def force_wifi_reset():
    """Hard resets the WiFi interface via NetworkManager."""
    print('[ACTION] Hardware reset triggered. Bouncing wlan0...')
    STATUS_LED.blink(0.2, 0.2) # Fast blink during reset
    try:
        subprocess.run(['nmcli', 'device', 'disconnect', 'wlan0'], check=True)
        time.sleep(2)
        subprocess.run(['nmcli', 'connection', 'up', PROFILE_NAME], check=True)
        print('[SUCCESS] Interface reset complete.')
    except subprocess.CalledProcessError as e:
        print(f'[FAIL] Reset failed: {e}')

# Bind the hardware button to the reset function
RESET_BTN.when_pressed = force_wifi_reset

print(f'Monitoring WiFi profile: {PROFILE_NAME}')
print('Press the button on GPIO 27 to force a network reset.')

try:
    while True:
        state = get_wifi_state()
        if state == 'connected':
            STATUS_LED.on()
        elif state == 'disconnected':
            STATUS_LED.blink(1, 1) # Slow blink = disconnected
            # Optional: Add automated software retry logic here
        else:
            STATUS_LED.off() # Off = interface missing or error
        
        time.sleep(5)

except KeyboardInterrupt:
    print('\nExiting monitor.')
    STATUS_LED.off()
    sys.exit(0)

Troubleshooting: Exact Error Strings and Ranked Fixes

When a Raspberry Pi WiFi config fails, the first three things to check are: (1) Are you using the correct nmcli syntax for Bookworm? (2) Is the 27W PSU delivering stable voltage (check vcgencmd get_throttled)? (3) Is the router enforcing MAC filtering or WPA3-Enterprise incorrectly?

If those pass, match your terminal output to these exact error strings.

Error 1: Error: Connection activation failed: (7) Secrets were required, but not provided.
  • Cause A (Most Likely): Incorrect password or special characters in the password were interpreted by the bash shell (e.g., $, &, !).
  • Fix: Wrap the password in single quotes, not double quotes: nmcli device wifi connect 'SSID' password 'P@ssword!$'.
  • Cause B: The router is using WPA3-SAE and the Pi's wpa_supplicant backend (used internally by NM) is failing the handshake.
  • Fix: Force WPA2 transition mode on your router, or update the Pi: sudo apt update && sudo apt upgrade.
Error 2: OSError: [Errno 101] Network is unreachable (Seen in Python scripts)
  • Cause A (Most Likely): The script executed before NetworkManager finished DHCP negotiation on boot.
  • Fix: Do not use time.sleep() hacks. Add a network-online dependency to your systemd service file: After=NetworkManager-wait-online.service.
  • Cause B: The wlan0 interface dropped its IP lease but remained 'connected' at the MAC layer.
  • Fix: Run nmcli connection reload and nmcli connection up 'HomeWiFi'.
Error 3: wlan0: CTRL-EVENT-DISCONNECTED bssid=... reason=3 locally_generated=1 (Seen in journalctl -u NetworkManager)
  • Cause A (Most Likely): Power delivery brownout causing the Cypress/Infineon WiFi chip to reset internally.
  • Fix: Verify you are using the official 27W PD supply. Third-party phone chargers often drop voltage under the Pi 5's transient RF transmit loads.
  • Cause B: USB 3.0 interference drowning out 2.4GHz WiFi.
  • Fix: Switch the Pi to a 5GHz WiFi network, or use a shielded USB 3.0 hub for external peripherals.

Extending the Build: Static IPs and Enterprise WPA3

Once your baseline Raspberry Pi WiFi config is stable, you will likely need to lock down the IP address for port forwarding or MQTT broker hosting.

How to Extend: Assigning a Static IP via nmcli

Forget editing /etc/dhcpcd.conf—that file is ignored in modern Pi OS. Assign a static IP directly to the NetworkManager profile:

# Set static IP and Subnet Mask
nmcli connection modify 'HomeWiFi' ipv4.addresses 192.168.1.50/24

# Set Gateway
nmcli connection modify 'HomeWiFi' ipv4.gateway 192.168.1.1

# Set DNS (using Cloudflare and Google)
nmcli connection modify 'HomeWiFi' ipv4.dns '1.1.1.1 8.8.8.8'

# Force manual IP assignment (disables DHCP for this profile)
nmcli connection modify 'HomeWiFi' ipv4.method manual

# Apply changes
nmcli connection up 'HomeWiFi'

How to Simplify: Using nmtui for Complex Auth

If you are connecting to a university or corporate network requiring WPA2-Enterprise (PEAP/MSCHAPv2) with CA certificates, typing the nmcli flags is a nightmare. Simplify the build by launching the visual terminal interface:

sudo nmtui

Use the arrow keys to select 'Edit a connection', navigate the nested menus to input your identity, password, and certificate paths, and save. NetworkManager will handle the underlying 802.1X handshake automatically.

For deeper documentation on NetworkManager's capabilities on ARM boards, refer to the official Raspberry Pi networking documentation and the upstream NetworkManager nmcli reference. If you are expanding the Python monitoring script to control external relays based on network state, consult the GPIO Zero API documentation for advanced threading patterns.