The Shift to Headless: Why SSH is Mandatory

When building IoT clusters, smart home hubs, or remote environmental monitors, attaching a keyboard and monitor to every Single Board Computer (SBC) is inefficient. A robust Raspberry Pi SSH setup is the backbone of headless project management. However, the landscape of Raspberry Pi OS has changed dramatically with the release of Debian Bookworm. The legacy methods of simply dropping an empty ssh file into the boot partition and logging in with the default pi user are obsolete and inherently insecure.

In this project tutorial, we will architect a secure, modern SSH pipeline tailored for Raspberry Pi OS Bookworm. We will cover pre-boot provisioning, cryptographic key generation optimized for ARM processors, and daemon hardening using the new drop-in configuration architecture.

Phase 1: Pre-Boot Provisioning (Bookworm OS)

Security by design means never exposing a default password to the network. Before your Raspberry Pi boots for the first time, you must inject your credentials and enable the SSH daemon.

Method A: The Raspberry Pi Imager GUI

The most frictionless approach utilizes the official Raspberry Pi Imager. When selecting your OS and storage, click the gear icon (Advanced Options) or press Ctrl+Shift+X. Here, you can:

  • Set a custom hostname (e.g., pi-node-01.local).
  • Enable SSH and select Use password authentication (temporarily) or Allow public-key authentication only.
  • Configure Wi-Fi credentials for headless network attachment.

Method B: Manual SD Card Injection (userconf.txt)

If you are automating deployments via bash scripts or flashing images without the GUI, you must manually create the user. Raspberry Pi OS requires a userconf.txt file in the boot partition. This file must contain a single line formatted as username:encrypted-password.

To generate the encrypted password hash on your host machine, use OpenSSL:

echo 'MySecureP@ssw0rd' | openssl passwd -6 -stdin

Copy the resulting hash and create the file on the mounted SD card boot partition:

echo 'fluxadmin:$6$xyz...hashed_string...' > /media/boot/userconf.txt
touch /media/boot/ssh

This ensures the SSH daemon starts on boot and your custom user is immediately available. For more details on headless configuration, refer to the Raspberry Pi Configuration Guide.

Phase 2: Cryptographic Handshakes (Ed25519 Keys)

Password authentication is vulnerable to brute-force attacks and network sniffing. We will transition to key-based authentication using the Ed25519 algorithm. Unlike RSA, Ed25519 offers superior security with much smaller key sizes and faster signing operations, which reduces CPU overhead on ARM-based SBCs like the Pi Zero 2 W or Pi 4.

Expert Insight: Always use the -a flag to increase the Key Derivation Function (KDF) rounds. This makes the key more resistant to offline brute-force attacks if the private key is ever stolen from your host machine.

On your host machine, generate the keypair:

ssh-keygen -t ed25519 -a 100 -C 'fluxadmin@pi-node-01' -f ~/.ssh/pi_node_ed25519

Next, inject the public key into the Raspberry Pi. If you have temporary password access enabled, use ssh-copy-id:

ssh-copy-id -i ~/.ssh/pi_node_ed25519.pub fluxadmin@pi-node-01.local

Verify the connection using the private key:

ssh -i ~/.ssh/pi_node_ed25519 fluxadmin@pi-node-01.local

Phase 3: Hardening the Daemon (sshd_config)

With key-based authentication verified, we must lock down the SSH daemon. A critical E-E-A-T detail for modern Raspberry Pi OS (Bookworm) is the shift to drop-in configuration directories. Instead of editing the monolithic /etc/ssh/sshd_config file, best practice dictates creating a custom configuration file in /etc/ssh/sshd_config.d/.

Create a new hardening profile:

sudo nano /etc/ssh/sshd_config.d/99-hardening.conf

Populate the file with the following directives to align with OpenBSD sshd_config security standards:

# Disable password and root logins
PasswordAuthentication no
PermitRootLogin no

# Restrict authentication methods
PubkeyAuthentication yes
AuthenticationMethods publickey

# Limit connection attempts
MaxAuthTries 3
LoginGraceTime 30

# Change default port to evade basic botnet scanners
Port 2222

Restart the SSH service to apply the changes:

sudo systemctl restart ssh

To streamline your workflow, configure your host machine's SSH client to automatically apply the correct key and port. Edit your local ~/.ssh/config file:

Host pi-node-01
    HostName pi-node-01.local
    User fluxadmin
    Port 2222
    IdentityFile ~/.ssh/pi_node_ed25519
    ServerAliveInterval 60
    ServerAliveCountMax 3

This configuration not only saves keystrokes but the ServerAliveInterval directive prevents intermediate routers from dropping idle TCP connections, a common issue in smart home networks.

Phase 4: Network Perimeter Defense

Obscuring the SSH port is not a substitute for a firewall. We will configure UFW (Uncomplicated Firewall) to restrict inbound traffic. Install and configure UFW on your Pi:

sudo apt update && sudo apt install ufw -y
sudo ufw allow 2222/tcp comment 'Custom SSH'
sudo ufw allow 8123/tcp comment 'Home Assistant (Optional)'
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

This ensures that even if a vulnerability is discovered in another running service, the network perimeter remains sealed. For comprehensive remote access strategies, consult the Raspberry Pi Remote Access Documentation.

Diagnostic Matrix: SSH Failure Modes

When deploying headless nodes in enclosures or remote locations, troubleshooting via serial console is tedious. Use this diagnostic matrix to quickly resolve common Raspberry Pi SSH setup failures.

Symptom / Error Message Root Cause Analysis Resolution Protocol
Connection refused SSH daemon is not running, or UFW is blocking the port. Verify port in 99-hardening.conf. Check UFW status via serial: sudo ufw status.
Permission denied (publickey) Incorrect Linux file permissions on the Pi's .ssh directory. SSH requires strict permissions. Run: chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys.
Network is unreachable Headless Wi-Fi failed to connect; wpa_supplicant missing or misconfigured. Use Pi Imager to inject Wi-Fi creds, or manually place wpa_supplicant.conf in the boot partition.
Host key verification failed The Pi's SD card was re-flashed, changing its cryptographic fingerprint. Clear the old fingerprint from your host machine: ssh-keygen -R pi-node-01.local.

Summary

A production-grade Raspberry Pi SSH setup requires moving beyond legacy tutorials. By leveraging Bookworm's userconf.txt provisioning, adopting Ed25519 cryptography, utilizing drop-in sshd_config directories, and enforcing UFW network rules, you transform a vulnerable SBC into a hardened, reliable node. This foundation is critical before deploying containerized workloads via Docker or orchestrating fleet updates with Ansible in your smart home or IoT lab.