For some reason an user might want to limit a process to access the whole / . Using chroot you can run a process in a 'jailled' environment.
That article will explain how to isolate an user for them to been chrooted when loggin using ssh.
Adjust the following commands regarding your OS. For example, using Alpine GNU/Linux, bash is located ni /bin/ instead of /usr/bin/
We'll offer to the user random_user_name an access to bash, ls and chronyc.
ldd man page explains that for security reason, do not use ldd on an untrusted executable, use: objdump -p /path/to/program | grep NEEDED; instead.
ldd $( which bash)
linux-vdso.so.1 (0x00007f31c3573000)
libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f31c33dc000)
libc.so.6 => /lib64/libc.so.6 (0x00007f31c3203000)
/lib64/ld-linux-x86-64.so.2 (0x00007f31c3575000)
ldd $( which ls | grep '/')
linux-vdso.so.1 (0x00007fe78234f000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fe7822ee000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007fe7822e1000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe782108000)
libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fe782068000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe782351000)
ldd $( which chronyc)
linux-vdso.so.1 (0x00007fe364d95000)
libm.so.6 => /lib64/libm.so.6 (0x00007fe364cc7000)
libgnutls.so.30 => /lib64/libgnutls.so.30 (0x00007fe364800000)
libedit.so.0 => /lib64/libedit.so.0 (0x00007fe364c8c000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe364627000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe364d97000)
libp11-kit.so.0 => /lib64/libp11-kit.so.0 (0x00007fe364b26000)
libidn2.so.0 => /lib64/libidn2.so.0 (0x00007fe364605000)
libunistring.so.5 => /lib64/libunistring.so.5 (0x00007fe364457000)
libtasn1.so.6 => /lib64/libtasn1.so.6 (0x00007fe364441000)
libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007fe364414000)
libffi.so.8 => /lib64/libffi.so.8 (0x00007fe364403000)
ldd permits to identify the shared libraries used by the programs to run.
Find the folder to create, here it will be only /lib64, but you might need some subdirectory as well (ex: lib/x86_64)
ldd $(which bash) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
ldd $(which ls | grep '/') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
ldd $(which chronyc) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
export CHROOT_PATH=/chroot
mkdir -p $CHROOT_PATH/{bin,etc,lib64} #create every requiered directory found using ldd + bin, etc
mkdir -p $CHROOT_PATH/dev/pts/
Copy the executable file to the $CHROOT_PATH//bin and the configuration file to its linked $CHROOT_PATH/etc if exists.
for ELFNAME in {ls,bash,chronyc}
do
cp -n $(which $ELFNAME | grep '/' | tr -d '\t') $CHROOT_PATH/bin
for i in $(ldd $(which $ELFNAME | grep '/' | tr -d '\t') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'); do cp -n --parent $i $CHROOT_PATH; done
done
To chroot, as root run chroot destination_path program_to_run
chroot $CHROOT_PATH /bin/bash
Remember that executables are located at bin, to launch a program add the full chrooted path /bin/program_name
To exit from the chroot enter exit or ctrl+d
exit
To chroot an user login via ssh
Create the minimum file set that are mandatory to create a session
export CHROOT_USERNAME=<changeme to username>
cd $CHROOT_PATH/dev
mknod -m 666 null c 1 3
mknod -m 666 tty c 5 0
mknod -m 666 zero c 1 5
mknod -m 666 random c 1 8
cd pts
mknod -m 666 0 c 5 0
Append to the /etc/ssh/sshd_config file the line
echo "Match User $CHROOT_USERNAME" >> /etc/ssh/sshd_config
echo "ChrootDirectory $CHROOT_PATH" >> /etc/ssh/sshd_config
systemctl restart sshd #Restart the service according to your distribution
$ ssh testuser@57.129.106.133 -p 1234
Last login: Thu Dec 18 20:36:01 2025 from x.y.z.w
-bash-5.2$ /bin/ls /
bin dev etc lib64 usr
-bash-5.2$ chronyc
-bash: chronyc: command not found
-bash-5.2$ /bin/chronyc
chrony version 4.6.1
Copyright (C) 1997-2003, 2007, 2009-2024 Richard P. Curnow and others
chrony comes with ABSOLUTELY NO WARRANTY. This is free software, and
you are welcome to redistribute it under certain conditions. See the
GNU General Public License version 2 for details.
Cannot read termcap database;
using dumb terminal settings.
chronyc>
export CHROOT_USERNAME=<changeme to username> # please set the username you want to chroot
export CHROOT_PATH=/chroot
for names_file in {ls,bash,chronyc} # Change here the list of programs
do
for list_libs in $(ldd $(which $(echo $names_file)) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/')
do
mkdir -p $CHROOT_PATH/$(python3 -c "u=str('$(echo $list_libs)').split('/');print('/'.join(u[:-1]).lstrip('/'))")
done
done
mkdir -p $CHROOT_PATH/{bin,etc}
mkdir -p $CHROOT_PATH/dev/pts/
cd $CHROOT_PATH/dev
mknod -m 666 null c 1 3
mknod -m 666 tty c 5 0
mknod -m 666 zero c 1 5
mknod -m 666 random c 1 8
cd pts
mknod -m 666 0 c 5 0
for ELFNAME in {ls,bash,chronyc} # Change here the list of programs
do
cp -n $(which $ELFNAME | grep '/' | tr -d '\t') $CHROOT_PATH/bin
for i in $(ldd $(which $ELFNAME | grep '/' | tr -d '\t') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'); do cp -n --parent $i $CHROOT_PATH; done
done
echo "Match User $CHROOT_USERNAME" >> /etc/ssh/sshd_config
echo "ChrootDirectory $CHROOT_PATH" >> /etc/ssh/sshd_config
systemctl restart $(ss -antp | grep ssh | head -n 1 | awk '{ print $6}' | cut -d '"' -f 2)