To SSH into a Raspberry Pi for a headless hardware build, enable the SSH daemon by placing an empty file named ssh in the boot partition or using the Raspberry Pi Imager's advanced settings, locate the device's IP via your router's DHCP table or ping raspberrypi.local, and connect via terminal using ssh your_username@ip_address. For embedded projects running on the latest Raspberry Pi OS (Bookworm), you must also configure the lgpio backend to interact with the GPIO header remotely.
Running a Pi without a monitor, keyboard, or mouse (headless) is the standard deployment for industrial sensor nodes, MQTT gateways, and automated relays. This guide walks through provisioning a Raspberry Pi 5 for a remote I2C sensor build, writing the control code, and debugging the exact SSH errors that halt jobsite deployments.
Project Spec Sheet & Hardware Requirements
Before writing a single line of code, verify your hardware. The Raspberry Pi 5 has stricter power delivery requirements than the Pi 4; running I2C sensors and WiFi simultaneously on a marginal power supply will cause silent brownouts that drop your SSH session and corrupt the SD card.
Bill of Materials (BOM)
- Compute: Raspberry Pi 5 (8GB variant) - Required for heavy edge-computing or multi-threaded MQTT logging.
- Power: Official Raspberry Pi 27W USB-C PD Power Supply - Do not use generic 5V/3A phone chargers; the Pi 5 negotiates 5V/5A via PD.
- Storage: SanDisk Extreme 64GB microSD (A2 rating) - A2 ensures the random I/O performance needed for OS logging over SSH.
- Sensor: Adafruit BME280 I2C Temperature/Humidity/Pressure breakout.
- Indicator: Standard 5mm LED with a 330Ω current-limiting resistor.
Network & SSH Protocol Specifications
When configuring your router or firewall for a remote Pi node, use these baseline parameters for secure, modern SSH access.
| Parameter | Specification / Value | Notes for Embedded Deployments |
|---|---|---|
| Protocol Version | SSH-2 (OpenSSH 9.x) | SSH-1 is obsolete and rejected by modern Pi OS defaults. |
| Default Port | 22 (TCP) | Change to a non-standard port (e.g., 2222) if exposed to the WAN via port forwarding to reduce botnet noise. |
| Key Exchange | Curve25519-SHA256 | Default on Bookworm; do not downgrade to RSA-1024 for legacy compatibility. |
| Authentication | Ed25519 Public Key | Disable password authentication in sshd_config after verifying key-based login works. |
| Encryption | AES256-GCM | Hardware-accelerated on the Pi 5's Cortex-A76 cores; negligible CPU overhead. |
BME280 I2C Pin Mapping
The Pi 5's I2C bus layout remains compatible with previous generations, but physical pin numbering must be exact. Wire the BME280 to the primary I2C bus (Bus 1).
| BME280 Pin | Pi 5 GPIO Header Pin | BCM GPIO Number | Function |
|---|---|---|---|
| VIN / 3V3 | Pin 1 | N/A | 3.3V Power |
| GND | Pin 6 | N/A | Ground Reference |
| SCK / SCL | Pin 5 | GPIO 3 (SCL1) | I2C Clock |
| SDI / SDA | Pin 3 | GPIO 2 (SDA1) | I2C Data |
Step-by-Step Headless SSH Configuration
Do not boot the Pi with a monitor just to enable SSH. Use the Raspberry Pi Imager's advanced settings to inject credentials and network configs before the SD card ever touches the board.
- Flash the OS with Advanced Settings: Open Raspberry Pi Imager, select Raspberry Pi OS (64-bit) Bookworm. Click the gear icon (or press
Ctrl+Shift+X). Check Enable SSH and select Use password authentication (we will switch to keys later). Set your custom username, password, and inject your local WiFi SSID/password. - Boot and Discover the IP: Insert the SD card and apply power. Wait 60 seconds for the first-boot resize and network connection. From your workstation terminal, run
ping raspberrypi.local. If mDNS is blocked on your network, check your router's DHCP client list for the MAC address starting withdc:a6:32or2c:cf:67(common Pi OUI blocks). - Generate and Copy SSH Keys: On your host machine, generate an Ed25519 key pair:
ssh-keygen -t ed25519 -C 'pi5-sensor-node'. Copy it to the Pi:ssh-copy-id -i ~/.ssh/id_ed25519.pub username@raspberrypi.local. - Harden the SSH Daemon: SSH into the Pi. Edit the config via
sudo nano /etc/ssh/sshd_config. SetPasswordAuthentication noandPermitRootLogin no. Restart the service withsudo systemctl restart ssh.
/etc/machine-id and /etc/ssh/ssh_host_* keys.
Remote GPIO Control Code (Python)
The code below targets the Raspberry Pi 5 (8GB) running Bookworm (Python 3.11+). Because the legacy RPi.GPIO library is incompatible with the Pi 5's new RP1 southbridge chip, this script uses gpiozero (which defaults to the lgpio backend on Bookworm) for the LED, and smbus2 for raw I2C communication with the BME280.
Install dependencies via your SSH session before running: sudo apt update && sudo apt install python3-gpiozero python3-smbus2 i2c-tools.
import time
import sys
from gpiozero import LED
from smbus2 import SMBus
# --- Pin & Address Definitions ---
# BCM GPIO 17 is Physical Pin 11 on the Pi 5 header
STATUS_LED_PIN = 17
# Default BME280 I2C address (SDO tied to GND)
BME280_I2C_ADDR = 0x76
I2C_BUS_ID = 1
# Initialize GPIO via gpiozero (uses lgpio backend on Pi 5)
status_led = LED(STATUS_LED_PIN)
def read_bme280_temp(bus):
"""Reads uncompensated temperature from BME280 register 0xFA."""
try:
# Read 3 bytes from the temp register
data = bus.read_i2c_block_data(BME280_I2C_ADDR, 0xFA, 3)
adc_t = ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4
# Simplified conversion for demonstration (real-world requires calibration registers)
temp_c = (adc_t / 16384.0) * 25.0
return round(temp_c, 2)
except OSError as e:
print(f'I2C Bus Error: {e}. Check wiring and pull-ups.', file=sys.stderr)
return None
def main():
print(f'Starting sensor node on I2C Bus {I2C_BUS_ID}...')
status_led.blink(on_time=0.5, off_time=0.5, n=2) # Visual boot confirmation
try:
with SMBus(I2C_BUS_ID) as bus:
# Wake up BME280 (Write 0x00 to ctrl_meas register 0xF4 for sleep, then normal)
bus.write_byte_data(BME280_I2C_ADDR, 0xF4, 0x27)
time.sleep(0.1) # Allow sensor startup
while True:
temp = read_bme280_temp(bus)
if temp is not None:
print(f'Node Temp: {temp} C')
status_led.on()
else:
status_led.blink(on_time=0.1, off_time=0.1) # Fast blink on error
time.sleep(5)
except KeyboardInterrupt:
print('\nSSH session terminated by user. Cleaning up GPIO.')
except FileNotFoundError:
print(f'Fatal: I2C Bus {I2C_BUS_ID} not found. Run "sudo raspi-config" to enable I2C.', file=sys.stderr)
finally:
status_led.off()
status_led.close()
if __name__ == '__main__':
main()
Debugging SSH Connection Failures
When a headless Pi drops off the network or refuses your connection, you are flying blind. Before pulling the SD card or plugging in a monitor, evaluate the exact error string returned by your SSH client. Here are the first three things to check when it fails, mapped to the specific terminal output.
1. Error: ssh: connect to host 192.168.1.50 port 22: Connection refused
What it means: Your workstation reached the IP address, but the Pi's OS actively rejected the TCP connection on port 22.
- Cause A (Most Likely): The SSH daemon is not running or was never enabled. If you forgot the
sshfile in the boot partition, the Pi OS disables SSH by default for security. - Cause B: The Pi has crashed or is caught in a boot loop due to a power brownout. The IP is still leased in your router's ARP table, but the OS isn't up.
- Fix: Ping the IP. If it replies, SSH is disabled. Power down, mount the SD card on your PC, and place an empty file named exactly
ssh(no extension) in the root of thebootfspartition.
2. Error: Permission denied (publickey).
What it means: The SSH handshake succeeded, but the Pi rejected your authentication credentials.
- Cause A: You disabled password authentication in
sshd_config, but your host machine is trying to use a password or the wrong key pair. - Cause B: File permissions on the Pi are too open. OpenSSH strictly enforces that
~/.ssh/authorized_keysmust be600and the~/.sshdirectory must be700. - Fix: Force key-based auth explicitly from your host:
ssh -i ~/.ssh/id_ed25519 username@192.168.1.50. If that fails, you must connect a physical keyboard/monitor to the Pi to fix thechmodpermissions on the.sshdirectory.
3. Error: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
What it means: The cryptographic fingerprint of the server at this IP address does not match what your workstation has saved in ~/.ssh/known_hosts.
- Cause A (Benign): You re-flashed the Pi's SD card with a fresh OS, generating new host keys, but the router assigned it the same DHCP IP address.
- Cause B (Malicious): An active Man-In-The-Middle (MITM) attack is intercepting your traffic (highly unlikely on a local home LAN, but possible in hostile corporate environments).
- Fix: If you know you re-flashed the Pi, clear the old key from your host machine by running:
ssh-keygen -R 192.168.1.50. Reconnect and accept the new fingerprint.
Extending and Simplifying the Build
Once your SSH session is stable and the Python script is polling data, you need to transition from a manual terminal session to a resilient embedded node.
How to Extend the Build
- Add MQTT Telemetry: Install
paho-mqttvia pip. Modify the Python script to publish thetemp_cvariable to a Mosquitto broker topic likesensors/pi5/bme280/temp. This allows Node-RED or Home Assistant to ingest the data without maintaining a persistent SSH tunnel. - Deploy as a Systemd Service: Do not rely on
tmuxorscreento keep your script running after you close the SSH session. Create a.servicefile in/etc/systemd/system/to run the Python script on boot, complete with automatic restart directives (Restart=on-failure) if the I2C bus throws an unhandled exception. - Watchdog Timer: Enable the Pi 5's hardware watchdog. If the OS freezes (common with flaky USB-C power), the hardware watchdog will force a hard reboot, bringing your SSH and sensor services back online automatically.
How to Simplify the Build
- Assign a Static IP: DHCP lease expirations change your Pi's IP, breaking your automated SSH scripts and cron jobs. Edit
/etc/NetworkManager/system-connections/or useraspi-configto lock the Pi to a static IP outside your router's DHCP pool. - Enable Serial Console: If WiFi fails and you don't have an Ethernet drop nearby, SSH is useless. Enable the serial console in
raspi-config(Interface Options -> Serial Port). This allows you to plug a $10 USB-to-TTL serial cable (like the CP2102) directly into GPIO 14 (TXD) and 15 (RXD) to access a root shell without a network.
For deeper reference on OpenSSH hardening parameters specific to Debian-based embedded systems, consult the official Raspberry Pi remote access documentation. For Python GPIO pin factory configurations on the Pi 5, review the gpiozero library documentation to ensure your scripts leverage the correct lgpio backend.






