If you are setting up a Raspberry Pi as a WiFi access point (AP) on Raspberry Pi OS Bookworm or newer, forget everything you know about the legacy hostapd and dnsmasq stack. Those tools are deprecated in modern Pi OS. The direct, modern answer is to use nmcli (NetworkManager command-line interface) to create a shared WiFi connection. This method handles DHCP, routing, and interface management natively without requiring fragile config file edits.

This guide targets the Raspberry Pi 4 Model B (4GB) and Raspberry Pi 5 (4GB) running Raspberry Pi OS Bookworm (64-bit). We will configure the AP via terminal commands, wire a physical GPIO toggle switch with a status LED, and cover the exact debugging steps when NetworkManager refuses to bring the interface up.

Onboard Wi-Fi Capabilities: Pi 4 vs Pi 5 vs Zero 2 W

Before configuring the software, you need to know what your hardware can actually push over the air. The onboard Wi-Fi modules differ significantly across board variants, which directly impacts your AP's real-world throughput and client capacity.

Board Variant Wi-Fi Chipset Bands Supported Theoretical Max Real-World AP Throughput Max Stable Clients
Raspberry Pi 4 Model B Cypress CYW43455 2.4GHz & 5GHz 433 Mbps (5GHz) ~45-60 Mbps 8-10
Raspberry Pi 5 Cypress CYW43455 (Same as Pi 4) 2.4GHz & 5GHz 433 Mbps (5GHz) ~50-65 Mbps 8-10
Raspberry Pi Zero 2 W Cypress CYW43436 2.4GHz Only 72 Mbps (2.4GHz) ~15-25 Mbps 4-5
Pi 4/5 + USB Wi-Fi Dongle (RTL8812AU) Realtek RTL8812AU 2.4GHz & 5GHz (AC1200) 867 Mbps (5GHz) ~120-150 Mbps 15-20
Bench Note: The Pi 5 uses the exact same Wi-Fi silicon as the Pi 4. The slight throughput bump on the Pi 5 comes from the faster PCIe bus and CPU handling the network stack interrupts, not a better antenna. If you need to serve more than 10 clients or push high-bandwidth video, bypass the onboard chip and use an external USB Wi-Fi adapter with an RTL8812AU chipset.

Parts List and GPIO Pin Mapping

To make this a true embedded project rather than just a software config, we are adding a physical hardware toggle to enable/disable the AP, plus a status LED. This is highly useful for headless Pi deployments in the field where you want to kill the RF emissions instantly without SSHing in.

Required Components

  • Board: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (4GB)
  • Power: Official 15W USB-C PD Supply (Pi 4) or 27W USB-C PD Supply (Pi 5) — do not use phone chargers; brownouts will drop the Wi-Fi radio first.
  • Storage: 32GB Samsung EVO Plus microSD (A2 rated)
  • Indicator: 5mm Green LED + 330Ω through-hole resistor
  • Switch: SPST momentary pushbutton (normally open)
  • Wiring: 3x female-to-female jumper wires

GPIO Pin Mapping Table

We are using gpiozero in Python, which relies on Broadcom (BCM) pin numbering. Wire your components exactly as follows:

Component BCM GPIO Pin Physical Pin Wiring Notes
Status LED Anode (+) GPIO 17 Pin 11 Wire in series with 330Ω resistor
Status LED Cathode (-) GND Pin 9 Direct to ground
Pushbutton Side A GPIO 27 Pin 13 Internal pull-up enabled in software
Pushbutton Side B GND Pin 14 Direct to ground

Step-by-Step NetworkManager AP Configuration

Boot your Pi, open a terminal, and ensure your system is updated (sudo apt update && sudo apt full-upgrade). We will use nmcli to create a routed wireless access point. This assigns the Pi a static IP and runs a built-in DHCP server for connected clients.

  1. Identify your wireless interface name.
    Run nmcli device status. You will likely see wlan0. If you are using a USB dongle, it might be wlx.... Note this name; we will use wlan0 for this guide.
  2. Create the base WiFi connection profile.
    sudo nmcli connection add type wifi ifname wlan0 con-name PiAP autoconnect yes ssid 'FluxNet-AP'
  3. Configure the AP mode, IP routing, and security.
    sudo nmcli connection modify PiAP 802-11-wireless.mode ap 802-11-wireless.band bg ipv4.method shared ipv4.addresses 10.42.0.1/24 wifi-sec.key-mgmt wpa-psk wifi-sec.psk 'SuperSecret123'
    Note: band bg forces 2.4GHz. Change to a for 5GHz if your clients support it and you want less interference.
  4. Bring the connection up.
    sudo nmcli connection up PiAP
  5. Verify the AP is broadcasting.
    Check your smartphone's WiFi list. You should see FluxNet-AP. Connect to it; your phone should receive an IP address in the 10.42.0.x range from the Pi's internal DHCP server.

Python Hardware Toggle Script

Now we tie the hardware to the software. This Python script uses the gpiozero library to monitor the pushbutton. When pressed, it checks the current state of the PiAP connection and toggles it up or down, updating the LED accordingly. It includes explicit error handling for subprocess failures.

#!/usr/bin/env python3
import subprocess
from gpiozero import Button, LED
from signal import pause
import sys

# Pin definitions matching our wiring table
AP_NAME = 'PiAP'
STATUS_LED = LED(17)
TOGGLE_BTN = Button(27, pull_up=True, bounce_time=0.05)

def get_ap_state():
    '''Returns True if the AP connection is currently activated.'''
    try:
        result = subprocess.run(
            ['nmcli', '-t', '-f', 'GENERAL.STATE', 'connection', 'show', AP_NAME],
            capture_output=True, text=True, check=True
        )
        return 'activated' in result.stdout
    except subprocess.CalledProcessError:
        return False

def toggle_ap():
    '''Toggles the AP state and updates the hardware LED.'''
    current_state = get_ap_state()
    action = 'down' if current_state else 'up'
    
    try:
        print(f'Toggling AP {action}...')
        subprocess.run(['nmcli', 'connection', action, AP_NAME], check=True)
        
        if action == 'up':
            STATUS_LED.on()
            print('AP is now broadcasting.')
        else:
            STATUS_LED.off()
            print('AP is now offline.')
            
    except subprocess.CalledProcessError as e:
        print(f'Error: nmcli command failed with exit code {e.returncode}')
        # Blink LED rapidly to indicate hardware/software fault
        STATUS_LED.blink(on_time=0.1, off_time=0.1, n=5, background=False)
    except Exception as e:
        print(f'Unexpected error: {e}')

if __name__ == '__main__':
    # Initialize LED to match current software state on boot
    if get_ap_state():
        STATUS_LED.on()
    else:
        STATUS_LED.off()
        
    print('Hardware AP Toggle ready. Press button to switch states.')
    TOGGLE_BTN.when_pressed = toggle_ap
    
    try:
        pause()
    except KeyboardInterrupt:
        print('\nExiting script.')
        sys.exit(0)

Save this as ap_toggle.py, make it executable (chmod +x ap_toggle.py), and add it to your crontab or a systemd service to run on boot.

Debugging: 'No Suitable Device Found' and Other Failures

The most common point of failure when migrating from legacy guides to NetworkManager is interface naming and RF kill switches. If you run sudo nmcli connection up PiAP and receive this exact error:

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

The First Three Things to Check

  1. Is the interface blocked by rfkill? Run rfkill list. If Wi-Fi shows 'Soft blocked: yes', run sudo rfkill unblock wifi.
  2. Is the interface name actually wlan0? Run ip link show. If your USB dongle or specific Pi board revision named it wlx... or wlp1s0, you must delete the connection (sudo nmcli connection delete PiAP) and recreate it with the correct ifname.
  3. Is NetworkManager managing the device? Run nmcli device status. If wlan0 shows as unmanaged, check /etc/NetworkManager/NetworkManager.conf and ensure [keyfile] unmanaged-devices is not explicitly ignoring your MAC address.

Ranked Causes for Activation Failures

Rank Cause Fix / Command
1 Wrong interface name specified in the profile nmcli connection modify PiAP connection.interface-name wlx[newname]
2 Wi-Fi radio soft-blocked by OS power management sudo rfkill unblock wifi
3 Attempting 5GHz AP on a Pi Zero 2 W (2.4GHz only) Modify profile: nmcli connection modify PiAP 802-11-wireless.band bg
4 Conflicting DHCP server (dnsmasq still installed) sudo systemctl stop dnsmasq && sudo systemctl disable dnsmasq

Extending and Simplifying the Build

How to Extend the Build

If you need more control than NetworkManager's default shared IPv4 method provides, you can extend this build into a Captive Portal. Install nginx and configure iptables to redirect all port 80 traffic from the 10.42.0.x subnet to a local landing page. This is the standard architecture for IoT provisioning APs where a user connects to the Pi to input their home WiFi credentials.

For higher throughput, bridge the AP to the ethernet port. Change ipv4.method shared to ipv4.method bridge and add eth0 as a slave interface. This turns the Pi into a transparent wireless switch rather than a NAT router, though you will lose the built-in DHCP server and rely on your main network's router to hand out IPs.

How to Simplify the Build

If you don't need the GPIO hardware toggle and just want a quick AP for a weekend project, skip the terminal entirely. Boot into the Raspberry Pi OS Desktop environment, click the NetworkManager applet in the top right taskbar, select 'Edit Connections', add a new Wi-Fi connection, set the Mode to 'Hotspot', and enter your SSID and WPA2 password. The GUI handles all the underlying nmcli commands automatically.

For further reading on modern Pi networking, consult the official Raspberry Pi NetworkManager documentation and the upstream nmcli reference manual.