I Flashed 12 SD Cards So You Don't Have To — ℬℛℳ's Log

ℬℛℳ's Log

Dispatches from below the abstraction layer

I Flashed 12 SD Cards So You Don't Have To

2026-03-07 — raspberry-pi, linux, headless, rant

Raspberry Pi OS Trixie's headless WiFi setup is broken out of the box. Not "hard to configure" broken. Not "read the docs" broken. Silently does nothing broken.

The Setup

I got a Raspberry Pi Zero 2 W to play with.

The Pi Zero 2 W has no ethernet port and no full-size HDMI. It's a headless board by design. You write an SD card, drop in your WiFi config, plug in power, and SSH in. That's the entire workflow. That's how it's worked since Raspbian Jessie in 2015.

Except on Trixie, it doesn't work.

What I Tried

I downloaded 2025-12-04-raspios-trixie-arm64-lite.img from the official Raspberry Pi downloads page. I followed the cloud-init configuration that Trixie now uses — user-data, network-config, and meta-data files in the boot partition. I configured WiFi, created a user, enabled SSH.

The Pi booted. The green LED blinked. And then nothing. No WiFi. No mDNS. No SSH. The Pi was alive but invisible to the network.

I tried:

  • 32-bit image. Nothing.
  • 64-bit image. Nothing.
  • renderer: NetworkManager in network-config. Nothing.
  • brcmfmac.roamoff=1 brcmfmac.feature_disable=0x82000 kernel params for the known WiFi driver bug. Nothing.
  • cfg80211.ieee80211_regdom=US for regulatory domain. Nothing.
  • A completely minimal config — no USB gadget, no brcmfmac workaround, just WiFi credentials. Nothing.
  • A USB serial gadget console so I could at least see what was happening. The kernel got stuck after probing efi_pstore. The USB device node on macOS was a ghost — stale /dev/cu.usbmodem2101 from a previous device that couldn't be removed.
  • Three different SD cards. Nothing, nothing, nothing.

Twelve SD card cycles. Two CPU architectures. Hours of debugging. An arp scan of every IP on my subnet. The Pi was never on the network. Not once.

The Root Cause

I dug into the Raspberry Pi Imager source code. Specifically, how Imager configures Trixie images. And there it was:

Raspberry Pi Imager appends ds=nocloud;i=rpi-imager- to cmdline.txt.

Without this kernel parameter, cloud-init on Trixie does not detect the NoCloud datasource. It doesn't process user-data. It doesn't process network-config. It doesn't create your user. It doesn't configure WiFi. It doesn't enable SSH.

It does nothing. Silently.

There's no error. There's no fallback. There's no "hey, I found config files in the boot partition but I don't know what datasource to use." It just boots into a system with no user, no WiFi, and no way in.

The Documentation Gap

The official Raspberry Pi documentation doesn't mention ds=nocloud. The cloud-init files are documented. The file format is documented. The fact that they don't work without an undocumented kernel parameter is not.

The only way to discover this is to read the Imager source code. Or to flash 12 SD cards and then have an AI read the Imager source code for you.

This is the default image on the official downloads page. The Pi Zero 2 W is a headless board. Headless setup is the primary use case. And it's broken unless you either:

1. Use RPi Imager (which appends the magic kernel parameter) 2. Know to add ds=nocloud;i= to cmdline.txt yourself

If you download the raw image and configure it manually — the way Linux people have done for a decade — you get a brick.

The Fix: Bookworm + custom.toml

After 12 Trixie attempts, I downloaded Raspberry Pi OS Bookworm Lite and wrote one file:

# /boot/firmware/custom.toml
config_version = 1

[system]
hostname = "pi"

[user]
name = "pi"
password = "MyPassword"
password_encrypted = false

[ssh]
enabled = true
password_authentication = true

[wlan]
ssid = "MyNetwork"
password = "MyPassword"
password_encrypted = false
hidden = false
country = "US"

[locale]
keymap = "us"
timezone = "America/Los_Angeles"

One file. One boot. WiFi connected. SSH accessible. mDNS resolved. Done.

No cloud-init. No meta-data. No network-config. No ds=nocloud incantation on the kernel command line. No three-file YAML ceremony. One TOML file that does exactly what you'd expect.

The Lesson

Trixie adopted cloud-init for first-boot configuration. Cloud-init is a powerful, flexible, enterprise-grade system designed for cloud VM provisioning. It supports multiple datasources, multi-stage boot ordering, Jinja2 templating, and module dependency resolution.

None of that matters if it silently fails when a kernel parameter is missing.

The Raspberry Pi is not a cloud VM. It's a $15 board that people configure by editing text files on an SD card. The entire value proposition is simplicity. When you replace wpa_supplicant.conf with a three-file YAML system that requires an undocumented kernel parameter to function, you haven't upgraded the platform. You've added abstraction debt.

Bookworm's custom.toml is what cloud-init should have been for this use case: a single file, obvious format, no hidden dependencies, works on first boot. The Raspberry Pi Foundation already had the right answer. They just shipped the wrong one as the default.

The Scorecard

Trixie (cloud-init)Bookworm (custom.toml)
Files needed3 (user-data, network-config, meta-data)1 (custom.toml)
Hidden requirementsds=nocloud;i= on kernel cmdlineNone
Failure modeSilentN/A — it works
SD card cycles to success121
DocumentationIncompleteSelf-explanatory
FormatYAML (whitespace-sensitive)TOML (readable)

If you're setting up a Pi Zero 2 W headless in 2026, use Bookworm. Not because Trixie is bad software — it's Debian 13, it's fine — but because the first-boot configuration pipeline is broken for the most common use case of the most popular single-board computer on earth.

The Raspberry Pi Foundation shipped Trixie as the default download knowing that headless WiFi setup doesn't work without their proprietary Imager tool. For a platform built on openness and accessibility, that's a hell of a choice.

✌️☮️


Written after the twelfth SD card. Should probably have a shot of whiskey...