The 2026 Reality: NetworkManager Replaced wpa_supplicant
If you are copying a wpa_supplicant.conf file into the /boot/firmware directory of a fresh Raspberry Pi OS image, your Raspberry Pi WiFi config will fail silently. As of the Bookworm release and continuing into current 2026 builds (including Trixie edge), Raspberry Pi OS has completely deprecated wpa_supplicant and dhcpcd in favor of NetworkManager and systemd-networkd.
This shift breaks years of legacy headless provisioning tutorials. NetworkManager handles WiFi security, roaming, and IP assignment differently, requiring interaction via the nmcli (command line) or nmtui (text UI) tools. For embedded deployments, automated Python scripts wrapping nmcli are now the gold standard for fleet provisioning.
wlan0 interface randomly during high-throughput MQTT or camera streaming tasks.
Hardware Spec Sheet & UART Debug Pinout
When WiFi configuration fails headless, you cannot SSH in to fix it. You must fall back to the UART serial console. Below is the exact hardware list and pin mapping required to debug a Pi that refuses to join a network.
| Component | Exact Variant / Model | Role in Build |
|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB) or Pi 4 Model B (4GB) | Target board (Code targets Bookworm/Trixie OS) |
| WiFi SoC | Infineon CYW43455 (Pi 5) / Cypress CYW43455 (Pi 4) | Internal 802.11ac dual-band radio |
| Power Supply | Official Raspberry Pi 27W USB-C PD | Prevents PCIe/SDIO bus brownouts |
| Debug Adapter | FTDI FT232RL USB-to-Serial (3.3V logic) | Headless UART console access |
UART0 Pin Mapping for Serial Debug
Before applying power, wire the FTDI adapter to the Pi's GPIO header. Ensure your FTDI adapter is physically jumpered to 3.3V. Sending 5V logic into GPIO 15 will permanently destroy the Pi's UART controller.
| Pi GPIO Pin | Function | FTDI FT232RL Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 8 (GPIO 14) | TXD (Transmit) | RXD (Receive) | Yellow |
| Pin 10 (GPIO 15) | RXD (Receive) | TXD (Transmit) | Orange |
| Pin 6 | GND | GND | Black |
Note: You must add enable_uart=1 to config.txt on the boot partition to activate UART0 on Pi 4/5, as the primary UART is often mapped to the Bluetooth module by default.
Configuration Decision Tree
How you configure the WiFi depends entirely on your deployment environment. Use this decision path to select the correct method. For automated embedded projects, we terminate on nmcli via Python.
| Scenario | Method | Pros / Cons | Verdict |
|---|---|---|---|
| Single Pi, Monitor & Keyboard attached | nmtui or Desktop GUI | Easy visual setup / Not scriptable | Use for prototyping only |
| Headless First-Boot (No network) | custom.toml in boot partition | Native to Pi Imager / Complex syntax | Use for initial factory flash |
| Fleet Provisioning / Runtime Updates | nmcli via Python Script | Robust, scriptable, handles errors / Requires base OS boot | DEFAULT PICK: Use for all embedded deployments |
Automated Python NetworkManager Provisioning
The following Python 3 script uses subprocess to interact with nmcli. It creates a persistent WiFi connection, forces the interface to connect, and verifies IP assignment. This code explicitly targets Raspberry Pi OS Bookworm/Trixie on Pi 4B and Pi 5, utilizing the wlan0 interface.
import subprocess
import sys
import time
# --- Configuration Variables ---
SSID = 'FactoryFloor_IoT'
PSK = 'SuperSecretWPA3Pass!'
CONN_NAME = 'iot-wifi-primary'
INTERFACE = 'wlan0'
TIMEOUT_SEC = 15
def run_nmcli(command: list) -> subprocess.CompletedProcess:
"""Execute nmcli command and capture output."""
full_cmd = ['nmcli'] + command
return subprocess.run(full_cmd, capture_output=True, text=True, check=False)
def configure_wifi():
print(f'[*] Checking interface {INTERFACE} status...')
# 1. Ensure WiFi radio is not hard-blocked
rfkill_check = run_nmcli(['radio', 'wifi'])
if 'disabled' in rfkill_check.stdout.lower():
print('[!] WiFi radio is disabled. Enabling...')
run_nmcli(['radio', 'wifi', 'on'])
# 2. Delete existing connection if it exists to prevent duplicate UUID errors
run_nmcli(['connection', 'delete', CONN_NAME])
# 3. Add new connection with WPA-PSK security
print(f'[*] Provisioning connection: {CONN_NAME}')
add_cmd = [
'connection', 'add', 'type', 'wifi', 'con-name', CONN_NAME,
'ssid', SSID, 'ifname', INTERFACE,
'wifi-sec.key-mgmt', 'wpa-psk', 'wifi-sec.psk', PSK,
'connection.autoconnect', 'yes', 'ipv4.method', 'auto'
]
result = run_nmcli(add_cmd)
if result.returncode != 0:
print(f'[ERROR] Failed to add connection: {result.stderr.strip()}')
sys.exit(1)
# 4. Activate connection and wait for IP
print('[*] Activating connection...')
up_cmd = ['connection', 'up', CONN_NAME, 'ifname', INTERFACE]
result = run_nmcli(up_cmd)
if result.returncode != 0:
print(f'[ERROR] Activation failed: {result.stderr.strip()}')
sys.exit(1)
# 5. Verify IP Assignment
start_time = time.time()
while time.time() - start_time < TIMEOUT_SEC:
ip_check = run_nmcli(['-g', 'IP4.ADDRESS', 'device', 'show', INTERFACE])
if ip_check.stdout.strip() and '192.' in ip_check.stdout or '10.' in ip_check.stdout:
print(f'[SUCCESS] Connected! IP: {ip_check.stdout.strip()}')
return True
time.sleep(2)
print('[ERROR] Timeout waiting for DHCP IP assignment.')
sys.exit(1)
if __name__ == '__main__':
try:
configure_wifi()
except KeyboardInterrupt:
print('\n[!] Provisioning aborted by user.')
sys.exit(130)
except Exception as e:
print(f'[FATAL] Unexpected error: {e}')
sys.exit(1)
Debugging: Exact Error Strings and Ranked Fixes
When the script above fails, nmcli returns specific error strings. Here are the exact errors, what they mean, and the ranked fixes. If your build fails, check these first three things in order:
- Power Supply Brownout: Check
dmesg | grep -i voltage. If you see under-voltage warnings, the WiFi chip is resetting. Upgrade to the 27W PD supply. - 5GHz DFS Channel Blocking: If connecting to a 5GHz network, the Pi must pass Dynamic Frequency Selection (DFS) radar checks. This delays connection by up to 60 seconds. Force your router to use a non-DFS channel (e.g., 36, 40, 44, 48) or use 2.4GHz for initial provisioning.
- Legacy Config Interference: Ensure no
wpa_supplicant.confexists in/boot/firmware/. NetworkManager will ignore it, but legacy startup scripts might try to parse it and hang the boot sequence.
Exact Error String Dictionary
nmcli device wifi list before debugging. If the list is empty, your issue is physical (firmware missing, rfkill blocked, or antenna disconnected), not a password issue.
| Exact Error String | Root Cause | Fix / Command |
|---|---|---|
Error: Connection activation failed: (7) Secrets were required, but not provided. |
Incorrect PSK, or missing wifi-sec.key-mgmt flag in the add command. |
Verify password. Ensure wifi-sec.key-mgmt wpa-psk is explicitly passed in the nmcli connection add array. |
Error: No suitable device found for this connection (reason: device-not-found). |
wlan0 is hard-blocked by rfkill, or the brcmfmac firmware failed to load. |
Run sudo rfkill unblock wifi. If that fails, check dmesg for missing /lib/firmware/brcm/brcmfmac43455-sdio.bin. |
Error: Connection activation failed: (5) IP configuration could not be reserved. |
DHCP server unreachable, or static IP conflict on the network. | Check router DHCP pool. Assign a static IP via nmcli: nmcli con mod iot-wifi-primary ipv4.addresses 192.168.1.50/24 ipv4.gateway 192.168.1.1 ipv4.method manual. |
Extending the Build: Static IPs and Fleet Scaling
Once your baseline Raspberry Pi WiFi config is stable via the Python script, you will likely need to harden it for production environments. Here is how to extend and simplify the build for fleet scaling.
1. Disable MAC Address Randomization
NetworkManager randomizes the WiFi MAC address on every scan and connection by default to prevent tracking. In an embedded IoT fleet where your router uses MAC filtering or static DHCP leases, this will break your network. Disable it globally:
sudo nano /etc/NetworkManager/conf.d/100-disable-wifi-mac-randomization.conf
Add the following block:
[device]
wifi.scan-rand-mac-address=no
[connection]
wifi.cloned-mac-address=preserve
Restart NetworkManager with sudo systemctl restart NetworkManager.
2. Fallback to Ethernet on WiFi Failure
For critical infrastructure (like greenhouse controllers or CNC monitors), WiFi is inherently unreliable due to RF interference. You can configure NetworkManager to assign a higher route metric to WiFi, ensuring that if an Ethernet cable is plugged in, traffic automatically routes over copper without dropping the active SSH session.
nmcli connection modify iot-wifi-primary ipv4.route-metric 600
nmcli connection modify 'Wired connection 1' ipv4.route-metric 100
By standardizing on nmcli and understanding the underlying shift away from wpa_supplicant, you eliminate the most common point of failure in modern Raspberry Pi embedded deployments. For deeper architectural references on connection profiles, consult the official NetworkManager nmcli documentation and the Raspberry Pi OS configuration guides.






