Setting up a raspberry pi as an access point used to mean wrestling with brittle hostapd, dnsmasq, and dhcpcd config files. If you are running Raspberry Pi OS Bookworm (the current standard for 2026), those legacy tutorials will break your network stack. The modern, robust method uses NetworkManager (nmcli) to handle the AP radio, DHCP, and routing natively.

This guide targets the Raspberry Pi 4 Model B (4GB) and Raspberry Pi 5 (4GB) running Bookworm. We will walk through the exact nmcli commands, add an I2C OLED status display for headless debugging, and troubleshoot the exact radio failures you will hit on the bench.

Hardware Spec Sheet and Pin Mapping

The internal Wi-Fi chip on both the Pi 4 and Pi 5 is the Cypress CYW43455. It supports 2.4GHz and 5GHz, but as we will cover in the debugging section, 5GHz AP mode is heavily restricted by regional DFS (Dynamic Frequency Selection) radar laws. For a reliable baseline, we are configuring a 2.4GHz AP with an external I2C OLED to display the IP address when headless.

Table 1: Required Hardware and Variants
ComponentExact Variant / SpecificationNotes
MicrocontrollerRaspberry Pi 4 Model B (4GB) or Pi 5 (4GB)Code targets Bookworm OS (64-bit)
Power SupplyOfficial 15W (Pi 4) or 27W PD (Pi 5) USB-CUndervoltage drops the Wi-Fi bus first
Status DisplaySSD1306 128x64 I2C OLED (3.3V logic)Requires 4.7kΩ pull-ups on SDA/SCL
Antenna (Optional)2.4GHz SMA pigtail + 5dBi antennaRequires opening the Pi RF shield (voids FCC)

I2C OLED Pin Mapping

The Raspberry Pi's I2C Bus 1 is enabled by default and includes onboard 1.8kΩ pull-up resistors, which is sufficient for a single short-run OLED module. If you are daisy-chaining sensors, add external 4.7kΩ pull-ups to 3.3V.

Table 2: SSD1306 I2C to Raspberry Pi GPIO Pinout
OLED PinPi GPIO (Physical Pin)Function
VCC3.3V (Pin 1)Power (Do not use 5V on 3.3V logic modules)
GNDGND (Pin 6)Common Ground
SCLGPIO 3 (Pin 5)I2C Clock (Bus 1)
SDAGPIO 2 (Pin 3)I2C Data (Bus 1)

Configuring the Wi-Fi Radio via NetworkManager

Safety & Compliance: Broadcasting a Wi-Fi access point is subject to local RF regulations. Never modify the Pi's internal RF shielding or solder external antenna pigtails unless you are testing in an RF-isolated environment and understand FCC/CE compliance implications.

Forget editing /etc/hostapd/hostapd.conf. In Bookworm, NetworkManager handles the Wi-Fi AP mode, IP addressing, and DHCP server (via its internal dnsmasq integration) in one command block.

  1. Update and identify the interface:
    sudo apt update && sudo apt install network-manager
    Run iwconfig or nmcli device to confirm your Wi-Fi interface is named wlan0.
  2. Create the AP connection profile:
    nmcli con add type wifi ifname wlan0 mode ap con-name PiAP ssid "FluxNet-AP"
  3. Set the band and channel (Crucial Step):
    nmcli con modify PiAP 802-11-wireless.band bg
    nmcli con modify PiAP 802-11-wireless.channel 6
    Note: We force 'bg' (2.4GHz). Attempting 'a' (5GHz) often fails due to DFS radar restrictions unless you manually set the regulatory domain and wait for radar clearance.
  4. Configure IP sharing (DHCP):
    nmcli con modify PiAP ipv4.method shared ipv4.addresses 192.168.4.1/24
    The shared method automatically spins up a DHCP server handing out IPs from 192.168.4.2 to 192.168.4.254, and sets up NAT masquerading if the Pi has upstream internet on eth0.
  5. Set WPA2 Security:
    nmcli con modify PiAP wifi-sec.key-mgmt wpa-psk wifi-sec.psk "SuperSecret123"
  6. Bring the interface up:
    nmcli con up PiAP

Headless Status Display (Python Code)

When running a Pi headless as a field AP, you need to know if wlan0 actually came up and what the IP is. This Python script uses the luma.oled library to read NetworkManager's state and display it on the SSD1306. It includes explicit pin/bus definitions and I2C error handling.

Install dependencies first: sudo apt install python3-pip i2c-tools then pip3 install luma.oled psutil.

#!/usr/bin/env python3
import sys
import time
import subprocess
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306

# --- PIN & BUS DEFINITIONS ---
# Raspberry Pi I2C Bus 1 maps to:
# SDA -> GPIO 2 (Physical Pin 3)
# SCL -> GPIO 3 (Physical Pin 5)
I2C_PORT = 1
OLED_ADDRESS = 0x3C

def get_ap_status():
    """Fetches wlan0 IP and connection state via nmcli."""
    try:
        ip_out = subprocess.check_output(['nmcli', '-g', 'IP4.ADDRESS', 'dev', 'show', 'wlan0']).decode('utf-8').strip()
        state_out = subprocess.check_output(['nmcli', '-g', 'GENERAL.STATE', 'dev', 'show', 'wlan0']).decode('utf-8').strip()
        return ip_out if ip_out else 'No IP', state_out
    except subprocess.CalledProcessError:
        return 'Error', 'wlan0 missing'

def main():
    try:
        # Initialize I2C serial interface and OLED device
        serial = i2c(port=I2C_PORT, address=OLED_ADDRESS)
        device = ssd1306(serial, rotate=0)
    except Exception as e:
        print(f'Fatal I2C Error: {e}. Check GPIO 2/3 wiring and i2c-tools.')
        sys.exit(1)

    print('OLED AP Status Monitor running. Press Ctrl+C to exit.')
    
    try:
        while True:
            ip, state = get_ap_status()
            with canvas(device) as draw:
                draw.text((0, 0), 'FluxNet AP Status', fill='white')
                draw.text((0, 20), f'IP: {ip}', fill='white')
                draw.text((0, 40), f'State: {state[:16]}', fill='white')
            time.sleep(5)
    except KeyboardInterrupt:
        device.cleanup()
        print('Monitor stopped.')

if __name__ == '__main__':
    main()

Debugging: Radio Failures and Error Strings

The most common point of failure when configuring a raspberry pi as an access point is the radio refusing to initialize. If your nmcli con up PiAP command fails, you will likely see this exact error string:

Error: Connection activation failed: No suitable device found for this connection (device wlan0 is unavailable)

This is a generic NetworkManager catch-all. Here are the ranked causes and how to fix them:

  1. RFKill Soft/Hard Block (Most Likely): The OS has disabled the radio to save power or due to a missing regulatory domain.
    Fix: Run rfkill list. If wlan0 shows 'Soft blocked: yes', run sudo rfkill unblock wifi. Then run sudo iw reg set US (or your country code) to satisfy the kernel's regulatory daemon.
  2. 5GHz DFS Radar Block: If you set the band to 'a' (5GHz) and the channel requires DFS, the kernel will silence the radio until it listens for radar pulses (usually 60 seconds). If it detects noise, it shuts down.
    Fix: Switch back to 2.4GHz (nmcli con modify PiAP 802-11-wireless.band bg) or explicitly set a non-DFS 5GHz channel like 36 or 40, provided your regional domain allows it.
  3. Power Supply Brownout: The CYW43455 Wi-Fi chip draws peak current during AP beacon transmission. If you are using a cheap USB-C phone charger, the voltage on the 5V rail will dip below 4.63V, triggering the Pi's brownout detector, which aggressively cuts power to the USB and Wi-Fi buses to save the CPU.
    Fix: Check dmesg | grep -i voltage for 'Under-voltage detected' warnings. Replace the power supply with the official 15W (Pi 4) or 27W (Pi 5) unit.
The First Three Things to Check: When the AP fails to start, immediately run:
1. rfkill list (Verify no blocks)
2. dmesg | grep brcmfmac (Verify firmware loaded without I/O errors)
3. iw reg get (Verify your country code is set, not '00' / Global)

Extending or Simplifying the Build

To Simplify: If you don't need the OLED status display, skip the Python script and I2C wiring entirely. You can verify the AP is broadcasting by running nmcli dev wifi list from a secondary device, or simply checking if your phone sees 'FluxNet-AP' in its Wi-Fi settings. To make the AP survive reboots, NetworkManager handles this automatically; the PiAP profile is saved in /etc/NetworkManager/system-connections/ and will auto-start on boot.

To Extend: If you want to turn this into a captive portal (like a hotel Wi-Fi login page), you will need to bypass NetworkManager's built-in DHCP and install dnsmasq manually to handle DNS hijacking, alongside nginx to serve your landing page. Alternatively, if you want to bridge the AP to the ethernet port so clients get IPs from your main home router instead of the Pi, change the IPv4 method: nmcli con modify PiAP ipv4.method bridge and add it to a bridge interface with eth0.

Frequently Asked Questions

Can the Raspberry Pi 5 act as a 5GHz access point?

Yes, the Pi 5 uses the same Cypress CYW43455 (or similar Infinoen variant depending on the exact board revision) as the Pi 4, which physically supports 5GHz. However, acting as a 5GHz AP is governed by your local regulatory domain. In the US (FCC) and EU (CE), many 5GHz channels require DFS (Dynamic Frequency Selection). The Pi must listen for radar signals before transmitting. If you force a DFS channel, the AP will silently fail to broadcast or drop clients when it detects noise. For reliable 5GHz AP mode, you must configure nmcli to use a non-DFS channel (like 36, 40, 44, or 48) and ensure your iw reg set matches your physical location.

Why is my Raspberry Pi access point dropping clients randomly?

Random client drops on a Pi AP are almost always caused by power supply ripple or thermal throttling. The Wi-Fi chip is highly sensitive to voltage drops on the 3.3V rail. If you are powering the Pi via a GPIO header or a low-quality USB-C cable, the voltage drop under load will cause the Wi-Fi firmware (brcmfmac) to crash and reload silently. Check dmesg for 'brcmfmac: brcmf_fw_alloc_request: using cypress/cyfmac43455-sdio.bin' repeating in the logs. If you see it, upgrade to the official power supply and a high-quality USB-C cable with 20AWG power wires.

How do I bridge the Raspberry Pi access point to ethernet?

If you want the Pi to act as a wireless bridge where connected Wi-Fi clients receive IP addresses from your main home router (rather than the Pi's internal DHCP), you need to bridge wlan0 and eth0. Note that the Pi's internal Wi-Fi chip does not support native 4-address WDS bridging in AP mode. Instead, you must use proxy ARP or configure a routed subnet. The easiest method in Bookworm is to leave the Pi as a NAT router (using the shared method shown in this guide) and let the Pi handle DHCP for the wireless clients, keeping the network topology clean and avoiding MAC address translation issues with the main router.