TL;DR:
- To upgrade Debian 12 to 13, back up the server, install all Debian 12 updates, change
bookwormtotrixiein Debian's APT sources, runsudo apt upgrade --without-new-pkgsand thensudo apt full-upgradeinside tmux, and reboot. - Regular security support for Debian 12 ended on July 12, 2026. Debian 12 LTS runs until June 30, 2028, and Debian 13 has full support until August 9, 2028.
- Before an upgrade over SSH, update openssh-server to
1:9.2p1-2+deb12u7or later. A fully updated Debian 12.15 server has1:9.2p1-2+deb12u10. - Before the reboot, pin the network interface name with a systemd
.linkfile and confirm it withudevadm test-builtin net_setup_link. A changed interface name can leave the server without network. apt full-upgraderemovesphp8.2-fpm. PHP-FPM 8.4 arrives only if thephp-fpmmetapackage is installed. The PostgreSQL 15 cluster keeps running until you runpg_upgradecluster 15 main.
Applies to: Debian 12.15 (bookworm) to Debian 13.7 (trixie) ยท apt 3.0.3 ยท PostgreSQL 17.11 ยท PHP 8.4.27 ยท checked October 2026
Debian 13 (Trixie) is the current stable release of Debian, first released on August 9, 2025, with full support until August 9, 2028 and Long Term Support until June 30, 2030. To upgrade Debian 12 to 13 on a VPS, back up the server, install every Debian 12 update, point APT at trixie, and run apt full-upgrade over SSH inside tmux. This tutorial covers the checks before the upgrade, both APT sources formats, the configuration prompts, the PostgreSQL 15 to 17 and PHP 8.2 to 8.4 moves, and the interface name pin before the reboot.
Should you upgrade Debian 12 to 13 now?
Yes, for most servers. On July 12, 2026, the Debian Long Term Support (LTS) Team took over security support for Debian 12 from the Security and Release Teams, and Debian's announcement encourages users to upgrade to Debian 13 where possible.
| Date | Event |
|---|---|
| August 9, 2025 | Debian 13.0 released |
| July 12, 2026 | Regular security support for Debian 12 ends, LTS begins |
| June 30, 2028 | Debian 12 LTS ends |
| August 9, 2028 | Full support for Debian 13 ends |
Debian 12 LTS covers amd64, arm64, i386, armhf and ppc64el, and a few packages are not covered. The debian-security-support package lists the installed packages that LTS does not cover.
What changes on a server in Debian 13
Debian 13 moves a Debian 12 server to Linux 6.12, OpenSSH 10.0 and systemd 257, and changes how /tmp and /etc/sysctl.conf work.
| Component | Debian 12 | Debian 13 |
|---|---|---|
| Linux kernel | 6.1 | 6.12 |
| OpenSSH | 9.2 | 10.0 (DSA keys removed) |
| systemd | 252 | 257 |
| APT | 2.6 | 3.0 (apt modernize-sources) |
| PHP | 8.2 | 8.4 |
| PostgreSQL | 15 | 17 |
| MariaDB | 10.11 | 11.8 |
| Nginx | 1.22 | 1.26 |
| Python 3 | 3.11 | 3.13 |
/tmp | Directory on disk | tmpfs in RAM, up to 50% of memory, after the reboot |
/etc/sysctl.conf | Read at boot | Not read; use /etc/sysctl.d/*.conf |
~/.pam_environment | Read by the SSH server | Ignored |
Chapter 5 of the release notes, Issues to be aware of for trixie, lists every known upgrade issue.
Upgrade Debian 12 to 13 in place or deploy a fresh Debian 13 server?
Debian supports an in-place upgrade from Debian 12 to 13 with APT. A fresh Debian 13 server is the alternative when the old one carries many third-party repositories or custom builds.
| Upgrade in place | Fresh Debian 13 server | |
|---|---|---|
| IP address | Stays the same | New IP, update DNS |
| Downtime | Service restarts and one reboot | Old server runs until you switch DNS |
| Configuration | Carried over | Rebuilt clean |
| Way back | Restore the backup | Keep the old server |
| Fits | Packages from the Debian archive | Many third-party repositories, custom builds |
Arct Cloud offers Debian 13 as a Debian VPS image. You can build the new server next to the old one and copy your data across with rsync and scp. Reinstalling the operating system on the existing server erases the disk. The rest of this tutorial covers the in-place upgrade.
Prerequisites
- A VPS running Debian 12 with SSH access.
- A non-root user with sudo privileges and SSH key login.
- A recent backup you can restore, where your server's location offers backups, plus a copy of your application data stored off the server. See VPS backups.
- A maintenance window. Services stop while their packages upgrade, and the server reboots at least once.
Commands run as the sudo user. Replace values written in capitals, such as YOUR_INTERFACE, with your own.
Step 1: Back up the server and record its state
Debian does not support downgrading, so the backup is your way back. The release notes name /etc, /var/lib/dpkg, /var/lib/apt/extended_states and the list of package selections as the files to keep. Archive them:
sudo tar -czf /root/bookworm-state.tar.gz /etc /var/lib/dpkg /var/lib/apt/extended_states
Save the package selections, the running services and the network interfaces to compare against later:
dpkg --get-selections '*' > ~/packages-bookworm.txt
systemctl list-units --type=service --state=running --no-legend --plain | awk '{print $1}' > ~/services-bookworm.txt
ip -br link > ~/links-bookworm.txt
The archive stays in /root, readable only by root. Copy it to your home directory, then off the server with your application data:
sudo install -m 600 -o "$USER" /root/bookworm-state.tar.gz ~/
Step 2: Install all Debian 12 updates, including OpenSSH
The upgrade assumes the latest Debian 12 point release. Install every update, plus tmux for Step 5:
sudo apt update
sudo apt full-upgrade
sudo apt install tmux
If the update installed a new linux-image package, reboot with sudo reboot and reconnect. An OpenSSH bug in older Debian 12 packages can lock you out when a remote upgrade is interrupted. Check the installed version:
dpkg-query -W openssh-server
The output looks similar to this:
openssh-server 1:9.2p1-2+deb12u10
The Debian 13 release notes ask for 1:9.2p1-2+deb12u7 or later before a remote upgrade. OpenSSH in Debian 13 also rejects DSA keys, even with the old configuration options. Find accounts that still log in with one:
sudo sh -c 'grep -ls ssh-dss /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys'
No output means no DSA keys. For each file listed, add an Ed25519 key for that account, as in How to Generate an SSH Key, and test the login before you continue.
Step 3: Check holds, sources, disk space, and the kernel
A held package that the upgrade needs makes it fail. List holds and packages in a broken state:
apt-mark showhold
sudo dpkg --audit
No output from either command means nothing is held or half-installed. Release a hold with sudo apt-mark unhold YOUR_PACKAGE. List installed packages that did not come from Debian:
apt list '?narrow(?installed, ?not(?origin(Debian)))'
Packages listed here came from third-party repositories, or Debian no longer ships them, and the upgrade may remove them. Then list your APT sources:
ls /etc/apt/sources.list.d/
The output looks similar to this:
debian.sources
Debian's own entries live in /etc/apt/sources.list (one-line format) or /etc/apt/sources.list.d/debian.sources (deb822 format). Other files usually belong to third-party repositories. Check each vendor's documentation for trixie packages. If the vendor publishes them, replace bookworm with trixie in that file in Step 4. If not, disable the repository until it does. Rename a one-line .list file:
sudo mv /etc/apt/sources.list.d/YOUR_REPO.list /etc/apt/sources.list.d/YOUR_REPO.list.disabled
In a deb822 .sources file, add the line Enabled: no instead. Remove bookworm-backports before the upgrade, as the release notes advise. When it is one word on the Suites: line of debian.sources, as in Debian's cloud images, remove only that word:
sudo sed -i 's/ bookworm-backports//' /etc/apt/sources.list.d/debian.sources
In a one-line sources.list, comment the line out:
sudo sed -i '/bookworm-backports/s/^/# /' /etc/apt/sources.list
If backports has a file of its own in /etc/apt/sources.list.d/, delete that file. Check free space:
df -h / /boot /var
If /boot is a separate partition, it needs at least 768 MB in total and about 300 MB free. Check that a kernel metapackage is installed, so apt full-upgrade pulls in the Debian 13 kernel:
dpkg -l 'linux-image*' | grep ^ii | grep -i meta
The output names a package such as linux-image-amd64 with (meta-package) in its description. No output means none is installed. Install the one that matches uname -r: sudo apt install linux-image-amd64, or linux-image-cloud-amd64 when the kernel name ends in -cloud-amd64.
If MariaDB runs on the server, stop it with sudo systemctl stop mariadb and check the journal with sudo journalctl -u mariadb -n 5 --no-pager. The last lines include /usr/sbin/mariadbd: Shutdown complete. If that line is missing, start MariaDB, wait for crash recovery, and stop it again. MariaDB 11.8 refuses to start on a data directory that 10.11 did not shut down cleanly.
Step 4: Switch the APT sources from bookworm to trixie
Replace the release codename in Debian's sources file. Use the command that matches the file your server has. If /etc/apt/sources.list holds only a comment, use Option B.
Option A: One-line /etc/apt/sources.list
sudo sed -i 's/bookworm/trixie/g' /etc/apt/sources.list
Option B: deb822 debian.sources
sudo sed -i 's/bookworm/trixie/g' /etc/apt/sources.list.d/debian.sources
grep Suites /etc/apt/sources.list.d/debian.sources
The output looks similar to this:
Suites: trixie trixie-updates
Suites: trixie-security
Switch each third-party repository you kept in the same way, in its own file. Confirm that no source still points at Debian 12:
grep -rnI bookworm /etc/apt/
No output means every source points at trixie. Files you disabled in Step 3 still show up here; any other line is a source to fix. Download the Debian 13 package lists and estimate the disk space the upgrade needs:
sudo apt update
sudo apt -o APT::Get::Trivial-Only=true full-upgrade
The output ends similar to this:
197 upgraded, 54 newly installed, 22 to remove and 0 not upgraded.
Need to get 140 MB of archives.
After this operation, 305 MB of additional disk space will be used.
E: Trivial Only specified but this is not a trivial operation.
The E: line is expected: the option stops apt before it changes anything. The download needs room on the file system that holds /var, and the additional space goes where packages install, usually the root file system. On a server with one partition, make sure it has room for both figures.
Step 5: Run the minimal upgrade, then apt full-upgrade
tmux keeps the upgrade running if your SSH connection drops. Start a session:
tmux new -s upgrade
Inside tmux, run the minimal upgrade first. It upgrades the packages that need no installs or removals. Debian recommends it because a direct full upgrade can remove packages you want to keep:
sudo apt upgrade --without-new-pkgs
Type y at the prompt, and answer the dialogs it shows as described in Step 6. If apt-listchanges is installed, it shows news about the upgraded packages in a pager after the download; press q to continue. Then run the full upgrade:
sudo apt full-upgrade
Read the removal list before you confirm. On a server that installed php8.2-fpm by name, it looks similar to this:
The following packages will be REMOVED:
libcurl4 libdb5.3 libelf1 libevent-core-2.1-7 libext2fs2 libgdbm-compat4 libgdbm6 libgnutls30 libhogweed6 libmagic1 libnettle8 libnpth0 libpsl5 libreadline8
libssh2-1 libssl3 libtirpc3 php8.2-cli php8.2-common php8.2-fpm php8.2-opcache php8.2-readline
...
74 upgraded, 54 newly installed, 22 to remove and 0 not upgraded.
Need to get 112 MB of archives.
After this operation, 300 MB of additional disk space will be used.
Do you want to continue? [Y/n]
Type y. If SSH drops, reconnect and reattach with tmux attach -t upgrade. APT writes the full terminal output to /var/log/apt/term.log.
Step 6: Answer the package and configuration file prompts
The upgrade stops for some package questions and for each configuration file you edited that the new package also changes. For files handled by dpkg, the prompt looks like this:
Configuration file '/etc/nginx/nginx.conf'
==> Modified (by you or by a script) since installation.
==> Package distributor has shipped an updated version.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** nginx.conf (Y/I/N/O/D/Z) [default=N] ?
- Configuring libc6. During the minimal upgrade, libc6 asks Do you want to upgrade glibc now? when PostgreSQL is running. Choose Yes, the default; No interrupts the upgrade. At Restart services during package upgrades without asking?, keep the default No, then choose Ok on the list of services to restart, such as
sshandcron. The dialog notes that restarting sshd should not affect existing connections. - Files you edited. Press
Dto review the difference, thenNto keep your version. dpkg saves the new version next to it asnginx.conf.dpkg-dist. /etc/ssh/sshd_config. The prompt is a menu titled Configuring openssh-server. Choose keep the local version currently installed, the highlighted default. The new version is saved as/etc/ssh/sshd_config.ucf-dist.- Upgrade PostgreSQL cluster 15/main to 17? Shown when the
postgresqlmetapackage is installed. Answer No, the default. Step 7 migrates the cluster with your applications stopped. - Files in
/etc/init.dand/etc/manpath.config. The release notes advise installing the maintainer's version. The old file is kept with a.dpkg-oldextension.
Two messages need no answer. dpkg: warning: unable to delete old directory lines come from the final /usr merge and are safe to ignore. If you edited /etc/sysctl.conf, the upgrade prints Saving as /etc/sysctl.conf.dpkg-bak; Step 9 puts those settings back.
Step 7: Upgrade the PostgreSQL cluster and PHP packages
The upgrade installs PostgreSQL 17, but your data still runs on the PostgreSQL 15 cluster. Check:
pg_lsclusters
The output looks similar to this:
Ver Cluster Port Status Owner Data directory Log file
15 main 5432 online postgres /var/lib/postgresql/15/main /var/log/postgresql/postgresql-15-main.log
If the server installed postgresql-15 by name instead of the postgresql metapackage, install PostgreSQL 17 first with sudo apt install postgresql-17. It does not create a second cluster while 15/main exists. Stop the applications that write to the database, then migrate the cluster:
sudo pg_upgradecluster 15 main
The output looks similar to this:
Upgrading cluster 15/main to 17/main ...
Stopping old cluster...
Restarting old cluster with restricted connections...
Creating new PostgreSQL cluster 17/main ...
...
Success. Please check that the upgraded cluster works. If it does,
you can remove the old cluster with
pg_dropcluster 15 main
pg_upgradecluster copies the data with pg_dump by default, which recreates every index in the new cluster and needs free space for a second copy. The 15 cluster moves to port 5433, stops and no longer starts at boot. After your applications pass their tests, remove it:
sudo pg_dropcluster 15 main
sudo apt purge postgresql-15 postgresql-client-15
For PHP, check that PHP-FPM 8.4 is installed:
systemctl status php8.4-fpm --no-pager
If the output is Unit php8.4-fpm.service could not be found., list the PHP 8.2 packages from Step 1:
grep '^php8.2-' ~/packages-bookworm.txt
The output looks similar to this:
php8.2-cli install
php8.2-common install
php8.2-fpm install
php8.2-opcache install
php8.2-readline install
Install each package under its 8.4 name. For the list above:
sudo apt install php8.4-fpm php8.4-cli php8.4-opcache php8.4-readline
php8.4-common comes in as a dependency. Removed packages keep their configuration files, so /etc/php/8.2 is still there. Compare the active settings:
diff <(grep -v '^;' /etc/php/8.2/fpm/php.ini | grep .) <(grep -v '^;' /etc/php/8.4/fpm/php.ini | grep .)
The output looks similar to this:
18,19c18,19
< memory_limit = 256M
< error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
---
> memory_limit = 128M
> error_reporting = E_ALL & ~E_DEPRECATED
...
Compare the FPM pool settings, such as pm.max_children, the same way:
diff <(grep -v '^;' /etc/php/8.2/fpm/pool.d/www.conf | grep .) <(grep -v '^;' /etc/php/8.4/fpm/pool.d/www.conf | grep .)
Copy the values you changed into the 8.4 files, keeping the listen line of the 8.4 pool, then run sudo systemctl restart php8.4-fpm. Point Nginx sites at the PHP 8.4 socket:
grep -rl "php8.2-fpm.sock" /etc/nginx/ | xargs -r sudo sed -i 's/php8.2-fpm.sock/php8.4-fpm.sock/g'
sudo nginx -t
sudo systemctl reload nginx
Step 8: Pin network interface names before the reboot
systemd 257 in Debian 13 can name some network interfaces differently from Debian 12, and a new kernel or driver can change a name too. A renamed interface can lose its network configuration at the reboot, and SSH then cannot reach the server. Do this step after apt full-upgrade and right before the reboot. List the interfaces:
ip -br link
For each interface other than lo, ask udev whether the systemd 257 naming rules give it a new name:
udevadm test-builtin net_setup_link /sys/class/net/YOUR_INTERFACE 2>/dev/null
Replace YOUR_INTERFACE with the name from ip -br link, such as ens3. The output ends with an ID_NET_NAME= line. This check covers only the systemd naming change. For an interface that udev already renamed at boot it prints the current name, and a new kernel or driver can still change the name after the reboot. On a server you reach only over SSH, pin the current name to the interface's MAC address, which ip -br link shows in its third column. Skip this for an interface named eth0: systemd advises against a .link file that sets a name the kernel may assign.
sudo mkdir -p /etc/systemd/network
sudo tee /etc/systemd/network/10-persistent-net.link > /dev/null <<'EOF'
[Match]
MACAddress=YOUR_MAC_ADDRESS
[Link]
Name=YOUR_INTERFACE
EOF
sudo update-initramfs -u
The 10- prefix sorts the file before systemd's 99-default.link, and update-initramfs copies it into the early boot image. Run the udevadm command again: ID_NET_LINK_FILE= now names 10-persistent-net.link, and ID_NET_NAME= shows the current name.
Step 9: Reboot, verify, and run apt modernize-sources
Reboot into the Debian 13 kernel:
sudo reboot
Reconnect and check the release:
cat /etc/debian_version
The output looks similar to this:
13.7
Check the kernel:
uname -r
The version starts with 6.12. If it still starts with 6.1, install the kernel metapackage from Step 3 and reboot again. Compare running services with the list from Step 1:
systemctl list-units --type=service --state=running --no-legend --plain | awk '{print $1}' > ~/services-trixie.txt
diff ~/services-bookworm.txt ~/services-trixie.txt
Lines starting with < are services that stopped running, and lines starting with > are services that run now but did not before. After Step 7, php8.4-fpm and postgresql@17-main replace php8.2-fpm and postgresql@15-main. List units that failed to start:
systemctl --failed
A clean result ends with 0 loaded units listed. Check /tmp:
findmnt -n -o SOURCE,FSTYPE /tmp
The output looks similar to this:
tmpfs tmpfs
/tmp now uses up to 50% of RAM. To set a fixed size, run sudo systemctl edit tmp.mount, add Options=mode=1777,nosuid,nodev,size=2G under a [Mount] line, and reboot. To keep /tmp on disk, run sudo systemctl mask tmp.mount and reboot. If the upgrade saved an edited /etc/sysctl.conf, find the backup:
ls /etc/sysctl.conf*
The output looks similar to this:
/etc/sysctl.conf.dpkg-bak
Move its settings to a file that Debian 13 reads, and apply them:
sudo cp /etc/sysctl.conf.dpkg-bak /etc/sysctl.d/99-local.conf
sudo sysctl --system
Convert a one-line sources.list to the deb822 format with APT 3.0:
sudo apt modernize-sources
The output looks similar to this:
The following files need modernizing:
- /etc/apt/sources.list
...
Rewrite 1 sources? [Y/n] y
Modernizing /etc/apt/sources.list...
- Writing /etc/apt/sources.list.d/debian.sources
Answer N at the prompt to preview the new file first. The old file is kept as /etc/apt/sources.list.bak. A server that already uses debian.sources gets All sources are modern. Point Signed-By at the canonical keyring name, because Debian will eventually remove the .gpg compatibility links:
sudo sed -i 's/debian-archive-keyring\.gpg/debian-archive-keyring.pgp/' /etc/apt/sources.list.d/debian.sources
sudo apt update
Last, remove packages that Debian 13 no longer needs, and purge the leftover configuration of removed packages, which includes /etc/php/8.2. Purging can also delete those packages' log files, so copy any logs you need first:
sudo apt autoremove
apt list '?obsolete'
sudo apt purge '?config-files'
Debian 13 has no upgrade path for obsolete packages and usually ends their security support a year after the release. Replace or remove the ones you still use.
Troubleshoot common upgrade errors
APT 3.0 prints Error: where APT 2.6 printed E:, so the same message can start with either.
| Symptom | Cause | Fix |
|---|---|---|
E: Could not perform immediate configuration on a package | apt cannot configure that package early in the run | sudo apt full-upgrade -o APT::Immediate-Configure=0 |
E: You don't have enough free space in /var/cache/apt/archives/. | The download does not fit on /var | sudo apt clean, then run the upgrade again. To remove packages first, switch the sources back to bookworm, run sudo apt update and sudo apt autoremove, then switch them to trixie again |
E: The repository '... trixie Release' does not have a Release file. | A third-party repository has no trixie suite yet | Disable that file as in Step 3, then run sudo apt update again |
dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. | The upgrade process was killed | sudo dpkg --configure -a, then sudo apt full-upgrade |
open terminal failed: not a terminal on tmux attach | tmux was upgraded to a new major version while the session ran | /proc/$(pgrep "tmux: server")/exe attach |
Unit php8.4-fpm.service could not be found. | php8.2-fpm was installed by name and the upgrade removed it | Install the 8.4 packages from Step 7 |
System is tainted: unmerged-bin in the journal | systemd 257 notes that /usr/bin and /usr/sbin are separate | Ignore it. Merging the directories by hand breaks later upgrades |
FAQ
Is it safe to upgrade Debian 12 to 13 over SSH?
Yes, with precautions. Update openssh-server to 1:9.2p1-2+deb12u7 or later, run the upgrade inside tmux or screen, and pin the network interface name with a .link file before the reboot. Debian advises caution on servers without out-of-band console access, so take a backup first.
How long is Debian 12 supported?
Debian 12 received regular security support until July 12, 2026. The Debian LTS team now supports it until June 30, 2028, on amd64, arm64, i386, armhf and ppc64el, and a few packages are not covered.
Can I upgrade Debian 11 directly to Debian 13?
No. Debian supports upgrades from the previous release only, and the Debian 13 release notes say skipping releases is not supported. Upgrade Debian 11 to 12 first, reboot and check your services, then upgrade to 13.
Will my network interface name change after upgrading to Debian 13?
It can. Debian lists two known cases: the i40e network driver, and firmware that exposes the ACPI _SUN object, which systemd 257 now reads. Pin the current name with a systemd .link file before the reboot, and check with udevadm test-builtin net_setup_link that the file applies.
Why is /tmp smaller after upgrading to Debian 13?
Debian 13 mounts /tmp as a tmpfs in RAM after the reboot, sized at up to 50% of memory, so a server with 1 GB RAM gets about 500 MB. Set a size with systemctl edit tmp.mount, or run systemctl mask tmp.mount and reboot to keep /tmp on disk.
Can I roll back from Debian 13 to Debian 12?
No. Debian does not support downgrading. To return to Debian 12, restore the backup you took before the upgrade, or deploy a new Debian 12 server and copy your data onto it.
Is apt full-upgrade the same as apt-get dist-upgrade?
Yes. apt full-upgrade and apt-get dist-upgrade both upgrade every package and install or remove packages when dependencies change, which a release upgrade needs. The Debian 13 release notes use apt full-upgrade.
Next steps
- How to Upgrade Ubuntu 24.04 to 26.04 LTS on a VPS
- How to Copy Files to a VPS with rsync and scp
- Debian 13 release notes: Upgrades from Debian 12
- Debian 13 release notes: Issues to be aware of for trixie
Arct Cloud Linux images, including Debian 12 and Debian 13, deploy in under 30 seconds on every plan. Compare plans.
This work is licensed under CC BY-NC-SA 4.0.