Skip to content

Upgrading Debian 12 to Debian 13 (trixie)

Christophe TREMBLAY-GUILLOUX
Christophe TREMBLAY-GUILLOUXLinux systems engineer

Upgrading Debian 12 to Debian 13 is usually not complicated if you already have a clean, fully mastered Debian 12 installation. In this note (which I use myself) we go through the main migration steps.

If you want to maximise stability a little further, I advise waiting for version 13.1, which comes out shortly after version 13.0, on a Saturday.

Every command is run as root, through the server's KVM console or over SSH. No pointless sudo, become root.

Why migrate to Debian 13 (without rushing)

Section titled “Why migrate to Debian 13 (without rushing)”

You use a Debian server to host WordPress, for example.

WordPress needs PHP, and is built for the PHP version most used at the time. You therefore have to keep up with the releases, or you will be stuck on updates sooner or later.

Debian brings a performance improvement (new scheduler, http/3, and so on).

You will find more and more online documentation referring to Debian 13 and less and less to Debian 12. To be able to rely on it and stay in step, you have to follow. The same goes for those still resisting systemd in place of initrd.

Abracadabra!

  • The last, lastb and lastlog commands are gone. Check your scripts if you use them.

  • Critical stop of your MariaDB server, with no way to restart it: check that your MariaDB server starts correctly and without errors before upgrading Debian, because repair is impossible once the MariaDB version has changed.

  • If you have external (unofficial) repositories, expect them to be disabled, and your specific packages to be downgraded or removed.

An upgrade can go wrong, destroy data, or leave the server unbootable.

I back up servers with BackupPC, RCLONE and snapshots (yes, all three). Databases must be dumped to SQL before any file backup.

Check that your system is properly backed up.

That the backups are held off site.

Take a snapshot as well if it is a virtual machine.

Set aside time to repair in case of trouble.

Be able to reboot in rescue mode.

Your server is normally already up to date, since you are conscientious. If not, it absolutely has to be before you start:

bash
apt update
apt upgrade
reboot # if needed

If you use Puppet, stop it. Its manifests will have to be reviewed for the new system version.

bash
systemctl stop puppet

If you have installed unofficial packages, they have to go. You will put them back later, if they still work.

To find those packages, use these commands:

bash
apt-get -s purge '?and(?installed,?not(?any-version(?origin(Debian))),?not(?name(puppet-agent)))'

If the list of packages to purge looks right, apply it:

bash
apt-get purge -y '?and(?installed,?not(?any-version(?origin(Debian))),?not(?name(puppet-agent)))'

Remove the puppet package without purging it:

bash
apt remove puppet-agent

An obsolete package is still installed but no longer exists in the repositories. Debian drops security support for it about a year after trixie's release.

Look at the list first:

bash
apt list '?obsolete'

Read it before purging: a package you installed yourself from an external source shows up here too.

bash
apt purge '?obsolete'

Check which configuration sample files are to be deleted.

bash
find /etc -name '*.dpkg-*' -o -name '*.ucf-*' -o -name '*.merge-error'

Then delete them for real if everything looks fine:

bash
find /etc \( -name '*.dpkg-*' -o -name '*.ucf-*' -o -name '*.merge-error' \) -type f -delete

Check the file or the directory /etc/apt/preferences or /etc/apt/preferences.d

They pin package versions. Check what needs doing, but these files most likely have to be disabled.

No package may be left in an unfinished installation state or any other:

bash
dpkg --audit

A held package is kept at its current version and refuses to move up. The list must be empty before the migration, otherwise the upgrade stops on a dependency conflict that is hard to read.

bash
apt-mark showhold

Release the hold on whatever comes up, unless you have a precise reason to keep it:

bash
apt-mark unhold <package>

In /etc/apt/sources.list or /etc/apt/sources.list.d/*, replace bookworm with trixie, and you should end up with:

deb http://deb.debian.org/debian trixie main contrib non-free non-free-firmware
deb http://deb.debian.org/debian trixie-updates main contrib non-free non-free-firmware
deb http://security.debian.org trixie-security main contrib non-free non-free-firmware

IMPORTANT: the name to use is deb.debian.org, which automatically points to the nearest mirror.

Or move into the directory and replace bookworm with trixie in a single command:

bash
sed -i 's/bookworm/trixie/g' *.list

Debian 13 expects its sources in /etc/apt/sources.list.d/debian.sources, in deb822 format: one stanza per repository, one field per line. The historical one-line format still works, but a .sources file has no .list extension, so the sed command above leaves it untouched. Check that the directory holds none before concluding your sources are up to date.

The same repositories as above, in deb822:

/etc/apt/sources.list.d/debian.sources:

Types: deb
URIs: https://deb.debian.org/debian
Suites: trixie trixie-updates
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: https://security.debian.org/debian-security
Suites: trixie-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

A given repository must be declared only once. If you create this file, empty or delete the old /etc/apt/sources.list.

bash
apt update
bash
apt -o APT::Get::Trivial-Only=true full-upgrade
[ ... ]
XXX upgraded, XXX newly installed, XXX to remove and XXX not upgraded.
Need to get xx.xMB of archives.
After this operation, AAAMB of additional disk space will be used.

If there is not enough room, this message appears:

E: You don't have enough free space in /var/cache/apt/archives/.
bash
apt clean
apt --purge autoremove

The kernel metapackage (linux-image-amd64 on a standard 64-bit server) automatically follows the kernel versions. Without it, the upgrade leaves the old kernel in place and the server reboots on it.

bash
dpkg -l 'linux-image*' | grep ^ii | grep -i meta

If the command returns nothing, install the metapackage matching your architecture before upgrading:

bash
apt install linux-image-amd64

The two commands that follow take a long time. Run straight from an SSH session, a network drop interrupts them mid-work and leaves the system half migrated. A screen or tmux session keeps running on the server even if your connection dies.

bash
apt install tmux
tmux new -s upgrade

After a drop, reconnect over SSH and take back control:

bash
tmux attach -t upgrade

From the KVM console this is not needed.

This step upgrades the system packages that neither add new dependencies on new packages nor remove any:

bash
DEBIAN_FRONTEND=noninteractive UCF_FORCE_CONFFOLD=1 NEEDRESTART_MODE=a apt -o Apt::Get::Assume-Yes=true -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade --without-new-pkgs
bash
DEBIAN_FRONTEND=noninteractive UCF_FORCE_CONFFOLD=1 NEEDRESTART_MODE=a apt -o Apt::Get::Assume-Yes=true -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" full-upgrade
apt --purge autoremove

If all went well, reboot the system.

If anything went wrong, analyse the situation before rebooting.