There are some complications to overcome:

The chroot directory must be root owned and world-readable (sshd checks this unconditionally for security reasons).
The user’s home directory should be relative to this root because the server will attempt to go to the directory relative to the root when the user logs in.
If you want to prevent users from being able to see even the list of other users on the server, then each user will need a separate chroot directory.
The typical solution involves a complex home directory path:
/home/chroot/domain/home/domain

Note that all directories in the path leading up to the final sub-directory should be root owned and world-readable.

Steps to follow:

1. Set the home directory template in Virtualmin accordingly:

Virtualmin -> System Settings -> Virtualmin Configuration -> Defaults for new domains -> Home directory base: /home/chroot/${DOM}/home

Virtualmin -> System Settings -> Virtualmin Configuration -> Defaults for new domains -> Home subdirectory: ${DOM}

Note that both settings are required, even if ${DOM} is the default, as Virtualmin will not correctly interpolate the directory unless a manual template is set.

2. Add a custom command to handle setting up and cleaning up the chroot:

Virtualmin -> System Settings -> Virtualmin Configuration ->Actions upon server and user creation -> Command to run before making changes to a server: /home/chroot/chroot.sh

Virtualmin -> System Settings -> Virtualmin Configuration ->Actions upon server and user creation -> Command to run after making changes to a server: /home/chroot/chroot.sh

3. Create the /home/chroot/chroot.sh as follows:

#!/bin/ksh

if [ ! “$VIRTUALSERVER_PARENT” ] then
if [ “$VIRTUALSERVER_ACTION” == “CREATE_DOMAIN” ] then
if [ ! “$VIRTUALSERVER_CREATED” ] then
mkdir -p /home/chroot/$VIRTUALSERVER_DOM/home
else
echo “Setting up $VIRTUALSERVER_DOM to chroot’ed environment for sftp”

usermod -d /home/$VIRTUALSERVER_DOM $VIRTUALSERVER_USER
ln -s $VIRTUALSERVER_HOME /home

echo ” .. done”
fi
elif [ “$VIRTUALSERVER_ACTION” == “DELETE_DOMAIN” ] then
if [ “$VIRTUALSERVER_CREATED” ] then
echo “Cleaning up $VIRTUALSERVER_DOM’s chroot’ed environment”

rm -rf /home/chroot/$VIRTUALSERVER_DOM /home/$VIRTUALSERVER_DOM

echo ” .. done”
fi
fi
fi
And don’t forget to give it executable permissions.

This script:

Creates the home directory’s parent directory as Virtualmin will not do this automatically, and fail without it.
Changes the /etc/passwd home directory entry to be relative to the chroot (so it’s not the same as the real home directory!).
Adds a symbolic link from this home directory location to the real directory for convenience so that things like ~domain still work.
4. Add Virtualmin users to a secondary group that sshd can identify for SFTP-only access:

Virtualmin -> System Settings -> Server Templates -> Default Settings -> Administration user -> Add domain owners to secondary group: sftponly

Be sure to create this group.

5. Update /etc/ssh/sshd_config to set SFTP-only access for members of this group:

Subsystem sftp internal-sftp
Match Group sftponly
ChrootDirectory /home/chroot/%u
ForceCommand internal-sftp
AllowTcpForwarding no
6. Reload sshd:

>systemctl reload sshd.service

7. In the Virtualmin, go to “Limits and validation” > “FTP directory restrictions”.

Select All server or the server you want, tick the box under “Active”, “Save”