Back to tutorials
Tutorial

SFTP Setup Tutorial (2026): Lock Down File Transfers on a Hosting VPS with Chroot, Keys, and Per-User Access

SFTP setup tutorial for 2026: create chrooted users, key-only login, and safe permissions for uploads on your hosting VPS.

By Anurag Singh
Updated on Aug 07, 2026
Category: Tutorial
Share article
SFTP Setup Tutorial (2026): Lock Down File Transfers on a Hosting VPS with Chroot, Keys, and Per-User Access

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/site1 and 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:

  1. Block root password logins (root keys are fine, but a non-root sudo admin is usually cleaner).
  2. 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/USERNAME is 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 .ssh or authorized_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 (or public_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 -t and 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 Match block.
  • 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.

SFTP Setup Tutorial (2026): Lock Down File Transfers on a Hosting VPS with Chroot, Keys, and Per-User Access | HostMyCode