systemd-nspawn jail / chroot

This was done on Debian 13, but should work on any modern systemd distro. The main advantage here is that you can enable per-user containers / jails for SSH and rsync.

First let’s create a minimal install with debootstrap

debootstrap --variant=minbase trixie /var/lib/machines/jail http://deb.debian.org/debian

We’ll create the nspawn config next in /etc/systemd/nspawn/jail.nspawn

[Exec]
Boot=yes

[Files]
BindReadOnly=/Videos

Reload systemd and enable the container, making sure we install systemd

systemctl daemon-reload
systemctl enable --now [email protected]
systemd-nspawn -D /var/lib/machines/jail /bin/bash
# apt-get install systemd dbus



Create a minimal wrapper, mostly so we can preserve rsync functionality. We’re using /usr/local/bin/nspawn-ssh-wrapper

#!/bin/bash
set -euo pipefail

MACHINE="jail"
USER="jail"

CMD="${SSH_ORIGINAL_COMMAND:-}"

# Find the container leader PID (host PID of container init)
LEADER="$(/usr/bin/machinectl show "$MACHINE" -p Leader --value)"

if [ -z "$LEADER" ] || [ "$LEADER" = "0" ]; then
  echo "Container $MACHINE not running" >&2
  exit 1
fi

if [ -z "$CMD" ]; then
  # Interactive: machinectl shell is fine (banner doesn't matter)
  exec /usr/bin/machinectl shell "${USER}@${MACHINE}"
else
  # Non-interactive (rsync/scp): MUST be banner-free.
  # Enter namespaces and run the SSH_ORIGINAL_COMMAND as the container user.
  exec /usr/bin/nsenter -t "$LEADER" -a \
    /usr/sbin/runuser -u "$USER" -- /bin/sh -c "$CMD"
fi

In your sshd_config

Match User jail
    ForceCommand /usr/bin/sudo -n --preserve-env=SSH_ORIGINAL_COMMAND /usr/local/bin/nspawn-ssh-wrapper
    PermitTTY yes
    X11Forwarding no
    AllowTcpForwarding no

And in /etc/sudoers

Defaults:jail env_keep += "SSH_ORIGINAL_COMMAND"
jail ALL=(root) NOPASSWD: /usr/local/bin/nspawn-ssh-wrapper

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.