
Most hosting breaches that start with “someone got FTP access” usually trace back to two issues: weak authentication and sloppy permissions. This SFTP setup tutorial shows how to run SFTP safely on Ubuntu or Debian. You’ll use chrooted users, key-only logins, per-site directories, and guardrails that keep clients out of /etc and away from other accounts.
You can use the same layout on a multi-tenant VPS, a reseller box, or a single high-value WordPress server. The workflow is CLI-first, easy to audit, and easy to repeat as you add sites.
What you’ll build (and why it’s safer than “just enable SFTP”)
By the end, your VPS will support:
- SFTP-only users (no shell) using OpenSSH’s internal SFTP subsystem.
- Chroot jails so each user sees only their own directory tree.
- Key-based auth for SFTP users (and optional password auth for a break-glass admin).
- Clean per-site permission model that prevents users from writing to the chroot root (a common misconfig that breaks chroot).
- Audit-friendly logging so you can trace failed logins and suspicious activity.
For production hosting, do this on a VPS where you control OpenSSH and the firewall. A HostMyCode VPS gives you root access, predictable networking, and enough isolation to host multiple sites.
Unlike shared hosting, you don’t share OS-level access.
Prerequisites and baseline checks
This guide targets Ubuntu 24.04 LTS / Debian 12+ in 2026. You’ll need root (or sudo) on the server. You also need an admin account you can already SSH into.
- Server: Ubuntu 24.04 LTS or Debian 12/13
- OpenSSH server installed (usually present by default)
- A domain or at least a stable IP for client access
Confirm OpenSSH and your current SSH config:
ssh -V
sshd -T | head
sudo ss -lntp | grep ':22'
Safety note: Keep your current SSH session open. Test new logins from a second terminal.
If you run strict firewall rules, review them before you touch SSH. HostMyCode also has a lockout-avoidance walkthrough here: VPS firewall troubleshooting tutorial (2026).
SFTP setup tutorial: create a dedicated SFTP group and directory layout
Start with a clear convention. This guide puts chroot homes under /sftp. Each account gets a writable subdirectory inside the jail, such as uploads or public_html.
sudo groupadd --system sftpusers
sudo mkdir -p /sftp
sudo chmod 755 /sftp
sudo chown root:root /sftp
Chroot has one non-negotiable rule: the jail root must be owned by root:root. It also must not be writable by the user.
Let users write only to subdirectories inside the jail.
Configure sshd for SFTP-only + chroot (clean and reversible)
Edit your SSH daemon config:
sudo nano /etc/ssh/sshd_config
Make sure the internal subsystem is enabled (it usually is). If you see an external path like /usr/lib/openssh/sftp-server, switch to internal.
Internal SFTP often behaves better with chroot:
Subsystem sftp internal-sftp
Then add a Match block at the bottom. This block applies only to users in the sftpusers group.
Match Group sftpusers
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
X11Forwarding no
AllowTcpForwarding no
PermitTunnel no
PasswordAuthentication no
Why this matters: ForceCommand removes shell access. Disabling forwarding blocks a common lateral-movement path if a key leaks.
Validate the config before you reload anything:
sudo sshd -t
If it returns silently, reload SSH. This won’t drop existing sessions:
sudo systemctl reload ssh
Create your first chrooted SFTP user (with correct ownership)
We’ll create a user named client1. Use the same pattern for each client or site.
sudo useradd -m -d /sftp/client1 -s /usr/sbin/nologin -g sftpusers client1
sudo passwd -l client1
Now lock down the chroot root. The jail root must be owned by root. It also cannot be writable by the user.
sudo chown root:root /sftp/client1
sudo chmod 755 /sftp/client1
Create a writable directory inside the jail. Use it for uploads, web files, or a staging drop.
This directory can be owned by the SFTP user:
sudo mkdir -p /sftp/client1/uploads
sudo chown client1:sftpusers /sftp/client1/uploads
sudo chmod 750 /sftp/client1/uploads
Common pitfall: If you make client1 the owner of /sftp/client1, chroot will fail with “bad ownership or modes for chroot directory”. Keep /sftp/client1 owned by root. Give users ownership only on subdirectories where they should write.
Set up SSH keys for SFTP users (key-only access)
On your local machine, generate a modern keypair if you don’t already have one:
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/client1_sftp
Now create an .ssh directory inside the chroot.
With chrooted SFTP users, authorized_keys must live under the user’s chrooted home. That’s the only filesystem the session can see.
sudo mkdir -p /sftp/client1/.ssh
sudo chown client1:sftpusers /sftp/client1/.ssh
sudo chmod 700 /sftp/client1/.ssh
Add the public key. Copy it from ~/.ssh/client1_sftp.pub on your local machine:
sudo nano /sftp/client1/.ssh/authorized_keys
sudo chown client1:sftpusers /sftp/client1/.ssh/authorized_keys
sudo chmod 600 /sftp/client1/.ssh/authorized_keys
Test from your local machine:
sftp -i ~/.ssh/client1_sftp client1@YOUR_SERVER_IP
You should land inside the jail. You should only see the directories you created.
Make SFTP useful for web hosting: map to a site directory safely
Most hosting setups want SFTP access to public_html for a vhost or a WordPress install. Two approaches work well.
Pick based on how you deploy and how much separation you want.
- Model A (simple): keep the website files inside the chroot under
/sftp/client1/public_html, and point your web server vhost root there. - Model B (safer separation): keep the site under
/var/www/site1and use deploy workflows or selective bind mounts. Choose this if you want a clearer line between “uploads” and “app code”.
Model A is the quickest for small hosting setups. Example:
sudo mkdir -p /sftp/client1/public_html
sudo chown client1:sftpusers /sftp/client1/public_html
sudo chmod 750 /sftp/client1/public_html
If you’re running Nginx, you can point a server block at this path. For a production-ready baseline, follow: Nginx Setup Guide Tutorial (2026).
Harden SSH access without breaking SFTP clients
Once SFTP is scoped to a group, you can tighten SSH with less risk. Two changes pay off quickly:
- Block root password logins (root keys are fine, but a non-root sudo admin is usually cleaner).
- Keep password auth off for SFTP users, and decide whether admins get passwords at all (many teams disable them everywhere).
Recommended baseline in /etc/ssh/sshd_config (outside the Match block):
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
If you need MFA or stricter admin workflows, a jump host is often better than exposing SSH broadly. This pairs well with: SSH Jump Host Setup Tutorial (2026).
Firewall checklist for SFTP on a hosting VPS
SFTP runs over SSH (usually TCP/22). You do not open extra “FTP data ports” here.
Those ports belong to FTP, not SFTP.
- Allow: TCP 22 from your office IPs or VPN if possible
- Allow: TCP 80/443 for web traffic (if this server hosts websites)
- Deny: everything else by default
If you use UFW, a typical rule set looks like:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
For a hosting-focused UFW baseline (DNS/email included), see: UFW firewall setup tutorial (2026).
Improve auditability: log SFTP activity and failed logins
On Debian/Ubuntu, OpenSSH logs to the systemd journal. It also typically logs to /var/log/auth.log.
Start with quick checks:
sudo journalctl -u ssh --since "1 hour ago" --no-pager
sudo tail -n 100 /var/log/auth.log
If you need more detail for investigations, bump SSH logging:
sudo nano /etc/ssh/sshd_config
LogLevel VERBOSE
Reload SSH:
sudo systemctl reload ssh
Don’t leave extra-chatty logging enabled forever on a small disk. If you need retention, ship logs off the box or rotate aggressively.
Operational workflow: add users quickly with a repeatable script (optional)
After you’ve created a few accounts, doing this by hand gets old. Here’s a small helper you can adapt.
It creates the user, the jail, and an uploads directory.
sudo nano /usr/local/sbin/add-sftp-user
#!/bin/bash
set -euo pipefail
USER="$1"
BASE="/sftp/${USER}"
getent group sftpusers >/dev/null || groupadd --system sftpusers
useradd -m -d "$BASE" -s /usr/sbin/nologin -g sftpusers "$USER"
passwd -l "$USER" >/dev/null || true
chown root:root "$BASE"
chmod 755 "$BASE"
mkdir -p "$BASE/uploads" "$BASE/.ssh"
chown "$USER":sftpusers "$BASE/uploads" "$BASE/.ssh"
chmod 750 "$BASE/uploads"
chmod 700 "$BASE/.ssh"
echo "Created SFTP user: $USER"
echo "Next: add public key to $BASE/.ssh/authorized_keys"
sudo chmod 750 /usr/local/sbin/add-sftp-user
sudo /usr/local/sbin/add-sftp-user client2
If you manage multiple servers, keep helpers like this in version control. That makes it easy to diff changes later.
Troubleshooting: fix the 5 most common SFTP chroot failures
If a client reports “Connection closed” or “Server unexpectedly closed network connection,” the cause is usually one of these.
1) “bad ownership or modes for chroot directory”
- Cause:
/sftp/USERNAMEis writable by the user or not owned by root. - Fix:
sudo chown root:root /sftp/client1
sudo chmod 755 /sftp/client1
2) Key is ignored, client keeps asking for password
- Cause: wrong permissions on
.sshorauthorized_keys, or the client isn’t offering the key you expect. - Fix:
sudo chmod 700 /sftp/client1/.ssh
sudo chmod 600 /sftp/client1/.ssh/authorized_keys
sudo chown -R client1:sftpusers /sftp/client1/.ssh
On your local machine, confirm the key is actually being tried:
ssh -i ~/.ssh/client1_sftp -v client1@YOUR_SERVER_IP
3) User can login but can’t upload files
- Cause: the user is trying to write to the chroot root, not the writable subdirectory.
- Fix: ensure
uploads(orpublic_html) exists and is owned by the user.
sudo mkdir -p /sftp/client1/uploads
sudo chown client1:sftpusers /sftp/client1/uploads
sudo chmod 750 /sftp/client1/uploads
4) “Could not chdir to home directory”
- Cause: home directory missing or the wrong path in
ChrootDirectory. - Fix: confirm the directory exists and matches
/sftp/%u.
getent passwd client1
sudo ls -ld /sftp/client1
5) You locked yourself out after changing SSH settings
- Cause: config error + restart, or a firewall rule blocks port 22.
- Fix: use console access (provider panel), then validate with
sshd -tand review firewall rules.
Practical security checklist for SFTP on VPS and dedicated servers
- Use key-only authentication for SFTP users.
- Chroot every client user to /sftp/%u (or a per-site jail).
- Keep chroot root owned by root:root, mode 755.
- Create a single writeable directory like uploads with mode 750.
- Disable forwarding in the SFTP
Matchblock. - Restrict SSH at the firewall (source IP allowlist if possible).
- Review SSH logs weekly; alert on repeated failures.
Where this fits in real hosting operations
Chrooted SFTP is a practical middle ground for client file access:
- It’s lighter than deploying a full control panel just to move files.
- It’s safer than handing out shell accounts.
- It fits WordPress and PHP hosting workflows where clients mainly upload media or edit a few theme files.
If you’re migrating sites onto this server, keep DNS changes controlled. Follow: DNS Migration Tutorial (2026) so you don’t trade a clean SFTP rollout for downtime.
Summary: a hardened SFTP baseline you can support
You end up with a setup that behaves the same way every time. Users authenticate with keys, land in a chroot jail, and can write only where you allow.
That’s the difference between “SFTP is enabled” and “SFTP is safe to run on a hosting server.”
If you want this on a server built for hosting workloads, start with a HostMyCode VPS. Move to dedicated servers when you need consistent CPU for multiple client sites.
Either way, keep file transfer access boring, locked down, and easy to audit.
Need a VPS that’s ready for secure client access and multi-site hosting? Use managed VPS hosting from HostMyCode for help with baseline hardening, SSH/SFTP policies, and ongoing patching. If you’re moving from shared hosting or another provider, our migration service can transfer sites while keeping access controls consistent.
FAQ
Can I use SFTP on port 22 without enabling “FTP ports”?
Yes. SFTP is a subsystem of SSH. You only need TCP/22 (or your custom SSH port). Do not open FTP passive port ranges unless you’re actually running FTP.
Should I allow password authentication for SFTP users?
For hosting environments, key-only is the safer default. If you must support passwords, enforce long passwords and rate-limit SSH. Expect more brute-force noise.
How do I give a user access to two directories?
Keep a single chroot, then place both directories inside it. If you need access to paths outside the jail, use bind mounts carefully and test permissions.
Does this replace a control panel like cPanel?
No. This replaces “give the client FTP” with a safer file transfer method. Control panels also manage DNS, mail, PHP versions, backups, and account isolation.
What’s the fastest way to test a client’s key login?
Use verbose SSH from your machine: ssh -i ~/.ssh/keyname -v user@server. It will show whether the client offers the key and why the server rejects it.