---
title: Debian 13
description: Step-by-step guide to install Debian 13 on a home server and set up SSH access, essential packages, and a ready-to-use base system.
---


:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}

[Debian 13 "Trixie"](https://www.debian.org/) is the base this whole guide sits on. It's a plain, boring, stable Linux, and for a homelab that's exactly the point: package versions stay frozen for the life of the release, security patches keep coming for about five years counting LTS, so the machine keeps running for years instead of needing a rebuild every few months.

The other reason is that it stays out of your way. Appliance systems like Unraid, TrueNAS or Synology's DSM put their own layer on top of Docker, and the day you need something their interface doesn't expose, you're stuck working around it. Debian is just a server: you install what you want, where you want, and nothing is hidden behind someone else's UI. It also happens to be what most self-hosted projects target first, so their docs hand you `apt` commands that work as-is, Docker publishes an official Debian repository, and any error message you paste into a search engine already has years of answers behind it. A minimal install is light enough to leave nearly all the RAM and CPU of a small N100 box to your containers.

![picture](/img/serveex/server.svg)

## Installation

::note{icon="" to="/general/linux/cli-basics"}
📋 __Prerequisite:__ everything past this point happens in a terminal, so you should be comfortable with the basics: moving around with `cd`, listing with `ls`, editing a file with `nano`, and reading what a command tells you when it fails. If any of that is new, **start with the command line basics** and come back.
::

### BIOS setup

Press :kbd{value="Del"} or :kbd{value="F2"} right after powering on to open the firmware setup (the boot screen usually says which key it is). Most machines also have a one-shot boot menu, often :kbd{value="F12"}, :kbd{value="F11"} or :kbd{value="F8"}, which lets you boot the USB installer once without touching the permanent boot order. Debian documents the general procedure in its [installation manual](https://www.debian.org/releases/forky/amd64/ch03s06.en.html), and here is what matters before you install:

- **Boot mode.** Prefer native UEFI. The important part is that the installer boots in the *same* mode you intend to run the server in, because UEFI uses GPT partitioning while legacy BIOS (and UEFI in CSM mode) uses a DOS partition table, and a mismatch installs the bootloader in the wrong place. Watch out on multi-boot machines: the default boot mode for removable devices is often not the one used for internal disks.
- **Secure Boot** can stay enabled. Debian ships a Microsoft-signed shim bootloader, so it boots fine as-is.
- **SATA mode** should be `AHCI`, not `RAID` / Intel RST, otherwise Linux may not see your drives at all. Changing this after installing another OS on the same disk will stop that OS from booting, so set it before you start.
- **Restore on AC power loss** so the server comes back by itself after an outage instead of waiting for someone to press the button. The setting lives in *Power Management*, *APM Configuration* or *Advanced* depending on the board, under a name like *Restore on AC Power Loss*, *AC Power Recovery*, *After Power Failure*, *AC Back Function* or *S0 state*. Set it to **Power On**, not *Last State*, which would leave the machine off if the outage caught it during a shutdown.
- **Wake on LAN**, if you want to power the machine up remotely instead of walking to it. Same *Power Management* menu: set *Wake on LAN*, *Power On By PCI-E/PCI* or *Resume by PCI-E Device* to **Enabled**, then disable *ErP* / *EuP Ready* and *Deep Sleep* / *Deep Sx*, which cut power to the network card once the machine is off and would keep it deaf to the magic packet. Debian also needs the card told to listen, see [Going further](#going-further).
- **Virtualization** (`VT-x` / `AMD-V`, plus `VT-d` for passthrough) costs nothing to turn on now and saves a trip back into the BIOS the day you want to run a VM. Docker itself doesn't need it on Linux.

::note
If you're dual-booting Windows, disable its *fast startup*: it leaves the filesystem in a state Linux can corrupt, and Windows Update likes to silently turn it back on.
::

### Download the ISO and write it to a USB stick

::steps{level="4"}
#### Download the netinst image

Grab the `amd64` **netinst** image from [debian.org](https://www.debian.org/download.en.html). It's around 700 MB and pulls the rest of the packages from the network during install, which is what you want on a server that's plugged into ethernet: you get current packages instead of installing from a months-old snapshot and patching afterwards. The full DVD images only make sense if the machine has no network during setup.

#### Write it with Rufus

On Windows, write it with [Rufus](https://rufus.ie/) (portable, no install needed). Plug in a USB stick of 2 GB or more, keeping in mind **it will be wiped entirely**, then:

- **Device**: your USB stick. Check the capacity twice, Rufus happily writes to the wrong drive if you let it.
- **Boot selection**: `SELECT`, then pick the Debian ISO you just downloaded.
- **Partition scheme**: this has to match the boot mode you set in the BIOS above. `GPT` for UEFI, `MBR` only if you're staying on legacy/CSM. The target system field follows automatically.
- Leave the format options at their defaults, then hit `START`. If Rufus asks how to write the image, keep the recommended *ISO Image mode*.

![Rufus configured to write the Debian ISO](/img/serveex/install/rufus.png)

_Screenshot from [this bootable USB guide on DEV Community](https://dev.to/devops2808/how-to-create-bootable-usb-installer-for-debian-12-4f66)._

Writing takes a few minutes.

#### Done !
::

### Install Debian

Boot the USB stick (one-shot boot menu from the BIOS section) and pick **Install**, the text installer. The goal here is a minimal headless server: no desktop, no graphical session, nothing but a shell reachable over SSH. The screen and keyboard you're using right now are only needed for this one install, after that the machine runs blind in a corner. The [official installation guide](https://www.debian.org/releases/trixie/amd64/ch06s03.en.html) documents every screen.

![Debian installer boot menu, Install selected](/img/serveex/install/debian-install-boot.png)

::steps{level="4"}
#### Language, country, keyboard

Nothing special. The keyboard layout is the one you're physically typing on, which is easy to get wrong if you picked English but type on AZERTY.

#### Network and hostname

A wired connection gets configured over DHCP by itself. When it asks for a **hostname**, give the machine a real name (`serveex`, `nas`...), you'll see it in every SSH prompt afterwards. The **domain** can be left empty, or set to something like `lan` if you already use one at home.

![Debian installer hostname screen](/img/serveex/install/debian-install-hostname.png)

#### Root password and user account

Leave the **root password empty**. Debian then disables the root account, installs `sudo` and puts your user in it, which is the safer default and saves you a round of setup later.

Then create your user: full name, username, password. This is the account you'll SSH into. Avoid `admin` as a username, it's reserved on Debian and the installer will reject it.

#### Clock

Confirm the timezone guessed from your country.

#### Partitioning

*Guided, use entire disk* on the system drive, then *All files in one partition*, which gives you one big `/` plus a swap partition. Separate `/home` or `/var` partitions buy you very little here and mostly guarantee that one fills up while the others sit half empty. Pick LVM only if you already know you want snapshots or to grow volumes later. Your data disks are not touched at this stage, you'll mount them afterwards.

Finish with *Finish partitioning and write changes to disk*, then confirm with *Yes*: this is the point of no return for that disk.

![Debian installer partitioning scheme, all files in one partition](/img/serveex/install/debian-install-partition.png)

::tip{icon="" to="/general/linux/filesystem"}
✨ __Tip:__ what actually lives on that one partition, and why `/srv/docker` is where this guide puts every stack, is covered in **folders and partitions**.
::

#### Mirror and surveys

Answer *No* to *Scan another installation medium?*, everything else comes from the network. For the mirror, pick any one in your country, or `deb.debian.org` which routes to a nearby one automatically, and leave the HTTP proxy field empty unless you actually have one. The popularity contest (anonymous package statistics) is yes or no, no consequence either way.

#### Software selection (tasksel)

The screen that actually decides whether your server stays minimal. Uncheck **everything**, in particular `Debian desktop environment` and `GNOME`, which are ticked by default and would drag in gigabytes of packages plus a graphical session you will never display. Keep exactly two boxes: **`SSH server`**, your only way in from now on, and **`standard system utilities`**, which the rest of this guide assumes.

::warning
Boxes are ticked and unticked with :kbd{value="Space"}, never :kbd{value="Enter"}. :kbd{value="Enter"} validates the whole screen and moves on, so pressing it on the desktop entry installs GNOME instead of removing it, and you get a graphical server you then have to strip by hand. Use :kbd{value="Tab"} to reach `Continue` once the two boxes above are the only ones checked.
::

![Debian installer software selection with only SSH server and standard system utilities checked](/img/serveex/install/debian-install-tasksel.png)

#### GRUB

Install it on the disk you just partitioned (`/dev/sda`, `/dev/nvme0n1`...), not on a partition.

![Debian installer asking which device to install the GRUB boot loader to](/img/serveex/install/debian-install-grub.png)

#### Done !
::

::note
Before rebooting, take a minute to give the server a **fixed address** in your router. Everything that comes later points at it: your SSH shortcuts, the reverse proxy, the bookmarks to each service. On a plain DHCP lease that address changes on its own eventually and all of it breaks at once.

The clean way is a DHCP reservation, which ties the address to the server's MAC address while leaving the router in charge of the addressing. See [NAT & DHCP](/general/networking/nat) for where to find it in your router's interface.
::

_Installer screenshots from [howtoforge.com's Debian minimal server guide](https://www.howtoforge.com/tutorial/debian-minimal-server/)._

### Connect over SSH

The server has no screen from now on, everything goes through SSH. These steps get you in, then make sure nobody else can be.

::steps{level="4"}
#### Connect from another machine

Remove the USB stick and reboot. The address to use is the one you reserved in the router just before, `192.168.1.42` in the examples below.

Everything from here happens from another machine on your local network, not on the server. Windows and macOS both ship an SSH client, so there's nothing to install: open **PowerShell** on Windows, or **Terminal** on macOS, and type the same command.

```bash [Terminal]
ssh username@192.168.1.42
```

The first connection asks you to confirm the server's fingerprint, which is normal, answer `yes`. It gets stored in `~/.ssh/known_hosts` and you won't be asked again.

::note
If the connection is refused, the `SSH server` box was probably left unchecked at the tasksel screen. Plug a screen back in, log in locally and run `sudo apt install openssh-server`.
::

The screen and keyboard are no longer needed. Unplug them, the machine can go live in its corner.

#### Log in with a key instead of a password

Passwords over SSH get brute-forced the moment the port is reachable from outside, and typing one on every connection gets old fast. Still on the other machine, generate a key if you don't already have one:

```bash [Terminal]
ssh-keygen -t ed25519
```

Press :kbd{value="Enter"} to accept the default path, and set a passphrase (it protects the key file itself, your system will remember it after the first unlock). Then copy the public half to the server. Windows has no `ssh-copy-id`, so it pushes the key over the connection instead:

::code-group
```bash [macOS]
ssh-copy-id username@192.168.1.42
```

```bash [Windows]
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh username@192.168.1.42 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
```

```bash [Linux]
ssh-copy-id username@192.168.1.42
```
::

It asks for your password one final time. Reconnect to check that it no longer does:

```bash [Terminal]
ssh username@192.168.1.42
```

#### Close the door behind you

Once the key works, turn off password logins and direct root access. On the server:

```bash [Terminal]
sudo nano /etc/ssh/sshd_config.d/hardening.conf
```

```properties [hardening.conf]
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
```

A file in `sshd_config.d/` is read on top of the main config, so your changes survive a package upgrade rewriting `/etc/ssh/sshd_config`. Apply it:

```bash [Terminal]
sudo systemctl restart ssh
```

::warning
Keep your current SSH session open while you test. Open a **second** terminal and connect again: if the key stopped working, the still-open session is your way back in to fix the config. Close it before you've checked and a typo locks you out of your own server, leaving the screen and keyboard as the only way back.
::

::note
The door is now closed for every other machine too, including the next one you'll want to connect from. To let a new one in, set `PasswordAuthentication yes` back in `hardening.conf`, restart SSH, run the two key steps above from that machine, then set it to `no` again and restart SSH one last time.
::

#### Done !
::

### Wake the server up remotely

A machine that runs 24/7 for two hours of actual use burns power, spins fans and wears drives for nothing. Wake on LAN lets you shut it down properly when you're done and bring it back in a few seconds without walking to it: the network card stays powered in standby, listening for one specific broadcast (the *magic packet*) carrying the server's MAC address, and switches the machine on when it sees it. Handy for a backup target you only need at night, or a media server nobody watches during the day.

Two conditions before you start: the machine has to be wired to ethernet, WiFi cards almost never support this, and the packet has to be sent from the same local network, since a broadcast doesn't cross a router. The BIOS side was covered in [BIOS setup](#bios-setup), here is the Debian side.

::steps{level="4"}
#### Find the interface and its MAC address

```bash [Terminal]
ip -br link
```

You get something like `enp1s0  UP  aa:bb:cc:dd:ee:ff`. Keep both: the interface name for the commands below, the MAC address for the machine that will send the packet.

#### Check the card supports it

```bash [Terminal]
sudo apt install ethtool
sudo ethtool enp1s0 | grep -i wake
```

The answer looks like `Supports Wake-on: pumbg` then `Wake-on: d`. The letter that matters is **g**, for magic packet. If the *Supports* line doesn't have it, the card can't do it and there's nothing to configure. `Wake-on: d` simply means disabled, which the next step fixes.

#### Turn it on

```bash [Terminal]
sudo ethtool -s enp1s0 wol g
```

Run the check again, `Wake-on` should now be `g`. This setting is reset at every boot, so it needs to be reapplied automatically.

#### Make it survive reboots

```bash [Terminal]
sudo nano /etc/systemd/system/wol.service
```

```ini [wol.service]
[Unit]
Description=Enable Wake on LAN

[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -s enp1s0 wol g

[Install]
WantedBy=basic.target
```

```bash [Terminal]
sudo systemctl daemon-reload
sudo systemctl enable --now wol.service
```

#### Wake it up

Shut the server down with `sudo poweroff`, then send the magic packet from another machine on the network. On macOS and Linux, the `wakeonlan` package does it in one command:

```bash [Terminal]
wakeonlan aa:bb:cc:dd:ee:ff
```

Windows has no built-in sender, so the simplest route there is a phone app: any of the free *Wake on LAN* apps takes the MAC address and works the same way. The server should start within a couple of seconds.

#### Done !
::

::note
Waking it from outside your home is another story, and going through a VPN doesn't help if that VPN runs on the server itself: the tunnel is down for as long as the machine is off. The way around it is to forward a UDP port on the router (7 or 9, the usual Wake on LAN ports) to the server, then send the packet to your public address from an app that handles it, [WolOn](https://wolon.app/) for instance. The [NAT rule](/general/networking/nat) is a plain one, `UDP 9` from the outside to `192.168.1.42:9` on the inside. The router still has to point that IP at the right MAC address while the machine is off, which is why some of them expose a static ARP entry, or a Wake on LAN button of their own that saves you the port forward entirely. Worth checking your router first.
::

### Keep it up to date

Debian doesn't update itself. Every month or so, or whenever you think about it, four commands over SSH:

::steps{level="4"}
#### Refresh the package lists

```bash [Terminal]
sudo apt update
```

Nothing is installed at this point, `apt` only asks the mirrors what's available and tells you how many packages are behind.

#### Apply the updates

```bash [Terminal]
sudo apt full-upgrade
```

`full-upgrade` is preferred over plain `upgrade` because it accepts removing a package when that's what it takes to move another one forward, which does happen on a server that lives for years. Read the summary before answering yes, it lists exactly what gets removed.

#### Clean up behind them

```bash [Terminal]
sudo apt autoremove --purge
```

Every kernel update leaves the previous one installed, and `/boot` is a small partition that eventually fills up and breaks the next upgrade. Do this every single time, not once in a while. `--purge` also drops the config files of the packages being removed.

#### Reboot if the kernel moved

```bash [Terminal]
sudo reboot
```

A kernel or libc update only takes effect after a restart. Everything else applies immediately, so this is only needed when the upgrade touched one of those, and it's worth planning for a moment when nothing depends on the machine.

#### Done !
::

::tip
If you don't need to watch what's going on, the first three steps fit on one line, `&&` stopping the chain as soon as one of them fails:

```bash [Terminal]
sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove --purge -y
```

`-y` answers yes to every question, including the day an upgrade proposes to remove something you would rather have kept, so keep it for routine rounds. Append `&& sudo reboot` to get the restart out of the way too.
::

For security patches without having to think about it, `sudo apt install unattended-upgrades` then `sudo dpkg-reconfigure -plow unattended-upgrades` applies them on its own every night. Note that all of this only covers the system: your containers are updated separately, from Dockge.

### Going further

- [Everything About Remote Console Access (SSH)](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys)
- Optional - [UPS Client in Case of Power Outage](https://www.sindastra.de/p/2078/how-to-connect-linux-server-to-synology-ups-server) / [also here](https://www.reddit.com/r/synology/comments/gtkjam/use_synology_nas_as_ups_server_to_safely_power/)

::tip{icon="" to="/general/linux/handy-tools"}
✨ __Tip:__ a handful of terminal tools worth adding on top of a minimal install, `btop`, `duf`, `ufw` and a few more, are covered in **handy CLI tools**.
::
