How to Configure an SFTP Chroot Jail on Ubuntu: Secure Directory Isolation for Users

Ubuntu tutorial - IT technology blog
Ubuntu tutorial - IT technology blog

Real-World Problem: Granting Upload Access While Exposing the Entire System

About six months ago, our team took on an outsourcing project for an external client. They needed an account to periodically upload backup data and image files to an Ubuntu VPS. Following common habit, a junior engineer created a standard Linux account, assigned a password, and handed it over so the client could upload files via SFTP using FileZilla.

Everything seemed fine until I checked the access logs one day. Out of curiosity, the client had opened an SSH session in their terminal and realized they could run cd /etc straight from their home directory, inspect running services via ps aux, and even read numerous sensitive configuration files across the system. Although they lacked root privileges to modify or damage anything, letting an outside party wander freely through the operating system’s file hierarchy was an unacceptable security risk.

Root Cause Analysis: How Default SFTP Works

SFTP (SSH File Transfer Protocol) runs under the hood as a subsystem of SSH (OpenSSH). When you create a standard user account on Ubuntu, the system does the following by default:

  • Assigns a valid login shell (usually /bin/bash or /bin/sh).
  • Grants read permissions to the others group across most system-level directories like /var/log, /etc, and /usr.

Consequently, when logging in with an SFTP client such as WinSCP or FileZilla, users can simply click the “Up one directory” button (..) to browse the entire root filesystem /. If they open a terminal and run ssh user@ip, they immediately get an interactive bash session with full regular-user privileges, which is why admins often implement strict controls like two-factor authentication (2FA) for SSH on production hosts.

Approaches to Secure File Sharing

When tasked with the requirement: “Allow external parties to upload files to the server without letting them view any other system data,” I weighed three potential options:

1. Plain FTP or FTPS (vsftpd)

This is the traditional approach: install vsftpd, enable local_enable, and configure chroot. However, the drawbacks are significant. You must open extra ports (port 21 alongside a passive port range), handle more complex firewall rules, and maintain another standalone daemon running on your VPS.

2. WebDAV or Object Storage Services

Setting up Nextcloud, MinIO, or uploading directly to S3-compatible storage keeps infrastructure very clean. However, it introduces unnecessary overhead for lightweight use cases, and partners accustomed to traditional FTP clients often struggle with automated upload scripts, unlike teams who leverage tools to automate data backups to Google Drive, S3, and OneDrive directly.

3. SFTP Chroot Jail with OpenSSH

Leverages the OpenSSH service already available on Ubuntu Server. It locks the user inside a designated directory (Chroot Directory) and completely revokes interactive shell access (nologin). The user can only transfer files without SSH access, and from their perspective, their assigned directory functions as the entire server’s root /. For a deeper architectural breakdown of jail environments, check out this guide on locking down SFTP chroot jails on Linux.

The Best Approach: Step-by-Step Guide to SFTP Chroot Jail on Ubuntu

I have deployed Ubuntu Server 22.04 on over 20 VPS instances, and this workflow is always my first choice whenever third parties need upload access. It requires no additional software beyond built-in OpenSSH, provides robust security, and has run with rock-solid stability in production for over six months.

Step 1: Create a Dedicated Group for SFTP Users

Group all accounts that need restricted access together (for example, sftp_users) to streamline permission management within the SSH configuration file:

sudo groupadd sftp_users

Step 2: Create a New User and Disable Shell Access

Create a new user named uploaduser. The critical step here is assigning the /usr/sbin/nologin shell (or /bin/false) to completely block interactive SSH terminal access:

sudo useradd -m -g sftp_users -s /usr/sbin/nologin uploaduser
sudo passwd uploaduser

Set a strong, secure password for this account.

Step 3: Configure the OpenSSH Subsystem

Open the SSH daemon configuration file:

sudo nano /etc/ssh/sshd_config

Locate the default Subsystem sftp line and change it to internal-sftp. Then, append the dedicated configuration block for the sftp_users group at the end of the file:

# Find and comment out the existing line if present, replace with:
Subsystem sftp internal-sftp

# Add to the end of sshd_config:
Match Group sftp_users
    ChrootDirectory /var/sftp/%u
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PasswordAuthentication yes

Parameter breakdown:

  • Match Group sftp_users: Applies the security rules below to all members of this group.
  • ChrootDirectory /var/sftp/%u: Jails the user within /var/sftp/uploaduser. The %u token dynamically resolves to the username.
  • ForceCommand internal-sftp: Forces the internal sftp handler to run immediately upon successful connection, preventing any shell spawn.
  • X11Forwarding no and AllowTcpForwarding no: Disables port forwarding and graphical forwarding to prevent the connection from being abused as a proxy.

Step 4: Set Chroot Directory Permissions (Crucial)

This is where most people get tripped up, often encountering “Connection reset by peer” or “Permission denied” errors. OpenSSH security policies strictly enforce the following rule:

The ChrootDirectory MUST be owned by root and CANNOT be writable by any other user or group.

Therefore, we structure the directories such that the outer jail directory is owned by root, with a writable subdirectory named files inside owned by uploaduser:

# Create the jail directory structure
sudo mkdir -p /var/sftp/uploaduser/files

# Ensure the jail directory is owned by root:root with 755 permissions
sudo chown root:root /var/sftp/uploaduser
sudo chmod 755 /var/sftp/uploaduser

# Grant user ownership of the upload subdirectory
sudo chown uploaduser:sftp_users /var/sftp/uploaduser/files
sudo chmod 775 /var/sftp/uploaduser/files

Step 5: Verify the Configuration and Restart SSH

Before restarting SSH, always run a syntax check to avoid locking yourself out of the server due to a typo:

sudo sshd -t

If the check returns no output or errors, proceed with restarting the SSH service, and you can also run an Ubuntu server security audit with Lynis to verify overall service hardening:

sudo systemctl restart ssh

Step 6: Verify the Setup in Practice

From your local client machine, test both connection scenarios:

1. Attempt a standard SSH login:

ssh uploaduser@your_server_ip

The server will return: This account is currently not available. or immediately terminate the connection. The user cannot access a terminal.

2. Connect via SFTP:

sftp uploaduser@your_server_ip

Connection successful! Now inspect the current directory:

sftp> pwd
Remote working directory: /
sftp> ls
files
sftp> cd ..
sftp> pwd
Remote working directory: /

No matter how many times the user runs cd .., they remain confined inside the virtual root /, seeing only the files directory for uploads. To keep a comprehensive audit trail of file operations, consider installing and configuring auditd on Ubuntu Server so all system events and file access stay fully traceable while Ubuntu system files are completely shielded from external view.

Share: