Matt White

Matt White


Matt White


| blog
| categories
| tags
| rss

Raspberry Pi Headless on Bullseye

I’ve been on a crusade to upgrade my half dozen Raspberry Pi’s to the most recent Raspberry Pi OS version. It’s more of a pain to setup headless than it used to be. Here’s my notes on the process.

All this assumes you’ll be using either wifi or ethernet with your Raspberry Pi.

What to Download

Raspberry Pi OS Lite

Download one of the Raspberry Pi OS Lite images on the official downloads page. Only download the 64-bit version if your Raspberry Pi is compatible (e.g. Pi Zero W’s and Pi Zero v1.3’s don’t work with 64 bit).

Compatibility for Raspberry Pi OS Lite

You may need to decompress the OS image with xz. If you don’t already have xz, you can install it via:

# Arch
sudo pacman -Sy xz

# Debian
sudo apt install xz-utils


xz -d 2022-04-04-raspios-bullseye-armhf-lite.img.xz

Image Burning Software

I prefer balenaEtcher to actually burn the image.

Download balenaEtcher from the official site. Windows and OSX have their own installers. On linux, you can just unzip and run the AppImage.


With Etcher and the Pi OS image at hand, insert a micro SD card and burn the image.

It’s not generally this fast.

Remember which device it is so you can mount it later; for me, it’s /dev/sdf. Etcher is usually able to guess pretty well which devices you probably want, but be careful not to pick the wrong one.

Mount the Micro SD Card

If your system automatically mounts devices, you can probably just unplug and replug the card after flashing. You’ll need to update the paths in the rest of this guide if you go with that.

You can use lsblk to look at the currently mounted devices, where they’re mounted, and other info. This can help you narrow down which device or path you’re looking for.

I’m using /dev/sdf, as mentioned above. To manually mount:

# Make the mount point
sudo mkdir -p /media/microsd/boot

# Mount the partition
sudo mount /dev/sdf1 /media/microsd/boot/

We’re only mounting boot because it’s the only partition we need.

Setup Wifi

This is only necessary if you aren’t using ethernet for your Pi.

Create a wpa_supplicant.conf file in the boot drive. Use sudo and your favorite editor to create /media/microsd/boot/wpa_supplicant.conf:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev

       ssid="YOUR WIFI'S NAME HERE"

Update your network info and country as-needed.

Add a User

Since Bullseye, the default pi user is no longer available. For Raspberry Pi OS desktop, it’ll force you to make a user on first boot; with headless that’s not an option.

To setup a user that you can login with, we need to add one to boot/userconf. Note the spots for your desired password and desired username.

sudo sh -c 'echo "YOUR-PASSWORD" | \
openssl passwd -6 -stdin | \
xargs -I {} echo "YOUR-USERNAME:{}" > /media/microsd/boot/userconf'

Enable SSH Access

sudo touch /media/microsd/boot/ssh

SSH Over USB Configuration (Optional)

Source: Getting WIFI and USB serial port to work on Pi0W

Open the command line config file /media/microsd/boot/cmdline.txt and add: modules-load=dwc2,g_cdc after rootwait.

Append to the config file:

sudo sh -c 'echo "dtoverlay=dwc2,dr_mode=peripheral" >> /media/microsd/boot/config.txt'

The USB connection won’t work until the gadget service is running. After your first boot, you’ll need to enable login via the gadget serial port (covered below).


sudo umount /media/microsd/boot

First Boot

Now you can remove the micro SD card from your system and place it in the Raspberry Pi. If you wanted to use ethernet, plug that in. Turn on the system and wait for it to boot.

SSH into the pi using the credentials you setup in userconf. Most of the time you can use raspberrypi.local to access the Pi.

# Clear out any old host information
ssh-keygen -R raspberrypi.local

# Connect to the default hostname
ssh YOUR-USERNAME@raspberrypi.local

With a successful connection, we can run updates on the Pi, itself.

sudo apt update
sudo apt full-upgrade

And you can run through raspi-config.

sudo raspi-config

The configurations I always update are:

  • System > Hostname
  • Localisation > Locale
  • Localisation > Timezone

Loading times have been cut out - locale takes awhile to generate.

After reboot, you should be in a pretty good state. Most people can stop here.

If you updated the hostname, you’ll need to use your-hostname.local instead of raspberrypi.local for the remainder of ssh commands.

Enable SSH Over USB Service (Optional)

If you configured SSH over USB, the connection won’t work until the gadget service is running. After your first boot, enable login via the gadget serial port on the Raspberry Pi:

sudo systemctl enable --now getty@ttyGS0.service

When you connect the Raspberry Pi over USB, usually there’s a specific USB port you have to use.

USB Port on a Raspbery Pi Zero W

SSH Hardening and Uploading Keys (Optional)

Unless you really care about security, you can skip this. If you lose your key, you lose your access to the Pi’s SSH interface.

It’s best to avoid password authentication over SSH and opt for public key authentication, instead.

If you don’t have an existing key, you can generate one (on your local system) as follows:

# If you choose to enter a password, you'll be prompted for it on each connection
ssh-keygen -t rsa -b 4096 -C "your-email" -f ~/.ssh/your-key-name.pem

Uploading an SSH key to the Raspberry Pi:

ssh-copy-id -i ~/.ssh/your-key-name.pem YOUR-USERNAME@raspberrypi.local

Don’t be afraid to use ssh-copy-id -f to force it if ssh-copy-id thinks the key is already there.

Test that your key works:

ssh -i ~/.ssh/your-key-name.pem YOUR-USERNAME@raspberrypi.local

You should NOT be prompted for the Raspberry Pi’s password unless there’s a problem with the key setup. If you set an SSH key password, you will be prompted for that.

Only proceed if the key is working. It’s better to use password authentication than to lock yourself out.

If the key’s working, you can make SSH-ing easier by adding your Pi’s info to ~/.ssh/config:

Host your-hostname
  IdentityFile ~/.ssh/your-key-name.pem
  User your-username
  Hostname your-hostname.local

Now you should be able to connect just by running ssh your-hostname.

Basic Hardening

Open the sshd config with some terminal editor:

sudo nano /etc/ssh/sshd_config

Most of the settings we’re looking for are already in the sshd config; many of them are commented out. Here are the key settings I usually update/add:

LoginGraceTime 1m
PermitRootLogin no
StrictModes yes
PasswordAuthentication no
PermitEmptyPasswords no
UsePAM no
Protocol 2

Now restart sshd on the Raspberry Pi to pick up the new settings.

sudo systemctl restart sshd
Leave your existing SSH session open until you’ve tested that you can still connect. If something goes wrong, you can roll back the changes with the existing session.

You should no longer be able to login to the Raspberry Pi using a password; the key you generated should be the only way to do it.

The new SSH settings rejecting a

Debugging Issues With Setup

Check out the LED blink error codes for Raspberry Pi’s to help debug the issue.

For example, my Pi Zero W was giving 7 short blinks over and over; it couldn’t detect the kernel because I’d accidentally used the incompatible 64-bit version of Raspberry Pi OS Lite.

ssh: Could not resolve hostname: Name or service not known

1st check: Is your Raspberry Pi properly booted or is it exhibiting a blink sequence?

2nd check: Is your Raspberry Pi connected to your network? It’s worth double checking your ethernet cable or the wpa_supplicant.conf file from earlier.

Scanning for the Raspberry Pi

If your Pi is booted and connected, your system may be unable to resolve raspberrypi.local. You can use nmap to find it manually.

This will look for devices with port 22 open and provide the SSH version. As of writing, Raspberry Pi OS should have Raspbian in the SSH version name, so it’s easy to filter on.

# Scan for open SSH ports
nmap -p22 --open -sV | \
# Filter it down to 'Raspbian' results
grep Raspbian -B 4

Example output:

Scanning for the Raspberry Pi with nmap

You probably won’t have this many results; ideally you’ll just have 1.

Try out these IP addresses with SSH. In my case, I needed

ssh matt@

You can use the IP address in place of the hostname.

A little less terrible every day.