SFTP (SSH File Transfer Protocol) is the secure way to transfer files to a server. It runs over SSH, so if your server has OpenSSH, you already have an SFTP server: nothing extra to install, and no plain-text passwords like FTP. With a few settings, you can give clients, colleagues or applications SFTP access that is locked to their own folder, with no shell access.
SFTP vs FTP vs FTPS
| FTP | FTPS | SFTP | |
|---|---|---|---|
| Encryption | None | TLS | SSH |
| Ports | 21 + data port range | 21/990 + data port range | 22 only |
| Firewall friendliness | Poor | Poor | Good |
| Extra software | vsftpd etc. | vsftpd + certificates | Built into OpenSSH |
For most uses, SFTP is the simplest secure choice.
1. Basic SFTP for existing users
Any user who can SSH can already use SFTP:
sftp [email protected]
sftp> put backup.tar.gz
sftp> get logs/app.log
The rest of this guide creates SFTP-only users confined to a directory.
2. Create a group and user
sudo groupadd sftpusers
sudo useradd -m -d /home/client1 -s /usr/sbin/nologin -G sftpusers client1
sudo passwd client1
-s /usr/sbin/nologin prevents shell logins.
3. Set up the chroot directory
OpenSSH requires the chroot directory and every directory above it to be owned by root, and not writable by anyone else. So the user gets a writable subfolder inside it:
sudo mkdir -p /srv/sftp/client1/upload
sudo chown root:root /srv/sftp /srv/sftp/client1
sudo chmod 755 /srv/sftp /srv/sftp/client1
sudo chown client1:sftpusers /srv/sftp/client1/upload
sudo chmod 750 /srv/sftp/client1/upload
4. Configure sshd
Edit /etc/ssh/sshd_config. Make sure the SFTP subsystem uses the internal server:
Subsystem sftp internal-sftp
Add at the end of the file (Match blocks apply until the next Match or the end of the file):
Match Group sftpusers
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp -l INFO
AllowTcpForwarding no
X11Forwarding no
PermitTTY no
%u expands to the username. Test and reload:
sudo sshd -t && sudo systemctl reload ssh # the service is "sshd" on RHEL family
Keep your current SSH session open while testing, so a mistake can’t lock you out.
5. Test
sftp [email protected]
sftp> pwd # Remote working directory: /
sftp> ls # upload
sftp> cd upload
sftp> put test.txt
ssh [email protected] should be refused (“This service allows sftp connections only”).
6. Use SSH keys instead of passwords
For automated transfers and better security:
sudo mkdir -p /home/client1/.ssh
sudo nano /home/client1/.ssh/authorized_keys # paste the client's public key
sudo chown -R client1:client1 /home/client1/.ssh
sudo chmod 700 /home/client1/.ssh && sudo chmod 600 /home/client1/.ssh/authorized_keys
The keys live in the normal home directory, outside the chroot, which is fine. You can then disable passwords for the group with PasswordAuthentication no inside the Match block. See SSH keys with ssh-keygen.
Connecting with GUI clients
| Client | Platform | Settings |
|---|---|---|
| FileZilla | Windows, macOS, Linux | Protocol: SFTP; host; port 22; user; password or key file |
| WinSCP | Windows | File protocol: SFTP |
| Cyberduck | macOS, Windows | SFTP (SSH File Transfer Protocol) |
In FileZilla, keys are added under Settings → Connection → SFTP.
Logging
With -l INFO on internal-sftp, file operations are logged. In a chroot, the SFTP process can’t reach /dev/log, so add a log socket inside the chroot base if you need full logs (for rsyslog: $AddUnixListenSocket /srv/sftp/dev/log, with a dev directory). Authentication events always appear in /var/log/auth.log or journalctl -u ssh.
Security tips
- Protect SSH from brute force: keys, Fail2ban, or limiting port 22 to known IPs. See Fail2ban.
- One user per client, so access can be revoked individually.
- Set disk quotas if users might upload a lot. Monitor disk space. See the df command.
- For a multi-user file server with a web UI, S3 and WebDAV, consider SFTPGo. See SFTPGo.
Troubleshooting
| Error | Cause / fix |
|---|---|
| “Connection closed” right after login | Chroot ownership wrong: every directory in the chroot path must be root-owned and not group/other-writable. Check journalctl -u ssh for “bad ownership or modes” |
| “Permission denied” on upload | User has no write access to the folder: upload into upload/, check owner and mode |
| “Received message too long” | Shell startup files print text; use ForceCommand internal-sftp |
| Key not accepted | Permissions on .ssh / authorized_keys; check /var/log/auth.log |
Quick reference
| Task | Command / setting |
|---|---|
| SFTP-only user | useradd -s /usr/sbin/nologin -G sftpusers user |
| Jail | ChrootDirectory /srv/sftp/%u (root-owned) |
| No shell | ForceCommand internal-sftp |
| Apply | sshd -t && systemctl reload ssh |
| Connect | sftp user@host |
Related: the scp command and connecting to a VPS with SSH.