The most reliable way to configure a Raspberry Pi as hotspot on modern Raspberry Pi OS (Bookworm and later) is using NetworkManager via the nmcli command-line tool. The legacy method of manually editing hostapd.conf and dnsmasq is deprecated, prone to boot-race conditions, and will fail on current OS images. This guide provides the exact nmcli commands, a Python GPIO monitoring script, and a debugging framework for when the interface drops.

The Verdict: Which Board Variant to Pick

Not every Pi is suited for 24/7 access point duty. Thermal throttling and Wi-Fi chip capabilities dictate your choice. Use this decision path to select your board:

Criteria Pi Zero 2 W Pi 4 Model B (4GB) Pi 5 (8GB)
Wi-Fi Band 2.4GHz only Dual-band (2.4 / 5GHz) Dual-band (2.4 / 5GHz)
Thermal Profile (Passive) Excellent (low draw) Good (requires basic heatsink) Poor (requires Active Cooler)
Routing Overhead High (CPU bottleneck) Low Negligible
Approx. Cost (2026) $15 - $20 $55 $80+
Concrete Pick: Choose the Raspberry Pi 4 Model B (4GB). It offers dual-band Wi-Fi, handles NAT routing without breaking a sweat, and runs cool enough for 24/7 AP duty with a $5 passive aluminum heatsink case. The Pi 5 is overkill for a pure hotspot and requires active cooling; the Zero 2 W lacks 5GHz support, which is mandatory for avoiding congested 2.4GHz urban airspace.

Parts List and GPIO Pin Mapping

This build targets the Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS Bookworm (64-bit, Lite). The code and pinouts below are fully compatible with the Pi 5 if you opt for that route.

Bill of Materials

  • Board: Raspberry Pi 4 Model B (4GB RAM)
  • Storage: SanDisk Extreme 32GB microSD (A2 rating required for Bookworm's I/O patterns)
  • Power: Official Raspberry Pi 27W USB-C PD Power Supply (prevents brownout warnings under Wi-Fi TX load)
  • Indicators: 2x 5mm LEDs (Green/Red), 2x 330Ω resistors, half-size breadboard

GPIO Pin Mapping for Status Monitor

We will use two GPIO pins to drive physical LEDs, giving you a visual diagnostic of the hotspot state without needing to SSH in.

Function GPIO (BCM) Physical Pin Component
Hotspot Active (Green) GPIO 17 Pin 11 Green LED + 330Ω to GND
Hotspot Down (Red) GPIO 27 Pin 13 Red LED + 330Ω to GND
Ground Reference GND Pin 9 Common ground for LEDs

Step-by-Step Hotspot Configuration

Forget /etc/hostapd/hostapd.conf. On Bookworm, NetworkManager owns the Wi-Fi stack. Run these commands exactly as written in your SSH terminal.

  1. Update the system and install prerequisites:
    sudo apt update && sudo apt upgrade -y
    sudo apt install network-manager python3-gpiozero -y
  2. Create the Wi-Fi connection profile:
    sudo nmcli connection add type wifi ifname wlan0 con-name "PiHotspot" autoconnect yes ssid "FluxNet_5G"
  3. Set the connection to Access Point mode and configure IP sharing:
    sudo nmcli connection modify "PiHotspot" 802-11-wireless.mode ap 802-11-wireless.band a ipv4.method shared

    Note: band a forces 5GHz. Change to band bg if you strictly need 2.4GHz for legacy IoT devices.

  4. Apply WPA2 security:
    sudo nmcli connection modify "PiHotspot" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "YourSecurePassword123!"
  5. Bring the interface up:
    sudo nmcli connection up "PiHotspot"
  6. Verify the state:
    nmcli connection show --active

    You should see PiHotspot listed with wifi as the type and wlan0 as the device.

Pro-Tip: NetworkManager's ipv4.method shared automatically spins up an internal DHCP server (dnsmasq under the hood) and configures iptables for NAT routing. You do not need to manually configure IP forwarding in sysctl.conf.

Python Status Monitor Script

Headless access points are frustrating when they drop offline. This Python script polls NetworkManager via nmcli and updates the physical GPIO LEDs. It includes robust error handling for interface drops.

#!/usr/bin/env python3
import subprocess
import time
import sys
from gpiozero import LED

# --- PIN DEFINITIONS ---
PIN_GREEN = 17  # Physical Pin 11 (Hotspot Active)
PIN_RED = 27    # Physical Pin 13 (Hotspot Down/Error)

green_led = LED(PIN_GREEN)
red_led = LED(PIN_RED)

def check_hotspot_status():
    """Queries NetworkManager for the PiHotspot connection state."""
    try:
        # Run nmcli to get the state of our specific connection
        result = subprocess.run(
            ['nmcli', '-g', 'GENERAL.STATE', 'connection', 'show', 'PiHotspot'],
            capture_output=True, text check=True, timeout=5
        )
        state = result.stdout.strip()
        if 'activated' in state.lower():
            return True
        return False
    except subprocess.CalledProcessError:
        # Connection profile missing or nmcli failed
        return False
    except subprocess.TimeoutExpired:
        # NetworkManager is hung
        return False
    except Exception as e:
        print(f"Unexpected error querying nmcli: {e}", file=sys.stderr)
        return False

def main():
    print("Starting Pi Hotspot GPIO Monitor...")
    try:
        while True:
            is_active = check_hotspot_status()
            if is_active:
                green_led.on()
                red_led.off()
            else:
                green_led.off()
                red_led.blink(on_time=0.5, off_time=0.5, background=False)
                # Note: background=False blocks, so we use a manual loop for blink effect in a real daemon,
                # but for this simple script, we'll just do a solid red to avoid blocking the sleep.
                red_led.on()
            
            time.sleep(10) # Poll every 10 seconds
    except KeyboardInterrupt:
        print("\nMonitor stopped by user.")
    finally:
        green_led.off()
        red_led.off()
        print("LEDs cleaned up.")

if __name__ == '__main__':
    main()

Save this as ap_monitor.py and run it via sudo python3 ap_monitor.py. For persistence, wrap it in a systemd service.

Debugging: Exact Error Strings and Ranked Causes

When configuring a Raspberry Pi as hotspot, NetworkManager will throw specific errors if the hardware or stack is misconfigured. Here are the first three things to check, mapped to their exact terminal outputs.

1. "No suitable device found"

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

Ranked Causes:

  1. RFKill Soft Block: The Wi-Fi chip is disabled at the kernel level. Fix: Run sudo rfkill unblock wifi.
  2. Interface Renaming: You plugged in a USB Wi-Fi dongle, and the internal chip became wlan1. Fix: Run iwconfig to verify the internal chip name, then modify the connection: sudo nmcli connection modify "PiHotspot" connection.interface-name wlan0.
  3. Country Code Missing: The 5GHz radio refuses to transmit without a regulatory domain. Fix: Run sudo raspi-config -> Localisation Options -> WLAN Country, and set it to your region.

2. "Secrets were required, but no secrets were provided"

Exact Error String: Error: Connection activation failed: Secrets were required, but no secrets were provided.

Ranked Causes:

  1. Password Too Short: WPA2 requires a minimum of 8 characters. If your wifi-sec.psk was shorter, NetworkManager silently drops it during creation and fails on activation. Fix: Re-run the nmcli modify command with a 12+ character password.
  2. Missing Key Management: You forgot to set wifi-sec.key-mgmt wpa-psk. Fix: Add it via the modify command.

3. "hostapd.service: Failed with result 'exit-code'"

Exact Error String: hostapd.service: Failed with result 'exit-code' (Seen in journalctl -xe)

Ranked Causes:

  1. Conflicting Legacy Configs: You followed a 2021 tutorial and manually installed hostapd and dnsmasq. NetworkManager and standalone hostapd cannot both control wlan0. Fix: sudo apt purge hostapd dnsmasq and reboot.
Authoritative Reference: For deep-dives into NetworkManager's Wi-Fi properties, consult the official nmcli documentation and the Raspberry Pi NetworkManager guide.

Extending or Simplifying the Build

Once your baseline hotspot is stable, you have two distinct paths depending on your network goals.

Extension: Add Network-Wide Ad Blocking (Pi-hole)

Because NetworkManager's shared method acts as a DHCP server, you can intercept DNS requests to run Pi-hole.

  1. Install Pi-hole: curl -sSL https://install.pi-hole.net | bash
  2. During setup, select wlan0 as the interface.
  3. Force NetworkManager to hand out the Pi's IP as the DNS server: sudo nmcli connection modify "PiHotspot" ipv4.dns "192.168.4.1" (assuming default shared subnet).
  4. Restart the connection: sudo nmcli connection up "PiHotspot".

Simplification: Bridge Mode (No NAT Routing)

If you don't want the Pi to act as a router creating a separate subnet, but rather just a wireless bridge to your main router's LAN:

  1. Delete the shared connection: sudo nmcli connection delete "PiHotspot"
  2. Create a bridge: sudo nmcli connection add type bridge con-name "br0" ifname br0
  3. Bridge the Ethernet and Wi-Fi: Add wlan0 and eth0 as slaves to br0 using nmcli connection add type bridge-slave.
  4. Warning: Bridging Wi-Fi to Ethernet requires the Wi-Fi chip to support 4-address mode (WDS), which the internal Pi Wi-Fi chips struggle with. If you need a true bridge, buy a TP-Link Archer T3U Plus USB Wi-Fi adapter which supports monitor/bridge modes reliably on Linux.

By sticking to NetworkManager and ditching legacy hostapd configs, your Raspberry Pi hotspot will survive OS upgrades, reboot cleanly, and route traffic at line speed without throwing cryptic DHCP race errors.