Setting up a Raspberry Pi AP (Access Point) on Raspberry Pi OS Bookworm requires a fundamental shift from older tutorials. Because Bookworm deprecated dhcpcd in favor of NetworkManager, legacy hostapd and dnsmasq configurations will fail or conflict with the OS network stack. The direct answer for a stable, modern Raspberry Pi AP is to use a Routed NAT topology powered by systemd-networkd for DHCP/masquerading and hostapd for the WiFi radio, while explicitly unmanaging the wireless interface from NetworkManager.
This guide targets the Raspberry Pi 5 (4GB) and Raspberry Pi 4 Model B running Raspberry Pi OS Bookworm (64-bit). By the end, you will have a WPA3-transition access point with an automatic hardware status LED.
Decision Tree: Bridged vs. Routed AP Topology
Before writing configuration files, you must choose how your Pi will route traffic. Most hobbyists default to bridged mode, but routed NAT is vastly superior for embedded and IoT deployments.
| Criteria | Bridged AP (Layer 2) | Routed NAT AP (Layer 3) |
|---|---|---|
| Subnet | Shares main router's subnet | Creates isolated Pi-managed subnet |
| DHCP Server | Main Router (requires MAC filtering/reservations) | Pi (systemd-networkd handles it locally) |
| IoT Isolation | None (IoT devices can see your main LAN) | High (IoT devices only see the Pi gateway) |
| Setup Complexity | High (requires bridging wlan0 to eth0, fighting NM) | Low (native systemd-networkd masquerade) |
Hardware & Parts List (2026 Standard)
Do not use underpowered supplies; the Pi 5 will brownout and drop the WiFi radio under AP transmit loads if starved of current.
- Compute: Raspberry Pi 5 (4GB) or Raspberry Pi 4 Model B (4GB)
- Power: Official Raspberry Pi 27W USB-C Power Supply (for Pi 5) or 15W USB-C (for Pi 4)
- Uplink: Cat6 Ethernet Cable (hardwired to your main router/switch)
- Storage: 32GB+ NVMe SSD via PCIe HAT (Pi 5) or A2-rated microSD (Pi 4)
- Status LED: 5mm Green Diffused LED + 220Ω 1/4W Resistor
- Wiring: 2x Female-to-Male Jumper Wires
Network Interface & GPIO Pin Mapping
Since this project relies on network interfaces rather than raw sensor GPIO, the mapping below defines the logical network boundaries and the physical hardware status indicator.
| Interface / Component | Designation | Role in AP Build |
|---|---|---|
| Ethernet Port | eth0 (or end0 on Pi 5) |
WAN Uplink (DHCP Client to main router) |
| Built-in WiFi | wlan0 |
AP Radio (Static IP, DHCP Server, NAT) |
| GPIO 21 | Physical Pin 40 | Status LED Anode (via 220Ω resistor) |
| Ground | Physical Pin 39 | Status LED Cathode |
Step-by-Step Configuration (Bookworm Native)
This procedure bypasses the deprecated dhcpcd and uses systemd-networkd's built-in IP masquerading, eliminating the need for complex iptables or nftables scripting.
1. Isolate wlan0 from NetworkManager
NetworkManager will aggressively try to connect wlan0 to known networks as a client. You must tell it to ignore the interface so hostapd can claim the radio.
sudo nmcli device set wlan0 managed no
sudo nmcli radio wifi off
2. Enable systemd-networkd
Enable the native systemd network daemon to handle the AP subnet and DHCP.
sudo systemctl enable systemd-networkd
sudo systemctl start systemd-networkd
3. Configure the AP Subnet and NAT
Create the network definition file. The IPMasquerade=ipv4 directive is the modern Bookworm method for NAT routing.
sudo nano /etc/systemd/network/10-wlan0.network
Paste the following configuration:
[Match]
Name=wlan0
[Network]
Address=192.168.4.1/24
DHCPServer=yes
IPMasquerade=ipv4
[DHCPServer]
PoolOffset=10
PoolSize=50
EmitDNS=yes
DNS=1.1.1.1
4. Configure hostapd for WPA3-Transition
WPA3 (SAE) is mandatory for modern security, but older ESP8266/ESP32 boards only support WPA2. We use transition mode to support both. For full parameter details, refer to the canonical hostapd configuration reference.
sudo nano /etc/hostapd/hostapd.conf
country_code=US
interface=wlan0
ssid=FluxLab_IoT
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1
# Security: WPA3-SAE Transition Mode
wpa=2
wpa_key_mgmt=SAE WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP
ieee80211w=2
sae_require_mfp=1
wpa_passphrase=YourSecurePassword123!
Note: Change country_code to your local ISO code. Operating without this violates RF regulations and blocks 5GHz channels.
5. Point hostapd to the Config and Enable
Edit the default hostapd daemon file to load your config:
sudo nano /etc/default/hostapd
Uncomment and set: DAEMON_CONF="/etc/hostapd/hostapd.conf"
sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo systemctl start hostapd
The Python Status Monitor (Complete Code)
Headless Pis need physical feedback. This Python script uses gpiozero to monitor the hostapd service state. If the AP drops, the LED blinks rapidly; if running, it stays solid.
#!/usr/bin/env python3
"""
Raspberry Pi AP Status Monitor
Target: Raspberry Pi 5 / 4 (Bookworm)
Hardware: LED on GPIO 21 (Pin 40) via 220 ohm resistor
"""
import subprocess
import time
from gpiozero import LED
from signal import pause
# Pin 40 maps to GPIO 21
STATUS_LED = LED(21)
def check_hostapd_status():
"""Returns True if hostapd is actively running."""
try:
result = subprocess.run(
['systemctl', 'is-active', 'hostapd'],
capture_output=True,
text=True,
check=False
)
return result.stdout.strip() == 'active'
except Exception as e:
print(f"Error checking service: {e}")
return False
def main():
print("Starting AP Status Monitor on GPIO 21...")
try:
while True:
if check_hostapd_status():
# Solid ON for healthy AP
STATUS_LED.on()
time.sleep(2)
else:
# Rapid blink for AP failure
STATUS_LED.blink(on_time=0.2, off_time=0.2, n=5, background=False)
time.sleep(1)
except KeyboardInterrupt:
STATUS_LED.off()
print("Monitor stopped.")
if __name__ == "__main__":
main()
Save this as /home/pi/ap_monitor.py, make it executable (chmod +x), and create a systemd service to run it on boot so it survives reboots without requiring user login.
Debugging: "nl80211: Could not configure driver mode"
If you run sudo hostapd -d /etc/hostapd/hostapd.conf and immediately see this exact error string, the kernel is refusing to put the WiFi chip into Master/AP mode.
nl80211: Could not configure driver modewlan0: Could not connect to kernel driver
The First Three Things to Check (Ranked by Probability):
- NetworkManager Interference (90% of cases): NetworkManager is still holding the interface.
Fix: Runsudo nmcli device set wlan0 managed noand reboot. - RFKill Soft Block (8% of cases): The OS has soft-blocked the WiFi radio to save power.
Fix: Runsudo rfkill unblock wifiand verify withrfkill list. - Missing or Invalid Country Code (2% of cases): The regulatory domain is unset, preventing the driver from initializing the radio frequencies.
Fix: Ensurecountry_code=US(or your region) is at the very top ofhostapd.conf.
For deeper diagnostics on Bookworm networking shifts, consult the official Raspberry Pi configuration documentation.
Extending and Simplifying the Build
Depending on your deployment environment, you may need to alter the complexity of this build.
How to Simplify (The NetworkManager Native Route)
If you do not need WPA3, custom DHCP ranges, or advanced hostapd parameters, you can skip systemd-networkd and hostapd entirely. NetworkManager can create a basic WPA2 hotspot in one command:
sudo nmcli connection add type wifi ifname wlan0 con-name FluxAP autoconnect yes ssid FluxLab_Simple
sudo nmcli connection modify FluxAP 802-11-wireless.mode ap 802-11-wireless.band bg ipv4.method shared
sudo nmcli connection modify FluxAP wifi-sec.key-mgmt wpa-psk wifi-sec.psk "SimplePass123"
sudo nmcli connection up FluxAP
Trade-off: This uses WPA2 only, offers no granular control over beacon intervals, and uses the 10.42.x.x shared subnet which is harder to route statically.
How to Extend (Adding DNS Filtering)
To turn this Raspberry Pi AP into a network-wide ad blocker or DNS sinkhole, install Pi-hole. Because we used Routed NAT, Pi-hole will automatically intercept DNS requests from the 192.168.4.x subnet.
Extension Step: In the [DHCPServer] block of your 10-wlan0.network file, change DNS=1.1.1.1 to DNS=192.168.4.1. This forces all connected IoT clients to use the Pi's local Pi-hole instance for name resolution before traffic is masqueraded out to the internet.






