Based on notes from my old wiki, refreshed as I remotely upgraded our home router from Debian 9 (stretch) to Debian 10 (buster).

Pre-upgrade

Check everything is currently up to date

Check all packages are at their current versions:

if grep ' /boot \S\+ \(ro\|\S\+,ro\)[, ]' /proc/mounts
then
  mount -o remount,rw /boot && echo "Remounted /boot rw"
fi
apt-get update
apt-get upgrade
apt-get dist-upgrade

N.B. If there's a kernel upgrade, probably worth rebooting into the new kernel at this stage.

If salt is being used to manage, check that states are currently correct (nothing to apply):

salt-call state.highstate

Remove any outdated/non-upgradable packages

Check for non-Debian and obsolete packages that are installed and decide whether to keep or remove them:

aptitude search '~i(!~ODebian)'

Check aptitude does not have any pending actions, by running aptitude in a terminal and pressing 'g'.

Check package status

The following command will show any packages which have a status of Half-Installed or Failed-Config, and those with any error status.

dpkg --audit

Check for any packages that are on hold. If there are any and they are essential for the upgrade, the upgrade will fail:

dpkg --get-selections | grep 'hold$'

Upgrade

Change the sources

Replace the "old" distribution names in /etc/apt/sources.list with the new one.

Do the upgrade

apt-get update
apt-get upgrade
apt-get dist-upgrade

At this point you have to fix any package issues that arrise during the upgrade.

For example, going from strech (9) to buster (10) I hit this bug with salt-master: #919231 - CacheDirectory/StateDirectory does not change owner/group and had to run chown -R salt:salt /var/lib/salt/pki/master /var/cache/salt/master to work-around.

Similarly, Icinga2 changed its Certificate Paths: (see: Upgrading Icinga 2)

Changed Certificate Paths

The default certificate path was changed from /etc/icinga2/pki to /var/lib/icinga2/certs.

Old Path New Path
/etc/icinga2/pki/icinga2-agent1.localdomain.crt /var/lib/icinga2/certs/icinga2-agent1.localdomain.crt
/etc/icinga2/pki/icinga2-agent1.localdomain.key /var/lib/icinga2/certs/icinga2-agent1.localdomain.key
/etc/icinga2/pki/ca.crt /var/lib/icinga2/certs/ca.crt

Post-upgrade

Reinstall new versions of any non-Debian packages that were removed pre-upgrade.

Remove any packages no longer required:

apt-get autoremove

Check for any newly-obsoleted packages:

aptitude search '~i(!~ODebian)'

Search for removed packages that still have configuration files on the system:

dpkg -l | awk '/^rc/ { print $2 }'

If you're happy with the list then these can be purged:

apt-get purge $(dpkg -l | awk '/^rc/ { print $2 }')

(optional) Find any configuration files that differ from the package maintainer's version and tidy them up:

find /etc -name '*.dpkg-*'