chroot: Changing the Root Directory to Isolate an Environment
What You'll Learn
- How
chrootswitches the root directory a process sees - How to diagnose "the command won't run inside chroot" as a shared-library problem
- A reliable system-recovery workflow from a live USB or rescue mode
- Why chroot is not a security boundary and what to use instead
Quick Summary
- chroot only changes the filesystem view. It does not isolate processes or networking.
- The command binary and its shared libraries must exist inside the new root.
- For recovery, bind-mount
/dev,/proc, and/sysbefore you chroot. - For real isolation, use containers (namespaces), not chroot.
Prerequisites
- OS: Ubuntu / Debian family (commands are identical on RHEL-family systems)
- Running
chrootrequires root privileges (CAP_SYS_CHROOT) - The
chrootcommand ships with coreutils (no extra install needed)
What Is chroot?
Conclusion: chroot changes the apparent root directory (
/) for a process and its children to a directory you specify, so that process can no longer reach files outside the new root.
Normally every process resolves paths from the system-wide root /. When you run chroot /mnt/newroot, everything under that point becomes the new / for the affected process.
$ sudo chroot /mnt/newroot
From that moment, the chrooted shell's /etc/passwd actually points to /mnt/newroot/etc/passwd. Anything outside the new root (the host's real /home, for example) can no longer be named as a path. That is why chroot is described as "isolating an environment."
A chrooted environment is conventionally called a chroot jail — the process is "jailed" and cannot see the file tree outside its new root.
Why Use chroot?
Conclusion: The three main uses are system recovery, clean build/test environments, and confining a service. Reach for it when you want to run a command against a filesystem that is independent of the running one.
Typical use cases:
| Use case | Example |
|---|---|
| System recovery | Boot a live USB, chroot into an unbootable OS, reinstall grub or reset a password |
| Clean environment | Build a package inside a pristine minimal root created with debootstrap |
| Confinement | Lock public SFTP users into their home directory (sshd ChrootDirectory) |
| Validation | Run a different distro's userland on top of the existing kernel |
chroot does not swap the kernel. Even if the new root contains different libraries and binaries, the running kernel is still the host's.
How Do You Run chroot?
Conclusion: The syntax is
chroot NEWROOT [COMMAND]. Omit the command and it launches the user's shell ($SHELLor/bin/sh) interactively. Root privileges are required.
Basic syntax:
$ sudo chroot NEWROOT [COMMAND [ARG...]]
Without a command:
# Start an interactive shell inside the new root $ sudo chroot /mnt/newroot
With an explicit command:
# Run /bin/ls inside the new root, then exit $ sudo chroot /mnt/newroot /bin/ls -l /
The path you give for COMMAND is absolute relative to the new root. chroot /mnt/newroot /bin/bash runs /mnt/newroot/bin/bash, not the host's path.
What Does a chroot Environment Need?
Conclusion: Besides the command binary itself, the shared libraries (
.sofiles) it depends on must exist inside the new root. Otherwise it fails with "No such file or directory."
This is the classic pitfall. Running chroot /mnt/newroot /bin/bash fails if /mnt/newroot/bin/bash is missing — and even when the binary is present, the shared libraries bash links against must also live inside the new root.
Find the dependencies with ldd:
$ ldd /bin/bash
linux-vdso.so.1 (0x00007fff...)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f...)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)
Copy each .so to the same path inside the new root and bash will run. A minimal jail built by hand:
$ NEWROOT=/mnt/jail
$ sudo mkdir -p $NEWROOT/{bin,lib,lib64}
$ sudo cp /bin/bash $NEWROOT/bin/
# Copy the .so files ldd reported into the matching directories
$ sudo cp /lib/x86_64-linux-gnu/{libtinfo.so.6,libc.so.6} $NEWROOT/lib/
$ sudo cp /lib64/ld-linux-x86-64.so.2 $NEWROOT/lib64/
$ sudo chroot $NEWROOT /bin/bashReal-world chroot environments are built with debootstrap (Debian/Ubuntu) or dnf --installroot (RHEL family), which lay down a complete userland with dependencies in one step. Treat the manual copy above as a way to understand the mechanism.
Recovering a System with chroot
Conclusion: To repair an unbootable OS, mount its disk, bind-mount
/dev,/proc, and/sysinto it, then chroot. This lets you operate the installed OS almost as if it had booted normally.
Steps to enter the installed OS from a live USB or rescue mode, assuming /dev/sda1 is the root partition:
# 1. Mount the target root partition $ sudo mount /dev/sda1 /mnt # 2. Bring in the virtual filesystems via bind mounts $ sudo mount --bind /dev /mnt/dev $ sudo mount --bind /proc /mnt/proc $ sudo mount --bind /sys /mnt/sys # 3. chroot into the installed OS $ sudo chroot /mnt # 4. Now operate normally (e.g. reinstall GRUB) # grub-install /dev/sda && update-grub
Without /dev, /proc, and /sys, commands that read kernel or device information — like grub-install or package operations — will fail. These bind mounts are effectively mandatory for a recovery chroot.
When finished, exit the chroot before unmounting. Getting the order wrong gives you target is busy.
# Exit the chroot first # exit $ sudo umount /mnt/sys /mnt/proc /mnt/dev $ sudo umount /mnt
Some cases also need /dev/pts (pseudo-terminals) or /run. arch-chroot (shipped by Arch Linux but usable elsewhere) is a convenient wrapper that sets up these bind mounts for you.
Is chroot a Security Boundary?
Conclusion: No. chroot only changes the filesystem view; a process with root privileges can escape it using well-known techniques. Use namespace-based containers for real isolation.
Treating chroot as a sandbox is dangerous. A process running as root inside a chroot can escape (a "chroot escape") with classic tricks such as a double chroot. This is a design limitation, not a bug.
What chroot does not isolate:
- The process table (
pssees outside processes, and you cankillthem) - Networking (the same network stack is shared)
- Users and privileges (root inside the chroot is the host's root)
- The kernel (shared)
Do not use chroot to isolate untrusted code or separate tenants. For genuine isolation, use namespaces + cgroups (containers) or virtual machines. Dropping the chroot's root to an unprivileged user (capsh --drop, etc.) mitigates the risk but is still not a complete boundary.
For safely confining unprivileged users, practical options include sshd's ChrootDirectory (SFTP-only confinement) and systemd service settings like RootDirectory= combined with PrivateDevices=.
Common Errors and Fixes
Conclusion: Most failures come down to missing shared libraries, missing bind mounts, or insufficient privileges. Read the error message to tell them apart.
chroot: failed to run command '/bin/bash': No such file or directory
If the bash binary exists but you still see this, the shared libraries (including the loader) are missing inside the new root in almost every case. Match the output of ldd /bin/bash into the new root (see the dependencies section).
$ ldd /bin/bash # Run on the host to list the .so files you need
chroot: cannot change root directory to '/mnt': Operation not permitted
You lack root privileges — add sudo. The same error appears when CAP_SYS_CHROOT has been dropped, such as inside a container.
Lots of "command not found" after chroot
Either PATH doesn't match the new root's layout, or the needed commands aren't installed. A minimal root may not even contain ls. Lay down a complete userland with debootstrap or similar.
umount reports target is busy
You're trying to unmount before leaving the chroot shell. exit the chroot first, then unmount in the order /sys, /proc, /dev, /mnt.
Copy-paste: safe recovery-chroot template
# Enter sudo mount /dev/sda1 /mnt sudo mount --bind /dev /mnt/dev sudo mount --bind /proc /mnt/proc sudo mount --bind /sys /mnt/sys sudo chroot /mnt # After your work, exit the chroot, then: sudo umount /mnt/sys /mnt/proc /mnt/dev sudo umount /mnt