Cross-installing Debian
May 2026
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
What? Why?
Starting from an already up and running Linux server we show how to replace the system by a fresh Debian install without physical access to the machine. All install steps are carried out via SSH. This approach is known as cross-installation.
Possible reasons for cross-installs:
- We get a minimal installation. So we can be sure we control and understand the details.
- We want to do a fresh install with minimal downtime. Downtime will be one minute for a reboot compared to half an hour or more for a standard install procedure.
- Thirst of adventure ;-)
Prerequisites
We assume that the reader has basic Linux administration and network knowledge.
Major sources for the instructions here are:
The Debian Installation Guide has a section on cross-installation and there are a small number of tutorials, too. See How to install Debian from Debian, for instance.
Considerations and steps described here by no means cover all relevant situations and settings. They originate from setting up two test system, thus, focus is on hardware and network features available during time of testing.
Installation procedure has been tested with the following systems:
- Debian 12 for the new system, Ubuntu 22.04 LTS for the old one,
- Debian 11 for the new system and for the old one (minor modifications are in order).
We assume that the host is Linux-based and has internet access. We have root access to the machine at least via SSH. Physical access may avoid headaches, but is not necessary.
For cross-installation, we need unused disk space. Here unused means that there is some space on a disk not belonging to a partition. If you do not have enough unused space for a basic Linux installation (about 10 GB) try to shrink a file system and corresponding partition. We do not cover the details here.
If you start with a fresh host machine (no operating system), install a standard Debian and leave some disk space unpartitioned. Then follow the instructions below.
Disk partitioning
For partition management, we use logical volume management (LVM). See Debian Handbook and Debian Wiki for relevant information. LVM allows resizing and moving partitions without formatting them. We will use this feature after removing the original system to make the whole disk available to the new one.
If not already available on your system, install LVM tools via
sudo apt install lvm2
Check available disks and their partition scheme with
sudo fdisk -l
You should also look at the output of
sudo cat /etc/fstab
sudo ls -la /dev/disk/by-uuid/
to see which partitions currently are in use.
If there is no unused partition, but unpartitioned disk space, create a new partition in unpartitioned area:
sudo fdisk /dev/nvme0n1
where nvme0n1 has to be replaced by your disk’s name.
Follow the interactive partitioning process by pressing/choosing p, n, 3, default, default, w (maybe different for your situation!).
Now that we have an empty partition, we create a physical LVM volume in it:
sudo pvcreate /dev/nvme0n1p3
where nvme0n1pe is your new partitions name (check partition names with sudo fdisk -l).
With
sudo pvdisplay
we get a list of all physical LVM volumes.
Now we create a volume group vg_main:
sudo vgcreate vg_main /dev/nvme0n1p3
Display results:
sudo vgdisplay
Next, the logical volumes:
sudo lvcreate -n lv_root -L 50G vg_main
sudo lvcreate -n lv_var -L 10G vg_main
sudo lvcreate -n lv_tmp -L 10G vg_main
sudo lvcreate -n lv_home -L 100G vg_main
sudo lvcreate -n lv_swap -L 8G vg_main
sudo lvdisplay
See Debian Installation Guide for hints on how many partitions to use and on partition sizes.
Format all partitions:
sudo mkfs.ext4 /dev/vg_main/lv_root
sudo mkfs.ext4 /dev/vg_main/lv_var
sudo mkfs.ext4 /dev/vg_main/lv_tmp
sudo mkfs.ext4 /dev/vg_main/lv_home
sudo mkswap /dev/vg_main/lv_swap
sudo sync
Debootstrap
The Debian ecosystem provides debootstrap for minimal manual installations.
Install with
sudo apt install debootstrap
Before we run debootstrap we have to mount the file systems for the new system:
sudo mkdir /mnt/debinst
sudo mount /dev/vg_main/lv_root /mnt/debinst
sudo mkdir /mnt/debinst/var
sudo mount /dev/vg_main/lv_var /mnt/debinst/var
sudo mkdir /mnt/debinst/tmp
sudo mount /dev/vg_main/lv_tmp /mnt/debinst/tmp
sudo mkdir /mnt/debinst/home
sudo mount /dev/vg_main/lv_home /mnt/debinst/home
sudo swapon /dev/vg_main/lv_swap
Now run
sudo debootstrap --arch amd64 --verbose stable /mnt/debinst http://ftp.tu-chemnitz.de/debian
You may specify different architecture (amd64), different version (stable) and different mirror (http://ftp.tu-chemnitz.de/debian).
Chroot into the new system
We use chroot to proceed with the installation process until the new system is bootable and provides SSH access.
To enter the chroot environment first mount all file systems (see Debootstrap above) and then run
sudo LANG=C.UTF-8 chroot /mnt/debinst /bin/bash
The console now lives inside the new system. Run
mount none /proc -t proc
chmod 777 /tmp
inside chroot to make several important things work.
To leave the chroot environment type
exit
Almost all steps described below have to be done in the chroot environment.
Basic config files
We have to create basic config files for the new system.
Enter the chroot environment for the following steps.
We start with the file systems to mount on boot.
Write the following to /etc/fstab:
# /etc/fstab: static file system information.
#
# file system mount point type options dump pass
/dev/vg_main/lv_root / ext4 defaults 0 1
/dev/vg_main/lv_tmp /tmp ext4 rw,nosuid,nodev 0 2
/dev/vg_main/lv_var /var ext4 rw,nosuid,nodev 0 2
/dev/vg_main/lv_home /home ext4 rw,nosuid,nodev 0 2
/dev/vg_main/lv_swap none swap sw 0 0
/dev/nvme0n1p1 /boot/efi vfat umask=0077 0 1
One of many ways to do this is
nano /etc/fstab
Save and close with Ctrl+x, y, return.
Replace nvme0n1p1 by the name of your EFI partition, if you have an EFI system.
For BIOS systems, remove that line.
Initialize timer correction by creating /etc/adjtime with content
0.0 0 0.0
0
UTC
Then run
dpkg-reconfigure tzdata
To make the apt package manager work place the following in /etc/apt/sources.list:
deb http://ftp.tu-chemnitz.de/debian bookworm main contrib non-free-firmware
deb-src http://ftp.tu-chemnitz.de/debian bookworm main contrib non-free-firmware
deb http://security.debian.org/ bookworm-security main contrib non-free-firmware
deb-src http://security.debian.org/ bookworm-security main contrib non-free-firmware
(replace the mirror http://ftp.tu-chemnitz.de if it is not close to your location).
Then reload package information via
apt update
For network management we use systemd.
Create /etc/systemd/network/50-ether-static.network with content
[Match]
Name = enp1s0
[Network]
Address = 192.168.178.53/24
Gateway = 192.168.178.1
DNS = 192.168.178.1
DNS = 8.8.8.8
DNS = 8.8.4.4
Domains = ~.
Replace all values with your network information.
On the host system (not in chroot) run
ip -c route get 1.1.1.1
to get the name of the network interface used for internet connectivity. A list of all network interfaces is shown by
ip addr show
If your host system manages network with NetworkManager, run
nmcli device show enp1s0
(where enp1s0 has to be replaced by the correct interface) to get relevant information.
If the host system uses systemd for network management have a look at the files in /etc/systemd/network.
Enable systemd network management in the chroot environment via
systemctl enable systemd-networkd.service
For DNS, you may use your networks DNS server or public DNS servers provided by Google (8.8.8.8, 8.8.4.4) or Cloudflare (1.1.1.1) or others. Beware of the fact that DNS server providers see all the servers you communicate with and how often and at what time you communicate with another machine!
Create /etc/hosts with content
127.0.0.1 localhost
127.0.1.1 your_hostname
with your_hostname suitably replaced (run hostname on a host system to get your hostname).
IMPORTANT: The setup described here is for IPv4 networks. If your machine lives in a IPv6 network things (IP addresses!) will look different.
Additional packages and their configuration
It’s time to install some packages into the chroot environment not belonging to the minimal installation done by debootstrap.
Choose and install a suitable Linux kernel via
apt search linux-image
apt install linux-image-amd64
(replace amd64 with you machine’s architecture).
Warnings about local problems and errors related to logging should be ignored at this point.
We’ll solve this later on.
Some important tools:
apt install lvm2 sudo ssh systemd-resolved
Sometimes the ssh server sshd does not start properly at system boot due to improper configuration of the start-up process of systemd. To ensure sshd comes up without problems create the file (and corresponding directories) /etc/systemd/system/sshd.service.d/wait.conf with content
[Unit]
Wants=network-online.target
After=network-online.target
User account
The new system needs a user account.
We adhere to the sudo variant, that is, there will be no root user.
In the chroot environment create a user via
adduser some_username
This will be the admin account.
Some people say that we shouldn’t call an admin’s account admin to make it harder for attackers to find the account to attack.
Choose a really secure password!
Then allow sudo for the account:
usermod -a -G sudo your_username
Bootloader (GRUB) configuration
Bootloader configuration has to be done on the host system (not in chroot).
After booting into the new system, we’ll install GRUB there and hand the boot process over to the new system.
WARNING: Be careful here! Mistakes may render your systems (old and new) unbootable! GRUB configuration heavily depends on your overall setup and also on your hardware. Check every detail twice!
Edit /etc/default/grub:
- Set
GRUB_TIMEOUTto something greater than zero to allow for manual OS selection on boot. - This is only relevant with physical access to the machine and if something goes wrong during reboot.
- Add a line with
GRUB_DISABLE_OS_PROBER=false. - This tells GRUB to autodetect operating systems (now you have at least two on your machine).
Run
sudo update-grub
IMPORTANT: Read the output of update-grub.
If it tells you that os-prober won’t be executed, then something went wrong.
We need os-prober.
Perhaps there is some GRUB config file overwriting our choice GRUB_DISABLE_OS_PROBER=false.
Run
sudo grep -r "GRUB_DISABLE_OS_PROBER" /etc/
to find such config files. Then change settings there.
In principle, we could reboot now into the new system.
But, especially if we don’t have physical access to the system, we have to tell GRUB which OS to boot.
Have a look at /boot/grub/grub.cfg.
There are lines starting with menuentry and maybe also with submenu.
Either get the title of the new system’s entry or get its number.
Counting starts at 0 and a whole submenu block counts like one menuentry.
Run
sudo grub-reboot menu_entry_title_or_number
make GRUB boot the new system at next reboot.
If you need to boot a submenu item, specify it as submenu_title_or_number>submenu_entry_title_or_number.
See manpage of grub-reboot, too.
Reboot into the new system
Run
sudo reboot now
and login with your admin account created above. SSH should work, so you shouldn’t need physical access to the machine.
Check network configuration
Check network status with
networkctl list
networkctl status
networkctl status --all
Some fine-tuning
The apt package manager should work now.
Let’s update installed packages:
sudo apt update
sudo apt upgrade
Configure locales and keyboard:
sudo apt install locales console-setup
sudo dpkg-reconfigure locales
sudo dpkg-reconfigure keyboard-configuration
sudo systemctl restart console-setup.service
Have a look at the kernel logs:
sudo dmesg
If there are complaints about missing firmware, install apt-file via
sudo apt install apt-file
sudo apt-file update
and look for the package containing the missing files:
apt-file search name_of_missing_firmware_file
Install help systems:
sudo apt install man info
Disable automatic updates (optional, don’t forget to do them manually at least weekly):
sudo systemctl disable apt-daily.timer
sudo systemctl disable apt-daily-upgrade.timer
Remove debootstrap logs:
sudo rm /var/log/bootstrap.log
To guarantee logging to disk, in /etc/systemd/journald.conf change Storage=auto to Storage=persistent.
Then restart logging with
sudo systemctl restart systemd-journald
Install time synchronization via NTP:
sudo apt install systemd-timesyncd
Install GRUB
It remains to install GRUB on the new system.
WARNING: Be careful here: mistakes may render your systems (old and new) unbootable! GRUB configuration heavily depends on your overall setup and also on your hardware. Check every detail twice!
Run
sudo apt install grub-efi
or
sudo apt install grub-pc
depending on your machine’s boot procedure to install GRUB packages.
Then install GRUB to the partition already used by the old system for GRUB (the one mounted to /boot/efi in old system’s /etc/fstab):
sudo grub-install /dev/nvme0n1
Edit /etc/default/grub following the description in bootloader (GRUB) configuration, but now on the new system, and run
sudo update-grub
Finally, set GRUB’s default menu entry to boot via
sudo grub-set-default menu_entry_title_or_number_of_new
If you want to boot into the old system, run
sudo grub-reboot menu_entry_title_or_number_of_old
Test the GRUB installation with
sudo reboot now
Removing the old system
The new system should work now, but it’s a good idea to keep the old one until you are really sure that the new one works flawlessly.
To remove the old system, we take its disk partitions and add them to the new system’s home partition.
Thus, entire disk space will be available to the new system.
Find the old system’s partitions from the output of
sudo fdisk -l
sudo pvdisplay
(partitions displayed by fdisk but not by pvdisplay belong to the old system, up to the EFI partition on EFI systems).
Have a look at the output of
sudo vgdisplay
sudo lvdisplay
to find the home partitions logical volume (/dev/vg_main/lv_home if you followed instructions above).
Then create a physical volume:
sudo pvcreate /dev/nvme0n1p2
(replace nvme0n1p2 by your partition name).
Add the new physical volume to the volume group vg_main via
sudo vgextend vg_main /dev/nvme0n1p2
and extend the home volume to fill all available space:
sudo lvextend -l +100%FREE /dev/vg_main/lv_home
Finally, extend the file system to fill the whole volume:
sudo resize2fs /dev/vg_main/lv_home