Installing Debian completely headless over SSH
The old 2012 Mac Mini that was my faithful homeserver, spinning away and protecting my network and anti-cloud philosophy day after day, finally reached the end of its life. Sad times.
I spent a few days searching for a replacement and, thanks to recommendations from a couple friends, finally settled on the Beelink SER5 mini PC.
My plan was pretty straightforward: install Debian, connect it to my network, and manage everything over SSH, preferably without once having to plug in a monitor and keyboard and pair of headphones.
I didn't want to bother with these peripherals for accessibility reasons (the state of accessibility on Linux is complex enough to deserve many blogs dedicated to the topic). Also I'm not someone that tolerates a GUI particularly well without getting annoyed, and why pass up the chance for some tinkering on day one?
Turns out, you can set up Debian headless over SSH relatively easily. Debian has supported this for years.
I wrote this post to document what I did, because I couldn't find a clear set of steps that just worked for me. My hope is that someone else that wants to do the same will come across it and be able to skip over the headaches that confused me and added like thirty minutes to my install. Maybe our friendly AI companions will scrape it and be able to guide someone through too.
After I did this, I was told about FAI.me (thanks Jonathan), which lets you build your own Debian installation ISO online. I haven't tested it out myself. If you aren't interested in building your own image from within a terminal based on a blog post, you might want to give it a try. Please note however that it doesn't seem to allow you to install Debian without a desktop environment.
My approach
Debian's installer includes a component called network-console, which allows you to connect to the installation environment using SSH. Ordinarily, you'd have to navigate the installer locally to enable this functionality, defeating the purpose of a completely headless installation.
Fortunately, Debian also supports a concept called preseeding, which allows you to provide answers to installer questions ahead of time. By embedding a preseed configuration into the installer and modifying its bootloader configuration, we can create a USB drive that:
- Boots directly into the Debian installer.
- Configures the Ethernet interface using DHCP.
- Starts an SSH server automatically.
- Waits for us to connect remotely and complete the installation.
The installation itself still happens interactively. We're simply moving that interaction from the local display to an SSH session on another computer.
One important caveat before we begin: it is still on you to figure out how to make your machine boot from the USB drive. Many will do this out of the box. Some might require you to muck around in the BIOS or UEFI settings, which requires a monitor, at least in the very beginning.
My setup fortunately worked without me needing to once enter the BIOS.
If you've written your ISO to the flash drive, plugged it in, tried to boot, and are back in your old operating system, you will have to look into changing the boot order first. For my blind and visually impaired readers, I have had great luck using ChatGPT and Gemini live to tell me what options are available and the one that I am focused on.
What you'll need
For this setup, I used the following. Alternatives are provided where applicable.
- A Beelink SER5 mini PC. (anything that can run Debian should work)
- A Windows computer with WSL installed. You really don't need Windows or WSL though, anything *nix (including macOS) should work.
- A USB flash drive.
- An Ethernet connection to my home network. (instructions for configuring your preseed file to use WiFi can be found at the bottom of this guide)
- The Debian 13 amd64 network installation ISO.
- Rufus for writing the customized image to the USB drive. (use dd on *nix)
1. Download Debian and prepare
First, download the Debian 13 amd64 netinst ISO from Debian's website.
Make sure you're downloading the amd64 version if you're following this guide on a typical Intel or AMD computer. If you're using another architecture, you'll need to adjust the installer paths and bootloader configuration accordingly.
Next, we need a few command-line utilities to modify the ISO:
xorriso, for extracting files from the ISO and rebuilding it.cpio, for adding our configuration to the installer.gzip, for decompressing and recompressing the installer image.
On Debian, Ubuntu, or another Debian-based distribution, including WSL, run:
sudo apt update
sudo apt install -y xorriso cpio gzip
On Fedora:
sudo dnf install xorriso cpio gzip
On Arch Linux:
sudo pacman -S xorriso cpio gzip
On macOS, you can install the necessary utilities through Homebrew:
brew install xorriso cpio
macOS already includes gzip. Homebrew installs GNU cpio as gcpio, which we'll account for shortly.
Now, create a working directory:
mkdir -p ~/debian-headless
cd ~/debian-headless
Copy your downloaded Debian ISO into this directory. On Linux and macOS, assuming it's in your Downloads folder, something like this should work:
cp ~/Downloads/debian-13*-amd64-netinst.iso .
If you're using WSL like I was, you can access the Windows filesystem under /mnt/c. For example:
cp /mnt/c/Users/YOUR_USERNAME/Downloads/debian-13*-amd64-netinst.iso .
Replace YOUR_USERNAME with your Windows username.
That's the extent of the WSL-specific preparation. Everything else will be done using the same commands, regardless of which operating system you're working with.
2. Create the preseed configuration
The first thing we need to do is tell Debian to start its SSH installer automatically.
Create a file called preseed.cfg:
nano preseed.cfg
Paste the following:
### Language and keyboard
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us
### Networking
d-i netcfg/choose_interface select auto
d-i netcfg/disable_autoconfig boolean false
d-i netcfg/get_hostname string beelink
d-i netcfg/get_domain string local
### Enable installation over SSH
d-i anna/choose_modules string network-console
### Temporary installer SSH password
d-i network-console/password password CHANGE_THIS_PASSWORD
d-i network-console/password-again password CHANGE_THIS_PASSWORD
### Debian package repositories
d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
There are a couple things you'll want to customize.
First, replace CHANGE_THIS_PASSWORD in both places with a password of your choosing. This is the password you'll use to connect to the installer over SSH. It is temporary and completely separate from the password you'll set up for your actual Debian installation later.
You can also change the hostname from beelink to whatever you want to call your server.
The networking section instructs Debian to configure the network automatically using DHCP. Assuming you have an Ethernet cable connected to your router, the machine should receive an IP address without any interaction on your part.
The anna/choose_modules line means load the network-console, which starts the SSH server during installation.
Finally, we supply the temporary SSH credentials and tell Debian which package mirror to use.
A quick security note: Your installer password is being stored in plaintext inside the ISO we're creating. Don't reuse a password from another account, and don't distribute the resulting ISO without removing or changing it to something you'll never use.
Save the file and exit your editor.
3. Extract the installer files
Now it's time to start messing with the Debian ISO. The fun part, right?
There are two files we're interested in:
initrd.gz: The initial RAM disk, which contains the environment Debian uses when the installer first starts.grub.cfg: The GRUB configuration file, which controls the boot menu on UEFI systems.
First, let's identify the ISO we downloaded:
ISO=$(find . -maxdepth 1 -name 'debian-13*-amd64-netinst.iso' -print -quit)
You can verify that the variable contains the correct filename:
echo "$ISO"
Next, extract the initial RAM disk:
xorriso -osirrox on \
-indev "$ISO" \
-extract /install.amd/initrd.gz initrd.gz
And the GRUB configuration:
xorriso -osirrox on \
-indev "$ISO" \
-extract /boot/grub/grub.cfg grub.cfg
At this point, our working directory should contain four files:
- The original Debian ISO.
preseed.cfg.initrd.gz.grub.cfg.
Do a quick ls to list the current directory and make sure everything is as it should be.
Now, we're going to modify the last two files and put them back into the ISO.
4. Embed the preseed configuration
The Debian installer can automatically load a file named preseed.cfg from its initial RAM disk.
This is exactly what we want. By embedding our configuration there, Debian will know to configure networking and start SSH before we ever have to interact with it.
First, decompress the RAM disk:
gzip -dc initrd.gz > initrd
Next, append our preseed file.
On Linux and WSL, run:
printf 'preseed.cfg\n' | cpio \
-H newc \
-o \
-A \
-F initrd
On macOS, if you installed GNU cpio through Homebrew, use gcpio instead:
printf 'preseed.cfg\n' | gcpio \
-H newc \
-o \
-A \
-F initrd
Now compress the modified RAM disk:
gzip -c initrd > initrd-custom.gz
That's it. Our custom installer now has everything it needs to configure SSH automatically.
Well OK, almost. We still need to make it boot without asking us what to do.
5. Configure automatic boot
By default, Debian's installation ISO presents a boot menu with several options.
We don't want to deal with a boot menu. So let's add a custom GRUB entry and make it the default.
First, back up the original configuration:
cp grub.cfg grub.cfg.original
Then append our custom boot entry:
cat >> grub.cfg <<'EOF'
# Headless installation configuration
set default="Headless SSH Installer"
set timeout_style=menu
set timeout=5
menuentry "Headless SSH Installer" {
linux /install.amd/vmlinuz auto=true priority=critical ---
initrd /install.amd/initrd.gz
}
EOF
set defaulttells GRUB which entry to boot automatically.set timeout=5gives the boot menu five seconds before selecting the default option.auto=trueenables Debian's automated installation behavior.priority=criticalreduces the number of installer questions asked before we can connect.
The linux and initrd lines tell GRUB to launch Debian's text-based installer using the initial RAM disk we just modified.
We're intentionally not automating the entire installation. We just want Debian to configure the network, start SSH, and let us take over from there. Unless you want to automate installations across a large number of machines, I'd recommend working through the installer manually because it changes frequently and you don't want to forget anything important.
One thing to keep in mind is that these instructions modify the UEFI boot configuration. If you're using an older machine that boots through legacy BIOS instead of UEFI, you'll need to modify its corresponding boot configuration as well.
6. Rebuild the ISO
Now that we've modified the necessary files, we need to put everything back together.
Run:
xorriso \
-indev "$ISO" \
-outdev debian-headless.iso \
-map initrd-custom.gz /install.amd/initrd.gz \
-map grub.cfg /boot/grub/grub.cfg \
-boot_image any replay
This takes our original ISO, replaces the two files we modified, and creates a new ISO called debian-headless.iso.
The -boot_image any replay argument tells xorriso to preserve the original ISO's boot configuration when creating the new image. Without it, we could end up with an ISO containing all the right files that just... doesn't boot.
Once the command finishes, verify that the new ISO exists:
ls -lh debian-headless.iso
You can also verify that our GRUB changes made it into the new image:
xorriso -osirrox on \
-indev debian-headless.iso \
-extract /boot/grub/grub.cfg grub-check.cfg
Then:
grep -A 5 'Headless SSH Installer' grub-check.cfg
You should see the custom boot entry we created earlier.
If you do, we're ready to write the ISO to a USB drive.
7. Write the ISO to a USB drive
How you accomplish this depends on your operating system. Any utility capable of writing a bootable disk image should work.
Be careful here. Writing the ISO will erase the destination USB drive. If you select the wrong disk, you could wipe something considerably more important, like your system disk. If you were copy-pasting commands until now, fair enough, but please make sure you know what you're doing here, and don't blame me if you don't pay attention and end up losing data.
Windows
I used Rufus.
If you're working in WSL, you'll first need to copy the ISO somewhere accessible from Windows:
cp debian-headless.iso /mnt/c/Users/YOUR_USERNAME/Downloads/
Open Rufus, select your USB drive, and choose debian-headless.iso as the boot image.
If Rufus asks whether you'd like to write the image in ISO mode or DD mode, choose DD mode.
Wait for it to finish, then safely eject the USB drive.
Linux
On Linux, you can write the image directly using dd.
First, identify your USB drive:
lsblk -o NAME,SIZE,MODEL,TRAN
Make absolutely sure you've identified the correct device. Again, you want the USB flash drive. The easiest way to know is to unplug your flash drive and run this command again. The disk that doesn't show up with the flash drive unplugged is the one that you want to use here.
Then run:
sudo dd if=debian-headless.iso of=/dev/sdX bs=4M status=progress conv=fsync
Replace /dev/sdX with your actual USB device.
macOS
On macOS, you can use dd as well.
First, identify the USB drive:
diskutil list
Once you've identified the correct disk, unmount it:
diskutil unmountDisk /dev/diskN
Then write the ISO:
sudo dd if=debian-headless.iso of=/dev/rdiskN bs=4m
Replace N with the disk number you identified earlier.
The rdisk device provides raw disk access and is generally faster for this kind of operation.
When the write finishes, eject the disk:
diskutil eject /dev/diskN
At this point, regardless of which operating system you used, you should have a USB drive containing our customized Debian installer. You can navigate to it and check.
8. Boot the machine
Now for the really really fun part.
Plug the USB drive into the computer you're installing Debian on, connect an Ethernet cable, and power it on. Assuming the machine boots from USB successfully:
- GRUB will load our custom boot entry.
- The Debian installer will start.
- The installer will load our preseed configuration.
- Networking will be configured automatically.
- The SSH server will start.
At this point, the machine should be waiting for us to connect.
Of course, we need to figure out its IP address first.
The easiest way is to check your router's DHCP client list. Depending on your router, this might be called "Connected Devices," "DHCP Clients," or something similar. Look for the hostname you configured earlier. In my case, that was beelink.
Alternatively, you can scan your network for devices listening on port 22. If you have Nmap installed, try:
nmap -p 22 --open 192.168.1.0/24
Replace the subnet with whatever your network uses, yours might be "192.168.0.0/24".
Obviously, this will also find any other machines running SSH, so you'll need to identify which one is your new server.
9. Connect to the installer over SSH
Once you've found the machine's IP address, open a terminal on your other computer and connect:
ssh installer@192.168.1.125
Replace 192.168.1.125 with the actual address.
The username is installer. This is important, because it's different from the username you'll create for your Debian installation later.
When prompted, enter the temporary password you configured in preseed.cfg.
You may also be asked to accept the server's SSH host key if this is your first connection.
After authenticating, you'll be presented with Debian's network-console interface, which includes an option to start the installer.
Select it, and you're off to the races. You now have a fully interactive Debian installer running over SSH!
The installer itself uses a text-based interface. It's kinda weird pressing tab and shift+tab to navigate and space to check things, but I'd take it over the alternative any day. You will probably need to navigate by character using your screen reader's virtual review commands to figure out what is currently selected. If anything goes wrong and you can't figure a way back, restart the installation by restarting the PC.
You'll be asked about partitioning, user accounts, package selection, and a few other things.
Software selection
I do want to highlight this screen in particular.
Debian will offer several software collections, including desktop environments.
For a minimal headless server, select:
- SSH server
- Standard system utilities
Leave the desktop environments unselected.
There's no point installing GNOME, KDE, or another graphical desktop when you intend to administer the machine entirely through SSH.
Finally, allow Debian to install GRUB onto the internal drive and finish the installation.
When the installer completes, remove the USB drive and reboot.
10. Connect to your new Debian server
After the reboot, the temporary SSH server used by the installer will be gone. Instead, your newly installed Debian system should start its permanent OpenSSH service.
It is possible that the machine may have received a different IP address. Nmap or check your router's DHCP client list again to check.
Then connect using the account you created during installation:
ssh YOUR_USERNAME@192.168.1.125
Replace the username and IP address with your own.
Once you're connected, you can verify that everything is working:
cat /etc/os-release
To check networking:
ip -br address
And to verify that SSH is running:
systemctl status ssh
Limitations
There are some limitations to this approach.
- The machine has to boot from USB. If your firmware insists on starting the existing operating system, you'll need to change the boot order. There's no clever trick in our custom ISO that can override the firmware's decision about which device to boot.
- Ethernet makes life considerably easier. You could adapt this process for Wi-Fi (see the following section), but automatically configuring wireless networking introduces complications that aren't worth dealing with if you have a wired connection available.
- SSH is only available once the operating system has booted far enough to start networking. If your machine gets stuck in the BIOS, fails to boot, or otherwise encounters a problem before SSH starts, you'll need another way to troubleshoot it.
- If you want to be particularly cautious, you can also test your customized ISO inside a virtual machine before attempting to boot it on physical hardware. A virtual machine configured for UEFI booting should let you verify that the installer starts and becomes accessible over SSH.
Optional: Installing over Wi-Fi instead of Ethernet
If you can't connect your machine to Ethernet, you can also configure the installer to connect to Wi-Fi automatically. This requires a few additional lines in your preseed.cfg file.
Replace the existing networking section with:
### Networking
d-i netcfg/choose_interface select auto
d-i netcfg/disable_autoconfig boolean false
d-i netcfg/wireless_essid string YOUR_WIFI_NAME
d-i netcfg/wireless_security_type select wpa
d-i netcfg/wireless_wpa string YOUR_WIFI_PASSWORD
d-i netcfg/get_hostname string beelink
d-i netcfg/get_domain string local
Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your network's SSID and password. Also replace the hostname as you did above.
If your computer has both Ethernet and Wi-Fi interfaces, I'd recommend replacing auto in netcfg/choose_interface with the name of your wireless interface, such as wlp2s0. Otherwise, Debian may select the Ethernet interface instead.
A few things to be aware of:
- The installer needs to recognize your wireless adapter and have the necessary firmware available. Debian 13 includes firmware for many common wireless chipsets, but support isn't universal.
- This configuration is intended for WPA/WPA2-Personal networks. WPA3-only networks or enterprise authentication are more complex and outside the scope of this guide.
- You will need to make sure Wi-Fi is configured in the installed Debian system before rebooting. Otherwise, you could finish the installation successfully and lose SSH access when the machine restarts.