To configure Raspberry Pi SSH for a headless setup in 2026, use the Raspberry Pi Imager Advanced Settings to inject your public SSH key and set a custom username before flashing the OS. The legacy method of dropping an empty ssh file in the boot partition still enables the daemon, but modern Raspberry Pi OS (Bookworm and newer) disables password authentication by default and removes the default pi user. If you do not inject an SSH key or configure a user via userconf.txt during imaging, you will be locked out of a headless board.
This guide walks through configuring secure SSH access on a Raspberry Pi 5 (4GB) and deploying a Python script to control a 5V relay module over the network.
The Decision Path: Choosing Your SSH Configuration Method
Not all SSH setup methods are equal. Use this decision matrix to select the right approach for your current hardware state.
| Scenario | Recommended Method | Why This Wins |
|---|---|---|
| Brand new headless Pi (MicroSD/NVMe) | Pi Imager Advanced Settings (SSH Key) | Pre-configures auth, bypasses password deprecation, sets hostname. |
| Existing Pi connected to monitor | sudo raspi-config (Interface Options) |
Safe, persistent, allows local password setup before switching to keys. |
| Headless Pi, forgot credentials | Mount SD on PC, edit cmdline.txt for init bash |
Only way to reset without re-flashing and losing data. |
Hardware Parts List and GPIO Pin Mapping
This build targets the Raspberry Pi 5 (4GB variant). The Pi 5 uses the RP1 southbridge for GPIO handling, which changes the underlying hardware addressing, but the gpiozero Python library abstracts this perfectly. Ensure your Pi 5 is powered by a 27W USB-C PD power supply to prevent brownouts when switching inductive loads via the relay.
Bill of Materials
- Board: Raspberry Pi 5 (4GB) with Active Cooler
- Storage: 64GB MicroSD Card (A2 rating, e.g., SanDisk Extreme) or 256GB NVMe via Pi 5 M.2 HAT+
- Component: 5V Single-Channel Relay Module (Optocoupler isolated, SRD-05VDC-SL-C)
- Wiring: 3x Female-to-Female Dupont jumper wires (22 AWG)
- Power: Official 27W USB-C PD Power Supply
Pin Mapping Table
The relay module requires a 5V power source to energize the coil, but the control signal (IN) is triggered by the Pi's 3.3V GPIO logic. Most modern relay modules feature an optocoupler that accepts 3.3V logic safely.
| Raspberry Pi 5 Pin | BCM GPIO Number | Relay Module Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 2 (5V Power) | N/A | VCC | Red |
| Pin 6 (Ground) | N/A | GND | Black |
| Pin 11 (GPIO 17) | GPIO 17 | IN (Signal) | Yellow |
Step-by-Step: Headless SSH Configuration
- Download Raspberry Pi Imager: Get the latest version from the official Raspberry Pi site.
- Select OS and Storage: Choose 'Raspberry Pi OS (64-bit)' (Bookworm or newer) and your target microSD/NVMe drive.
- Open Advanced Settings: Click the gear icon (or press
Ctrl+Shift+X). This is where the magic happens. - Set Hostname: Change from
raspberrypito something specific likeworkshop-relay.local. - Enable SSH & Inject Keys: Select 'Enable SSH'. Choose 'Use password authentication' ONLY if you must. For production, select 'Allow public-key authentication only' and paste your PC's
~/.ssh/id_rsa.pub(orid_ed25519.pub) into the box. - Set Custom Username: Uncheck the default user. Create a specific user (e.g.,
maker) and set a strong password as a fallback. - Flash and Boot: Write the image, insert the storage into the Pi 5, connect Ethernet (or pre-configure WiFi in the Imager), and apply power.
- Connect: From your PC terminal, run
ssh maker@workshop-relay.local.
Python Control Script: Compilable Code with Error Handling
Once logged in via SSH, install the GPIO library: sudo apt update && sudo apt install python3-gpiozero. Save the following script as relay_control.py. This script accepts command-line arguments passed directly through your SSH session.
#!/usr/bin/env python3
import sys
import logging
from gpiozero import OutputDevice
from signal import pause
# Configure logging for SSH terminal output
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
# Pin Definitions (BCM Numbering)
RELAY_PIN = 17
# Initialize Relay
# active_high=False assumes a low-level trigger relay module
# initial_value=False ensures relay is OFF on boot
try:
relay = OutputDevice(RELAY_PIN, active_high=False, initial_value=False)
except Exception as e:
logging.error(f'Failed to initialize GPIO {RELAY_PIN}: {e}')
sys.exit(1)
def control_relay(command):
try:
if command == 'on':
relay.on()
logging.info(f'Relay on GPIO {RELAY_PIN} engaged.')
elif command == 'off':
relay.off()
logging.info(f'Relay on GPIO {RELAY_PIN} disengaged.')
elif command == 'toggle':
relay.toggle()
state = 'engaged' if relay.is_active else 'disengaged'
logging.info(f'Relay toggled. Current state: {state}.')
else:
logging.error(f'Unknown command: {command}')
print_usage()
sys.exit(1)
except Exception as e:
logging.error(f'Hardware fault during relay switching: {e}')
sys.exit(1)
def print_usage():
print('Usage: python3 relay_control.py [on | off | toggle]')
if __name__ == '__main__':
if len(sys.argv) != 2:
print_usage()
sys.exit(1)
control_relay(sys.argv[1].lower())
Execution via SSH:
python3 relay_control.py on
Troubleshooting: Exact Error Strings and Ranked Causes
When headless SSH fails, it usually fails in one of two specific ways. Here is the exact decision tree for the most common errors.
Error 1: ssh: connect to host 192.168.x.x port 22: Connection refused
Meaning: Your PC reached the Pi's IP address, but the Pi actively rejected the connection on port 22. The SSH daemon is not listening.
- Cause A (Most Likely): The
sshfile was not created in the/boot/firmware/partition, or the OS hasn't finished its first-boot expansion. Fix: Wait 3 minutes after first power-on, or re-flash using Imager Advanced Settings. - Cause B: You are pinging a stale DHCP lease. The Pi got a new IP. Fix: Check your router's DHCP client list or use
ping raspberrypi.local. - Cause C: The Pi browned out during boot and halted before starting network services. Fix: Verify you are using a 27W PD supply for the Pi 5, not an old 5V/2.5A phone charger.
Error 2: Permission denied (publickey)
Meaning: The SSH daemon is running, but it rejected your cryptographic credentials. Password auth is disabled.
- Cause A (Most Likely): You are trying to log in as
pi. Thepiuser no longer exists in modern Pi OS. Fix: Use the custom username you created in the Imager (e.g.,ssh maker@IP). - Cause B: Your PC is offering the wrong key. Fix: Specify the key explicitly:
ssh -i ~/.ssh/id_ed25519 maker@IP. - Cause C: File permissions on the Pi's
.sshdirectory are too open (if you manually copied keys). Fix: Connect a monitor and runchmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys.
1. Run
ping [IP_ADDRESS] to verify network reachability.2. Verify your target username (assume
pi is dead; use your custom user).3. If local access is available, plug in a monitor and run
systemctl status ssh to check for daemon crash loops.
Extending and Simplifying the Build
Once you have stable SSH and GPIO control, you will inevitably want to scale the project. Here is how to move forward based on your end goal.
How to Extend (Scaling Up)
- Add MQTT Integration: Replace the CLI argument parser with an Eclipse Paho MQTT client. This allows Home Assistant or Node-RED to publish
workshop/relay/setmessages to the Pi, removing the need to SSH in just to flip a switch. - Implement a Web API: Wrap the
gpiozerologic in a lightweightFastAPIorFlaskserver. This is ideal if you want to build a custom mobile dashboard without dealing with broker infrastructure.
How to Simplify (Reducing Friction)
- Use Raspberry Pi Connect: If your primary reason for SSH is remote terminal access rather than automated scripting, enable Raspberry Pi Connect. It creates a secure WireGuard tunnel back to your browser, entirely bypassing local network IP hunting, port forwarding, and dynamic DNS configuration.
- Switch to an ESP32: If the Pi 5 is only running a single relay and a Python script, you are overpaying for compute. An ESP32-S3 running ESPHome costs $6, uses milliwatts of power, and integrates natively with smart home platforms without the overhead of a full Linux kernel.
For standalone networked GPIO control where Linux overhead is justified (e.g., local computer vision or heavy database logging), stick with the Pi 5 and SSH keys. For simple relay switching, migrate to an ESP32.






