Secure SFTP Server Setup on VPS: OpenSSH + Chroot
SFTP (SSH File Transfer Protocol) is a file transfer protocol that runs securely over SSH — far more secure than traditional FTP because all data is encrypted. You don't need to install a separate daemon; instead, you leverage OpenSSH that's already running on your VPS and configure a chroot jail to restrict users to only designated directories.
How SFTP Chroot Works
When users connect via SFTP with chroot enabled, they are "jailed" in a specific directory and cannot navigate or escape beyond it. This is ideal for:
- Developers who need to upload web files but must not access other parts of the server
- Clients requiring automated file exchange
- Remote backup destinations from other machines
Create SFTP User with Chroot
# 1. Create group for SFTP users
groupadd sftpusers
# 2. Create user with SFTP access only (no shell login)
useradd -g sftpusers -d /sftp/webdeploy -s /sbin/nologin webdeploy
# 3. Set password (or use SSH key instead)
passwd webdeploy
# 4. Create chroot directory (root must own it with 755 permissions only)
mkdir -p /sftp/webdeploy/www
chown root:root /sftp/webdeploy
chmod 755 /sftp/webdeploy
# Subdirectory where user can write
chown webdeploy:sftpusers /sftp/webdeploy/www
chmod 755 /sftp/webdeploy/www
Configure sshd_config
nano /etc/ssh/sshd_config
# Add this block at the end of the file
Match Group sftpusers
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
PasswordAuthentication yes
# Check syntax
sshd -t
# Reload (no need to restart, which would drop existing connections)
systemctl reload sshd
Use SSH Key Instead of Password (Recommended)
# On client machine, generate key pair
ssh-keygen -t ed25519 -f ~/.ssh/sftp_webdeploy -N ""
# Place public key on server
mkdir -p /sftp/webdeploy/.ssh
cat ~/.ssh/sftp_webdeploy.pub >> /sftp/webdeploy/.ssh/authorized_keys
chown -R webdeploy:sftpusers /sftp/webdeploy/.ssh
chmod 700 /sftp/webdeploy/.ssh
chmod 600 /sftp/webdeploy/.ssh/authorized_keys
/sftp/webdeploy/.ssh/ because OpenSSH looks relative to the chroot root, not /home/webdeploy/.ssh/ — also set AuthorizedKeysFile .ssh/authorized_keys %h/.ssh/authorized_keys in sshd_config.
# Add to sshd_config (before Match block)
AuthorizedKeysFile .ssh/authorized_keys %h/.ssh/authorized_keys
Test SFTP Connection
# Test with password
sftp webdeploy@YOUR_VPS_IP
# Test with SSH key
sftp -i ~/.ssh/sftp_webdeploy webdeploy@YOUR_VPS_IP
# Commands in SFTP shell
sftp> ls
sftp> cd www
sftp> put localfile.html
sftp> get remotefile.html
sftp> bye
# Test that user cannot escape chroot
sftp> cd ..
sftp> pwd
# Should show / (chroot root, not the real root)
Add Multiple SFTP Users Batch
#!/bin/bash
# Create SFTP users in batch
create_sftp_user() {
local USERNAME="$1"
local PASSWORD="$2"
useradd -g sftpusers -d "/sftp/${USERNAME}" -s /sbin/nologin "$USERNAME"
echo "${USERNAME}:${PASSWORD}" | chpasswd
mkdir -p "/sftp/${USERNAME}/upload"
chown root:root "/sftp/${USERNAME}"
chmod 755 "/sftp/${USERNAME}"
chown "${USERNAME}:sftpusers" "/sftp/${USERNAME}/upload"
chmod 755 "/sftp/${USERNAME}/upload"
echo "Created SFTP user: $USERNAME"
}
create_sftp_user "client_a" "SecurePass1!"
create_sftp_user "client_b" "SecurePass2!"
Monitor SFTP Activity
# View SFTP user logins
grep "subsystem request for sftp" /var/log/auth.log
# Current connection count
ss -tnp | grep ':22'
# Recently uploaded files
find /sftp -newer /tmp/.lastcheck -type f -ls
Set Up Fail2ban for SFTP
# Create jail for SFTP (uses same rule as sshd)
nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
systemctl reload fail2ban
Properly configured SFTP with chroot + SSH key authentication is a production-grade file deployment method — no need to rely on legacy FTP with plaintext data or password-based authentication vulnerable to brute force.
Verify SFTP is Working Correctly
# Test SFTP connection (from client)
sftp -i ~/.ssh/sftp_webdeploy webdeploy@YOUR_VPS_IP
# Verify chroot works — should not escape directory
sftp> pwd
sftp> cd ..
sftp> pwd
# Both commands should show / (chroot root, not the real root)
# View connection logs
grep "sftp" /var/log/auth.log | tail -20
# Verify sshd_config syntax
sshd -t && echo "Config OK"
sftp -v (verbose) to see debug logs during connection — shows every step including authentication failure reasons.
Common Issues & Troubleshooting
Error: "subsystem request for sftp failed on channel 0"
Usually caused by the chroot directory not being owned by root:root or permissions not being 755, both required by OpenSSH:
# Check ownership of chroot directory
ls -la /sftp/
# Must be: drwxr-xr-x root root /sftp/webdeploy
# Fix it
chown root:root /sftp/webdeploy
chmod 755 /sftp/webdeploy
systemctl reload sshd
Error: "Permission denied (publickey,password)" even with correct key
In a chroot environment, authorized_keys must be in a special location, not /home/user/.ssh/:
# Check authorized_keys location
ls -la /sftp/webdeploy/.ssh/authorized_keys
# Must exist
# Check permissions
stat /sftp/webdeploy/.ssh/authorized_keys
# Must be: 600 (rw-------)
# Verify AuthorizedKeysFile in sshd_config
grep AuthorizedKeysFile /etc/ssh/sshd_config
Error: SFTP login succeeds but file upload fails ("Permission denied")
The upload directory must be owned by the login user, not root:
# Check upload directory ownership
ls -la /sftp/webdeploy/
# www/ must be owned by webdeploy:sftpusers
# Fix it
chown webdeploy:sftpusers /sftp/webdeploy/www
chmod 755 /sftp/webdeploy/www
Error: "Bad owner or modes for /sftp/webdeploy/.ssh"
OpenSSH requires .ssh directory to be owned by the user with 700 permissions only:
chown webdeploy:sftpusers /sftp/webdeploy/.ssh
chmod 700 /sftp/webdeploy/.ssh
chmod 600 /sftp/webdeploy/.ssh/authorized_keys
systemctl reload sshd
Error: User can SSH shell login even with nologin set
Verify that ForceCommand internal-sftp is in the Match block of sshd_config and that the block correctly covers this user group:
grep -A6 "Match Group sftpusers" /etc/ssh/sshd_config
# Must contain: ForceCommand internal-sftp
# Test
ssh webdeploy@YOUR_VPS_IP
# Should receive: "This service allows sftp connections only." and disconnect immediately
Frequently Asked Questions
How does SFTP differ from FTP and FTPS?
FTP is an old protocol that sends data and passwords in plaintext — insecure. FTPS is FTP plus SSL/TLS encryption but requires separate certificate management. SFTP (SSH File Transfer Protocol) runs on SSH which is already deployed, encrypts everything automatically, uses port 22 like SSH, and is simpler to set up.
Can I use FileZilla or WinSCP to connect to this SFTP server?
Yes. Both FileZilla and WinSCP support SFTP protocol. Set the protocol to "SFTP – SSH File Transfer Protocol," enter host, port 22, username, and private key file (PPK for WinSCP or OpenSSH format for FileZilla) and you're connected.
How do I monitor who uploaded what files?
Check auth.log: grep "Accepted\|sftp" /var/log/auth.log shows login timestamps and IP addresses. Files uploaded don't log directly — use inotifywait or auditd to monitor filesystem events in the /sftp/ directory.