Bootable ssh-ready Raspberry Pi SD cards

Using cloud-init to produce bootable ssh-ready Raspberry Pi SD cards

Raspberry Pi OS Trixie images released on November 24, 2025 and later support cloud-init, making it easy to configure bootable SD cards for your personal needs.

This is super convenient but it comes with a security trade-off: network-config and user-data live on the SD card's FAT boot partition, so anyone with access to the card or the machine used to prepare it can read them easily.

Here I demonstrate how to use cloud-init to set up Wi-Fi and SSH, so that your Pi is ready to use remotely without having to connect it to a monitor and keyboard first. Because the boot partition remains mounted at /boot/firmware after boot, these files are still present on the running Pi. Treat any secrets placed there, especially Wi-Fi credentials, as exposed.

This example also grants passwordless sudo to the initial user so that we do not need to store a local password hash on the boot partition. That is another trade-off: if this user account is compromised, root access is immediate. For a personal Pi on a trusted network that may be acceptable, but on shared or Internet-exposed systems you should revisit this after first boot.

After flashing the Pi OS image, copy the following to /network-config on the boot filesystem:

# The following config sets up Wi-Fi and/or ethernet access

network:
  # See https://netplan.readthedocs.io/en/latest/netplan-yaml/
  version: 2

  # NetworkManager is the default network manager on Raspberry Pi OS
  renderer: NetworkManager

  wifis:
    wlan0:
      # Resolve IP dynamically via DHCP
      dhcp4: true

      # Set the Wi-Fi country code for legal channel/power limits
      regulatory-domain: "US"

      access-points:
        # SSID
        "<SSID>":

          # password
          password: "<WIFI-PASSWORD>"

  ethernets:
    eth0:
      # Resolve IP dynamically via DHCP
      dhcp4: true

and the following to /user-data, also on the boot filesystem:

#cloud-config
# The line above is a required magic header

# Hostname, but to actually access this locally, we need to set up mDNS (see
# below) and connect via <HOSTNAME>.local
hostname: <HOSTNAME>

# Updates /etc/hosts to match the hostname
manage_etc_hosts: true

users:
  # name is the username that we use to ssh
  - name: <USERNAME>

    # display name
    gecos: <DISPLAY-NAME>

    # login shell
    shell: /bin/bash

    # public keys allowed to SSH in
    ssh_authorized_keys:
      - ssh-ed25519 <SSH-PUBLIC-KEY> <EMAIL>

    # Allow this user to run any command via sudo without a password prompt
    sudo: ALL=(ALL) NOPASSWD:ALL

    # Lock the account password so login is SSH-key-only
    lock_passwd: true

# `false` disables SSH password authentication, so logins are key-only
ssh_pwauth: false

# enables the SSH server
enable_ssh: true

packages:
  # Sets up mDNS so we can connect via <HOSTNAME>.local
  - avahi-daemon

I'll explore more secure alternatives in a future post.


If you enjoyed this post, please let me know on Twitter or Bluesky.

Posted April 12, 2026.

Tags: #linux