To execute a reliable raspberry pi setup without monitor, you must pre-configure SSH and WiFi credentials via the Raspberry Pi Imager's hidden advanced menu (Ctrl+Shift+X) before flashing the SD card. The legacy method of dropping an empty ssh file and a wpa_supplicant.conf text file into the boot partition is officially deprecated in Raspberry Pi OS Bookworm and newer releases, which now rely on NetworkManager and firstrun.sh injection.
This guide targets the Raspberry Pi 5 (8GB) running Raspberry Pi OS (64-bit, Bookworm). We will cover the modern NetworkManager headless workflow, debug the exact SSH errors that stall headless boots, and provide a Python GPIO script to give your headless Pi a physical "SSH Ready" indicator.
The Headless Configuration Decision Tree
When configuring a headless Pi, you have three primary methods to inject network and SSH credentials. Follow this decision path to select the right tool for your build.
| Method | Best For | Pros | Cons |
|---|---|---|---|
| Raspberry Pi Imager GUI (Advanced) | 95% of standard headless builds | Handles NetworkManager profiles automatically; sets locale/hostname. | Requires a desktop OS to run the Imager software. |
Custom NetworkManager CLI Config |
Automated CI/CD flashing, bulk fleet deployment | Scriptable via nmcli or custom .nmconnection files. |
Complex syntax; high risk of typo-induced boot failures. |
| Ethernet Direct + SSH | Initial provisioning when WiFi is enterprise/WPA3 | Bypasses WiFi entirely; DHCP is instant. | Requires physical cable access to the router on first boot. |
Parts List & GPIO Pin Mapping
A headless setup fails most often due to power brownouts during the first-boot filesystem resize. Ensure your hardware matches these exact specifications.
Hardware Spec Sheet
- Board: Raspberry Pi 5 (8GB variant) with Active Cooler
- Power Supply: Official 27W USB-C PD Power Supply (Crucial: Pi 5 requires 5V/5A PD; standard 15W Pi 4 bricks will throttle USB and PCIe)
- Storage: 64GB Samsung EVO Plus microSD (A2 rated for high IOPS during OS resize)
- Indicator LED: 5mm Green LED with 330Ω current-limiting resistor
Pin Mapping: SSH Status LED
We will wire a physical LED to indicate when the SSH daemon is actively listening, saving you from guessing if the Pi has finished booting.
| Component | Pi 5 GPIO / Pin | Physical Pin # | Notes |
|---|---|---|---|
| LED Anode (+) | GPIO 21 | Pin 40 | Via 330Ω resistor |
| LED Cathode (-) | GND | Pin 39 | Direct connection |
Step-by-Step: Flashing and Booting Headless
Because Bookworm uses NetworkManager, the OS generates the WiFi connection profile on the first boot using the parameters injected by the Imager. Do not attempt to manually create wpa_supplicant files.
- Open Advanced Settings: Launch Raspberry Pi Imager on your PC/Mac. Select your Pi 5 and the 64-bit Bookworm OS. Press
Ctrl+Shift+X(orCmd+Shift+Xon Mac) to open the hidden advanced menu. - Set Hostname & SSH: Change the hostname to something unique (e.g.,
pi-node-01.local). Scroll to Enable SSH and select "Use password authentication". Enter your username and a strong password. - Configure WiFi: Check "Configure wireless LAN". Enter your exact SSID and password. Crucial: Set the WiFi country code correctly, or the 5GHz band will be disabled by regulatory domain locks.
- Flash and Seat: Write the image to the A2-rated SD card. Eject safely, seat it firmly into the Pi 5 (ensure it clicks), and connect the 27W power supply.
- The 90-Second Rule: On first boot, the Pi expands the root filesystem and generates SSH host keys. Do not attempt to SSH for at least 90 seconds. The Pi will reboot automatically once this is complete.
Troubleshooting: Exact Errors and Ranked Causes
Headless debugging relies on interpreting exact terminal errors. Here is the decision path for the two most common failure strings.
Error 1: ssh: connect to host raspberrypi.local port 22: Connection refused
This means your computer found the IP address via mDNS, but the Pi's SSH port is actively rejecting the TCP handshake.
- Cause 1 (Most Likely): First-boot filesystem resize is still running. The SSH daemon hasn't started yet. Fix: Wait 60 more seconds.
- Cause 2: You forgot to enable SSH in the Imager advanced menu. Fix: Reflash the SD card with SSH enabled.
- Cause 3: The Pi browned out during boot due to an underpowered supply, corrupting the SSH host key generation. Fix: Verify you are using the 27W USB-C PD supply, not a phone charger.
Error 2: ping: raspberrypi.local: Name or service not known
This is an mDNS (Multicast DNS) resolution failure. The Pi might be on the network, but your PC cannot resolve the .local hostname.
- Cause 1 (Most Likely): Your router has "AP Isolation" or "Client Isolation" enabled, blocking multicast packets between WiFi clients. Fix: Disable AP Isolation in your router admin panel.
- Cause 2: Windows lacks the Bonjour mDNS service. Fix: Install Bonjour Print Services for Windows, or use the direct IP address.
1. Log into your router's admin page and check the DHCP lease table for the Pi's MAC address to find its direct IP (bypassing mDNS).
2. Reseat the microSD card; the Pi 5 slot is shallow and partial insertion causes silent boot loops.
3. Check the Pi 5 power LED. A steady green means boot is complete; a blinking pattern indicates a bootloader or power fault.
Python Verification Script: SSH-Active Indicator
To eliminate the guesswork, we will run a lightweight Python daemon that checks if port 22 (SSH) is listening and turns on our GPIO 21 LED. This script targets the Raspberry Pi 5 (8GB) on Pi OS Bookworm.
Prerequisite: Ensure gpiozero is installed via sudo apt install python3-gpiozero.
#!/usr/bin/env python3
"""
Headless SSH Status Indicator for Raspberry Pi 5 (Bookworm)
Monitors local port 22 and illuminates GPIO 21 when SSH is ready.
"""
import socket
import time
import sys
from gpiozero import LED
from signal import pause
# Pin definition matching our physical wiring
SSH_LED = LED(21)
def is_ssh_listening(host='127.0.0.1', port=22, timeout=1):
"""Check if the SSH daemon is accepting connections on localhost."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
result = s.connect_ex((host, port))
return result == 0
except socket.error as e:
print(f"Socket error: {e}")
return False
def main():
print("Starting Headless SSH Monitor...")
print("Target: GPIO 21 (Pin 40)")
try:
while True:
if is_ssh_listening():
if not SSH_LED.is_lit:
print("[OK] SSH Daemon active. LED ON.")
SSH_LED.on()
else:
if SSH_LED.is_lit:
print("[WARN] SSH Daemon down. LED OFF.")
SSH_LED.off()
# Poll every 5 seconds to minimize CPU overhead
time.sleep(5)
except KeyboardInterrupt:
print("\nMonitor stopped by user.")
finally:
# Safe GPIO cleanup
SSH_LED.off()
SSH_LED.close()
sys.exit(0)
if __name__ == '__main__':
main()
Save this as ssh_monitor.py and run it in the background using nohup python3 ssh_monitor.py &. For persistent operation across reboots, create a systemd service file in /etc/systemd/system/.
Extending and Simplifying the Build
Depending on your deployment environment, you may want to strip this build down or add physical telemetry.
How to Simplify
If you are deploying a single Pi on a workbench and don't want to deal with WiFi credentials or mDNS issues, drop the WiFi configuration entirely for the first boot. Plug the Pi 5 directly into your router via an Ethernet cable. The Pi will pull a DHCP address instantly. SSH in via the router's DHCP table, configure your WiFi via nmcli device wifi connect 'SSID' password 'PASS', and then disconnect the cable. This bypasses all Imager GUI steps and guarantees network access.
How to Extend
For rack-mounted or enclosed headless nodes where you cannot see the GPIO LED, extend the build by adding a 128x64 I2C OLED (SSD1306) wired to GPIO 2 (SDA) and GPIO 3 (SCL). Using the luma.oled Python library, you can modify the script above to print the Pi's current wlan0 IP address and CPU temperature directly onto the screen. This turns a completely headless node into a self-reporting appliance, allowing you to plug in a keyboard and monitor only when the screen explicitly tells you the network has failed.






