Cross-installing Debian

May 2026

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

This post is about installing the rather old Debian 12. Most of its content originates from the Ananke project's documentation, where information on cross-installing has been removed recently. There's only little information available on the internet about cross-install techniques. Thus, the text might still be a valuable source for some people. Most of the material should apply to current Debian versions and other Linux distros, too.

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:

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:

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:

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