Setting up a Raspberry Pi headless means configuring the operating system, WiFi credentials, and SSH access entirely via software before the first boot, eliminating the need for a monitor, keyboard, or mouse. The direct answer for modern deployments: use the Raspberry Pi Imager's advanced settings (Ctrl+Shift+X) to inject WiFi and SSH configurations into the firstrun.sh script, flash Raspberry Pi OS Bookworm (64-bit), and boot.

However, the transition to Bookworm OS fundamentally changed how networking and user management work under the hood. The legacy method of dropping an empty ssh file and a wpa_supplicant.conf file into the boot partition is deprecated. This guide details the modern, reliable workflow for setting up Raspberry Pi headless environments, complete with hardware integration and robust Python telemetry code.

Project Difficulty Rating: Intermediate
Time to Complete: 45 minutes
Target Board Variant: Raspberry Pi 5 (8GB) and Raspberry Pi 4 Model B (4GB/8GB)

Parts List and Hardware Specifications

When running headless, you cannot rely on a monitor to catch brownout warnings. Power delivery and storage I/O speeds are critical. Here is the exact bill of materials for a stable headless sensor node.

ComponentExact Variant / SpecificationEstimated Cost (2026)
MicrocontrollerRaspberry Pi 5 (8GB RAM)$80.00
Power SupplyOfficial 27W USB-C PD Power Supply (Crucial for Pi 5 peripheral support)$12.00
StorageSanDisk Extreme 64GB microSD (A2 Application Performance Class)$14.00
SensorBosch BME280 I2C Temperature/Humidity/Pressure Breakout$9.00
WiringFemale-to-Female Dupont Jumper Wires (20cm)$4.00

Step-by-Step: Flashing and Configuring Headless Boot

Bookworm OS uses NetworkManager instead of dhcpcd and wpa_supplicant. Therefore, pre-configuring WiFi requires the Imager to write the correct NetworkManager keyfiles during the first boot sequence.

  1. Download Raspberry Pi Imager: Install the latest version from the official Raspberry Pi software page.
  2. Select OS and Storage: Choose Raspberry Pi OS (64-bit) (Bookworm) and select your A2-rated microSD card.
  3. Open Advanced Settings: Press Ctrl+Shift+X (or click the gear icon) to open the headless configuration menu.
  4. Set Hostname: Change the hostname from raspberrypi to something unique like sensor-node-01 to avoid mDNS collisions on your network.
  5. Enable SSH: Select 'Use password authentication' and set a strong username/password, OR select 'Allow public-key authentication' and paste your id_rsa.pub key. (Key-based is highly recommended for headless security).
  6. Configure WiFi: Enter your SSID and password. Critical: You must select your correct WiFi Country Code. The 5GHz band will not initialize if the country code is unset due to regulatory domain restrictions.
  7. Flash and Boot: Write the image, insert the card into the Pi, apply power, and wait 90 seconds for the first-boot partition resize and NetworkManager configuration.
Callout Tip: If you are building an image offline for deployment and cannot use the Imager GUI, you must create a userconf.txt file in the boot partition containing username:hashed_password and rely on a connected Ethernet cable for initial network access, as generating NetworkManager keyfiles manually via text files in the boot partition is no longer supported in Bookworm.

Hardware Integration: Pin Mapping for Headless Sensor Node

To verify our headless setup is working and reading hardware, we will wire a BME280 sensor via the I2C1 bus. The I2C pins on the Raspberry Pi 5 and Pi 4 remain identical on the 40-pin header.

BME280 Breakout PinRaspberry Pi GPIO / FunctionPhysical Pin Number
VCC / VIN3.3V PowerPin 1
GNDGroundPin 6
SCLGPIO 3 (I2C1 SCL)Pin 5
SDAGPIO 2 (I2C1 SDA)Pin 3

Note: Ensure I2C is enabled. In the Imager Advanced Settings (Step 3 above), check the box for 'Enable I2C' under the Services tab. If you missed this, you will have to enable it later via sudo raspi-config over SSH.

Complete Telemetry Code with Error Handling

Below is the complete, compilable Python script to read the sensor and log the data. Headless scripts must handle hardware faults gracefully, as you aren't there to see a traceback on a monitor.

Prerequisites: Run sudo apt update && sudo apt install python3-smbus python3-pip -y and pip3 install RPi.bme280 via your SSH session before executing.

import smbus2
import bme280
import logging
import time
import sys
import os

# ==========================================
# PIN & BUS DEFINITIONS
# ==========================================
# I2C1 Bus on Raspberry Pi 4/5
# SDA: GPIO 2 (Physical Pin 3)
# SCL: GPIO 3 (Physical Pin 5)
I2C_BUS_ID = 1
BME280_I2C_ADDR = 0x76  # Use 0x77 if your breakout board has the address jumper bridged
LOG_FILE = '/home/pi/headless_telemetry.log'

# ==========================================
# LOGGING CONFIGURATION
# ==========================================
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format='%(asctime)s | %(levelname)s | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

def initialize_sensor():
    """Initializes the I2C bus and BME280 sensor with calibration parameters."""
    try:
        bus = smbus2.SMBus(I2C_BUS_ID)
        calibration_params = bme280.load_calibration_params(bus, BME280_I2C_ADDR)
        logging.info('Sensor initialized successfully on I2C bus %d', I2C_BUS_ID)
        return bus, calibration_params
    except FileNotFoundError:
        logging.critical('I2C bus not found. Is I2C enabled in raspi-config?')
        sys.exit(1)
    except OSError as e:
        logging.critical('Hardware fault: %s. Check wiring and I2C address.', e)
        sys.exit(1)

def main():
    bus, calibration_params = initialize_sensor()
    
    while True:
        try:
            data = bme280.sample(bus, BME280_I2C_ADDR, calibration_params)
            temp_c = round(data.temperature, 2)
            humidity = round(data.humidity, 2)
            pressure = round(data.pressure, 2)
            
            logging.info(f'Temp: {temp_c}C | Humidity: {humidity}% | Pressure: {pressure}hPa')
            
            # In a production headless node, you would push this to MQTT or an API here.
            
        except OSError as e:
            logging.error('I2C read failure: %s. Retrying in 10s.', e)
        except Exception as e:
            logging.exception('Unexpected software error occurred.')
            
        time.sleep(60) # Poll every 60 seconds

if __name__ == '__main__':
    # Ensure the script is running with appropriate permissions for I2C
    if os.geteuid() != 0 and not os.access('/dev/i2c-1', os.R_OK):
        logging.warning('Running without root; ensure user is in the i2c group.')
    main()

Debugging: Network Failures and SSH Errors

When setting up Raspberry Pi headless, the most common point of failure is the first network connection. If your SSH client throws an error, follow this decision path.

Exact Error: ssh: connect to host 192.168.1.50 port 22: Connection refused

Meaning: Your computer can see the Pi's IP address on the network, but the Pi is actively rejecting the SSH connection.

  • Cause 1 (Most Likely): SSH was not enabled in the Imager Advanced Settings, or the firstrun.sh script failed to execute before you tried to connect.
  • Cause 2: You are trying to log in as root, which is disabled by default in Bookworm.
  • Fix: Wait another 60 seconds. The Pi 5 boots fast, but the first-boot partition resize and service enabling can take up to 2 minutes. If it still refuses, re-flash the SD card and double-check the 'Enable SSH' toggle in the Imager.

Exact Error: ssh: Could not resolve hostname sensor-node-01.local: Name or service not known

Meaning: mDNS (Multicast DNS) is failing to resolve the .local address to an IP.

  • Cause 1: Your Windows PC lacks the Bonjour Print Services or mDNS responder running.
  • Cause 2: The Pi is connected to a 2.4GHz network, and your PC is on a 5GHz network with AP isolation enabled on the router.
  • Fix: Bypass mDNS entirely. Log into your router's admin panel, check the DHCP lease table for 'sensor-node-01', and SSH directly into the assigned IPv4 address (e.g., ssh pi@192.168.1.50).
The First 3 Things to Check When Headless Boot Fails:
  1. Check Router DHCP Leases: Verify the Pi actually pulled an IP address. If it's not in the router table, it didn't connect to WiFi.
  2. Verify WiFi Country Code: If you left the country code blank in the Imager, the 5GHz radio will remain disabled by the kernel. Re-flash and set the code.
  3. Check Power LED: On the Pi 5, a solid green ACT LED indicates successful boot. If it's flashing in a specific pattern (e.g., 4 long, 5 short), it indicates a fatal firmware or EEPROM error, not a network issue.

Extending and Simplifying the Build

How to Simplify: If headless networking is causing persistent friction, simplify the build by using Raspberry Pi Connect. This is the official remote access service provided by Raspberry Pi Ltd. By installing the rpi-connect package on a Pi that has temporary internet access (via Ethernet or a quick desktop setup), you can access the shell and desktop via a secure web browser tunnel without messing with router port forwarding, mDNS, or static IPs.

How to Extend: For remote, off-grid headless nodes, extend the build by adding a hardware watchdog. The BCM2835/BCM2712 silicon includes a built-in watchdog timer. By enabling the watchdog daemon in Bookworm (sudo apt install watchdog), the Pi will automatically hard-reboot if the OS kernel panics or the Python telemetry script hangs the CPU, ensuring your headless node recovers from software faults without human intervention.

FAQ: Setting Up Raspberry Pi Headless

How do I enable SSH on a headless Raspberry Pi without the Imager?

If you have already flashed the drive and cannot use the Imager, the legacy method of placing an empty file named ssh (no extension) in the root of the boot partition still works on Bookworm, but only if you also configure a user. Because the default pi user no longer exists, you must also place a userconf.txt file in the boot partition containing username:hashed_password. You can generate the hash using openssl passwd -6 on another Linux machine. Without both files, SSH will enable, but you will have no valid credentials to log in.

Why is my headless Raspberry Pi not connecting to WiFi on Bookworm?

The most common culprit is the shift from wpa_supplicant to NetworkManager. In older OS versions, you could drop a wpa_supplicant.conf file into the boot drive. This file is completely ignored in Bookworm. You must use the Raspberry Pi Imager's advanced settings to generate the correct NetworkManager keyfiles, or connect via Ethernet first and configure the WiFi using the nmcli device wifi connect <SSID> password <PASSWORD> command over SSH.

Can I set up a Raspberry Pi headless using a smartphone instead of a PC?

Yes, but it requires a workaround since mobile phones cannot run the Raspberry Pi Imager. Flash the SD card using a PC first. Then, plug the Pi directly into your smartphone via a USB-C to USB-C cable. The Raspberry Pi 4 and 5 support USB Ethernet tethering (g_ether). By enabling USB OTG networking, your phone will assign the Pi an IP address via its mobile hotspot, allowing you to use a mobile SSH app like Termius to connect directly to the Pi over the USB cable to configure the rest of the system.