Skip to content
Français
Conteneurs & Orchestration medium

Debugging an Incus VM stuck at boot (emergency mode)

35 min de lecture

Read this page in French

incus logo

Your Incus VM shows as RUNNING in incus list, but incus exec and SSH no longer answer? When the guest agent is dead, the VM operating system never finished booting: it is most likely stuck in emergency mode. The short answer: stop going through incus exec (which needs the agent), capture the serial console with incus console, and if that is not enough, mount the VM disk from the host to read its systemd journal offline. This guide walks that diagnosis end to end, including a real case where hardening bricked the EFI boot.

What you will learn

  • Tell a dead agent from a plain SSH issue: why a RUNNING VM stays unreachable.
  • Capture the serial console of a VM that no longer answers, despite having no real TTY.
  • Mount the disk of a stopped VM from the host (ZFS, dir, qcow2) to read its journal.
  • Read the offline journalctl of a failed boot with journalctl -D.
  • Analyse a real case where a hardening sysctl broke the /boot/efi mount.
  • Put the safety nets in place: snapshots, persistent journald, a break-glass root.

Prerequisites

  • An Incus host with at least one virtual machine (not a container).
  • Root access on the host, which the disk mounting operations require.
  • Some knowledge of systemd (targets, units) and Linux partitioning.

The symptom: RUNNING but unreachable

You restart a VM, then nothing. incus list still shows it started:

Fenêtre de terminal
incus list <vm>
# +------+---------+------+------+-----------------+-----------+
# | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
# +------+---------+------+------+-----------------+-----------+
# | <vm> | RUNNING | | | VIRTUAL-MACHINE | 0 |
# +------+---------+------+------+-----------------+-----------+

Note the missing IPv4 address: the operating system never configured its network. An execution attempt confirms the diagnosis:

Fenêtre de terminal
incus exec <vm> -- whoami
# Error: VM agent isn't currently running

The RUNNING state in Incus only means QEMU is running the machine. It says nothing about the guest system. Here the agent (incus-agent) never started: the operating system stopped along the way. This is not an SSH problem, it is a boot that never completes.

Why the usual reflexes fail on a VM

On an Incus container, troubleshooting is direct. On a virtual machine, several usual commands fall flat, because they assume a working guest or a real terminal.

CommandOn a stuck VMWhy
incus exec <vm>VM agent isn't currently runningneeds the agent, so a booted OS
incus console <vm> --show-logempty or partial serial logthe serial buffer is not populated like a container log
incus info <vm> --show-logof little use for a VMno systemd boot detail
incus console <vm> in a pipeinappropriate ioctl for deviceno real TTY inside a pipe

incus exec died with the agent. --show-log, very useful for a container, does not give the internal boot journal of a VM, so another approach is needed. And piping incus console fails, because the command demands a pseudo-terminal.

Level 1: capturing the serial console

When you simply want to see the screen of the VM (the boot error message), attach the serial console. The trap: incus console demands a pseudo-terminal, so a naive redirection fails. You force a pty with script:

Fenêtre de terminal
timeout 14 script -qec 'incus console <vm>' /dev/null </dev/null 2>&1 | tail -45
  • script -qec '...' /dev/null runs the command in a pty with no file recorded (-q quiet, -e propagates the exit code, -c runs the command).
  • timeout 14 cuts after 14 seconds, enough to capture the current screen.
  • tail -45 keeps only the last lines, where the final state appears.

What you will read, depending on the failure:

You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, or "exit" to continue bootup.

or a start job that never finishes:

[*** ] A start job is running for /boot/efi (1min 30s / no limit)

or a kernel panic. To detach the interactive console cleanly, press Ctrl+a then q.

Level 2: reading the journal offline by mounting the disk

This is the right reflex. Rather than fighting a blind console, you stop the VM, mount its disk from the host and read its systemd journal calmly. The procedure depends on the storage backend.

Identify the driver first:

Fenêtre de terminal
incus storage list
# +---------+--------+-------------+---------+
# | NAME | DRIVER | DESCRIPTION | USED BY |
# +---------+--------+-------------+---------+
# | default | zfs | | 12 |
# +---------+--------+-------------+---------+

On ZFS, the VM disk is a zvol. Once the VM is stopped, that zvol sits at volmode=none: no /dev/zdN device is exposed. You expose it, read its partitions with kpartx, then mount the root read only:

Fenêtre de terminal
incus stop <vm> --force
# Identify the disk zvol (the .block suffix)
zfs list -t volume | grep <vm>
Z=<pool>/virtual-machines/<vm>.block
# Expose the zvol as a block device
sudo zfs set volmode=dev "$Z"
# kpartx creates the partition mappings (/dev/mapper/zd0p1, zd0p2 and so on)
sudo kpartx -av /dev/zd0
# Mount the root read only (p2 = ext4 root, p1 = EFI vfat)
sudo mkdir -p /mnt/vm
sudo mount -o ro /dev/mapper/zd0p2 /mnt/vm

After the diagnosis, clean up in reverse order:

Fenêtre de terminal
sudo umount /mnt/vm
sudo kpartx -d /dev/zd0
sudo zfs set volmode=none "$Z"

Once the root is mounted, read the failed boot journal. On a mounted disk the "current boot" is not necessarily the last one: list the boots, then target the offset you want.

Fenêtre de terminal
# List the recorded boots
sudo journalctl -D /mnt/vm/var/log/journal --list-boots
# IDX BOOT ID FIRST ENTRY LAST ENTRY
# -1 a1b2... 2026-06-29 09:14 2026-06-29 09:31
# 0 c3d4... 2026-06-29 09:32 2026-06-29 09:32 <- the failed boot
# Read the errors of that boot
sudo journalctl -D /mnt/vm/var/log/journal -b 0 -p err --no-pager
# Find the cause directly
sudo journalctl -D /mnt/vm/var/log/journal -b 0 --no-pager \
| grep -iE 'Dependency failed|Failed to mount|emergency'

That final grep almost always surfaces the chain of causality: a unit that fails, an unsatisfied dependency, then the switch to emergency.

Case study: hardening that bricks the EFI boot

Here is a real, reproducible case that illustrates the whole chain. The offline journal reveals this:

systemd[1]: Mounting boot-efi.mount - /boot/efi...
mount[420]: mount: /boot/efi: unknown filesystem type 'vfat'.
systemd[1]: boot-efi.mount: Mount process exited, code=exited, status=32
systemd[1]: Failed to mount boot-efi.mount - /boot/efi.
systemd[1]: Dependency failed for local-fs.target - Local File Systems.
systemd[1]: Reached target emergency.target - Emergency Mode.

The root of the problem: hardening had set kernel.modules_disabled=1 through a /etc/sysctl.d/ drop-in. That sysctl is a one-way switch: once at 1, no kernel module can be loaded any more, and it cannot go back to 0 without a reboot.

systemd applies sysctls very early at boot, through systemd-sysctl. Its ordering relative to mounting /boot/efi is not guaranteed: both converge on sysinit.target with no strict mutual ordering. If kernel.modules_disabled=1 is applied before mounting /boot/efi triggers loading the vfat module, and if that module is not already loaded, the mount fails. No vfat, no EFI partition mounted, local-fs.target fails, and the system drops into emergency mode.

The lesson: a one-way disabling sysctl such as kernel.modules_disabled must never live in a sysctl.d drop-in applied at boot. Set it late, through a systemd oneshot unit ordered After=local-fs.target (or even after the network), once every needed module is already loaded.

Recovery and safety net

Before any risky operation on a VM, take a snapshot. That is your way back:

Fenêtre de terminal
# Before a risky boot change
incus snapshot create <vm> before-hardening
# Roll back if the VM no longer boots
incus snapshot restore <vm> before-hardening

On the access side, never lock root without leaving a way back in. Emergency mode is precisely when you need it:

  • a local recovery account with a password,
  • or a documented GRUB password to edit the kernel command line at boot.

Prevention: the checklist

The best debugging is the kind you never have to do. Three reflexes avoid most of these lockups:

  1. Persistent journald from the start

    On your lab VMs, enable Storage=persistent (or create /var/log/journal/) at creation time. Without it, the failed boot journal goes up in smoke at reboot.

  2. Test boot changes on a disposable VM

    Anything touching boot (/etc/fstab, the kernel command line, a one-way sysctl) gets tested on a disposable VM protected by a snapshot, never directly on a machine that matters.

  3. Find the culprit through the trace, not through blind reboots

    Prefer a dry run of your configuration (the converged state without rebooting) to spot a dangerous change before it bricks the boot, rather than restarting at random.

Key points

  • A RUNNING VM with no agent has not finished booting: it is not an SSH problem.
  • incus exec needs the agent; on a stuck VM it returns VM agent isn't currently running.
  • For a VM, --show-log gives an often empty serial log: capture the serial console (incus console, detach with Ctrl+a q), forced into a pty through script.
  • The right reflex: stop the VM, mount its disk from the host (ZFS through volmode=dev plus kpartx, dir through losetup -fP, qcow2 through qemu-nbd) and read the journal with journalctl -D.
  • volmode=dev hides the zvol partitions, hence kpartx.
  • A one-way sysctl such as kernel.modules_disabled=1 set too early can break a mount (/boot/efi in vfat) and cause emergency mode.
  • Snapshots, persistent journald and a break-glass root are your three safety nets.

FAQ: common questions about debugging an Incus VM

Next steps

  • Securing Incus: the resource limits and isolation that stop a drifting instance taking the host with it.
  • Managing instances: the console commands this guide relies on, in their ordinary context.
  • Incus OS without a shell: a system with no shell, where diagnosis goes entirely through the serial console and the API.

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn