Back to tutorials
Tutorial

SFTP Setup Guide Tutorial (2026): Lock Down File Transfers on a Linux VPS Without Breaking Deployments

SFTP setup guide tutorial for 2026: create SFTP-only users, chroot folders, keys, and safe permissions on Ubuntu/Debian/AlmaLinux.

By Anurag Singh
Updated on Sep 15, 2026
Category: Tutorial
Share article
SFTP Setup Guide Tutorial (2026): Lock Down File Transfers on a Linux VPS Without Breaking Deployments

Most hosting breaches don’t start with a zero-day. They start with a leaked password.

That password lands on an account that can SSH into your VPS, roam home directories, and upload whatever it wants.

This SFTP setup guide tutorial shows how to create SFTP-only accounts with chroot jails, key-based auth, and sane permissions on Ubuntu, Debian, AlmaLinux, or Rocky. It also keeps the deployment patterns you already rely on.

The goal is simple: clients and developers can transfer files over SFTP. They can’t open a shell. They can’t browse the rest of the filesystem. They can’t overwrite code and configs by accident.

What you’ll build (and what you’ll need)

By the end, you’ll have:

  • An sftpusers group with SFTP-only access via OpenSSH internal-sftp
  • Optional per-user chroot to /home/USERNAME (or a custom mount path)
  • One writable upload directory (and read-only web/app directories if you want)
  • Key-based auth (optional but recommended) and a safe password policy fallback
  • Logging and quick troubleshooting commands

Prereqs: root SSH access (or sudo), OpenSSH server installed, and a clear target for where your site files live. Common paths are /var/www or /home/USER/public_html on control-panel stacks.

If you’re doing this on production, add guardrails first. Snapshots, monitoring, and a clean restore path reduce the risk of “one bad reload.”

A managed VPS hosting plan or a tuned HostMyCode VPS makes it easier to test changes without stress.

Step 1: Confirm SSHD supports internal-sftp

On current distros, OpenSSH usually includes internal-sftp. Confirm the version and what SSHD thinks its active settings are:

sshd -V 2>&1 | head -n1
sudo sshd -T | grep -E '^(subsystem|passwordauthentication|pubkeyauthentication)'

You want a Subsystem line like Subsystem sftp internal-sftp. You might also see a reference to /usr/lib/openssh/sftp-server.

In this guide, you’ll standardize on internal-sftp. It behaves well inside chroot and keeps the config simpler.

Step 2: Create a dedicated SFTP group

Put SFTP-only accounts in their own group. That keeps policy clean as you add more users.

sudo groupadd --system sftpusers
getent group sftpusers

This also helps on mixed-use servers. You can keep admin/deploy SSH users under one set of rules and clients under another.

Step 3: Create an SFTP-only user (no shell)

This example creates client1 and assigns them to sftpusers.

Set their shell to /usr/sbin/nologin (Debian/Ubuntu) or /sbin/nologin (common on RHEL-family). Use whichever exists on your host.

command -v nologin
# Example (Debian/Ubuntu): /usr/sbin/nologin

sudo useradd -m -g sftpusers -s /usr/sbin/nologin client1
sudo passwd client1
id client1

Setting a password is fine for initial testing.

In Step 7, you’ll switch to keys. At that point, you can also disable passwords for this group.

Step 4: Build a chroot directory layout that won’t fail

Chroot has one hard rule: the chroot directory must be owned by root:root. It also must not be writable by the user.

If you violate that, SFTP refuses to start. You’ll usually see: “bad ownership or modes for chroot directory”.

A reliable pattern is to chroot to the user’s home directory. Then create one writable subfolder for uploads.

# Chroot base (must be root-owned)
sudo chown root:root /home/client1
sudo chmod 755 /home/client1

# Writable directory inside chroot
sudo mkdir -p /home/client1/uploads
sudo chown client1:sftpusers /home/client1/uploads
sudo chmod 750 /home/client1/uploads

Common variations:

  • If you need a web root, create /home/client1/www and keep it owned by root, then deploy into it via your CI/CD user (not the client).
  • If you need the client to manage only wp-content/uploads, use a bind mount (Step 9) instead of giving broader write access.

Step 5: Update sshd_config for SFTP-only users (Match Group)

Edit your SSH daemon config. It’s typically /etc/ssh/sshd_config.

Some systems prefer drop-ins under /etc/ssh/sshd_config.d/*.conf. If you already manage SSH that way, stick with it. Otherwise, editing the main file is fine.

sudo cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)
sudo nano /etc/ssh/sshd_config

Make sure the SFTP subsystem is set (add it if it’s missing):

Subsystem sftp internal-sftp

Then add this block near the end of the file:

Match Group sftpusers
    ChrootDirectory %h
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY no

What these lines do:

  • ChrootDirectory %h keeps users inside their home directory.
  • ForceCommand internal-sftp blocks shell access even if they try to SSH normally.
  • Disabling forwarding/TTY trims off extra features you don’t want exposed to client accounts.

Check syntax before you reload anything:

sudo sshd -t

Then reload SSH without dropping existing connections:

sudo systemctl reload ssh
# or: sudo service ssh reload

If you’re hardening SSH more broadly (keys, 2FA, safe rollback), do that first.

The workflow in this SSH lockdown tutorial pairs well with the SFTP setup.

Step 6: Test SFTP access (and confirm shell is blocked)

From your local machine, connect over SFTP:

sftp client1@YOUR_SERVER_IP

You should land inside the chroot and see uploads. Try an upload:

put test.txt uploads/
ls -la

Now try a normal SSH login:

ssh client1@YOUR_SERVER_IP

Expected behavior: no shell. The connection is denied or immediately forces SFTP behavior.

If you still get a shell, your Match Group block isn’t applying. The most common causes are:

  • The user isn’t in sftpusers.
  • A later/earlier Match block is taking precedence.

Step 7: Switch to SSH keys (recommended) and restrict passwords

Password-only SFTP is an easy target. If you’re giving access to clients, keys are the safer default.

Create the user’s .ssh directory inside the chroot. That’s fine because it lives under /home/client1.

Keep the chroot base itself root-owned. The user should own .ssh and authorized_keys.

sudo mkdir -p /home/client1/.ssh
sudo chown client1:sftpusers /home/client1/.ssh
sudo chmod 700 /home/client1/.ssh

sudo nano /home/client1/.ssh/authorized_keys
sudo chown client1:sftpusers /home/client1/.ssh/authorized_keys
sudo chmod 600 /home/client1/.ssh/authorized_keys

Paste the client’s public key into authorized_keys (one key per line).

Next, decide how aggressive you want to be about passwords. Disabling passwords globally is stronger, but on mixed-use servers it can be disruptive.

A group-scoped rule is a good compromise.

Add this inside the same Match Group sftpusers block:

Match Group sftpusers
    PasswordAuthentication no
    PubkeyAuthentication yes
    ChrootDirectory %h
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY no

Reload SSH and test again.

Keep one root session open while you verify. That gives you a way back in if something goes sideways.

Step 8: Tighten logging so you can actually troubleshoot

SFTP problems almost always show up in SSH logs. The usual locations:

  • Debian/Ubuntu: /var/log/auth.log
  • AlmaLinux/Rocky: /var/log/secure
sudo tail -n 200 /var/log/auth.log
# or
sudo tail -n 200 /var/log/secure

On busy servers, logs can balloon fast. Make sure log rotation is working so troubleshooting doesn’t become a disk-space incident.

HostMyCode’s logrotate tutorial for hosting logs covers a clean baseline.

Step 9: Optional: Use a bind mount to expose only the folder you want

Sometimes you don’t want the user’s home directory to be their “world.” You want them to see one directory and nothing else.

Bind mounts are a straightforward way to do that.

Example: your WordPress site lives at /var/www/example.com and you only want SFTP access to wp-content/uploads.

1) Create a chroot base and a mount point:

sudo mkdir -p /sftp/client1
sudo chown root:root /sftp/client1
sudo chmod 755 /sftp/client1

sudo mkdir -p /sftp/client1/uploads
sudo chown root:root /sftp/client1/uploads
sudo chmod 755 /sftp/client1/uploads

2) Bind mount the real uploads directory into the chroot:

sudo mount --bind /var/www/example.com/wp-content/uploads /sftp/client1/uploads

3) Make it persistent in /etc/fstab:

sudo nano /etc/fstab
/var/www/example.com/wp-content/uploads  /sftp/client1/uploads  none  bind  0  0

4) Update SSHD to chroot to /sftp/%u for the SFTP group (note: this changes the base path pattern):

Match Group sftpusers
    ChrootDirectory /sftp/%u
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY no

5) Fix ownership for writable folders. The user must be able to write inside the bind-mounted directory.

In practice, that usually means the underlying directory must be writable by that user (or an appropriate group).

On WordPress hosts, be careful. Broad write permissions can become a long-lived malware foothold.

If you’re unsure, keep uploads writable and keep core code read-only. It’s not perfect, but it’s a sensible default.

Step 10: Add a sane permissions model for web hosting

Chroot helps, but permissions decide what an account can actually change.

On a VPS with multiple sites, that’s the difference between “contained” and “messy.”

  • Don’t give an SFTP user write access to the full document root unless you truly need it.
  • Do grant write access only to content directories (uploads, cache) and use a separate deploy user (or CI runner) for code.
  • Do keep config files (wp-config.php, environment files, API keys) readable only where required.

Quick diagnostics that catch the usual foot-guns:

# Show ownership and modes
namei -l /home/client1

# Find world-writable directories (a red flag)
sudo find /var/www -type d -perm -0002 -maxdepth 4 2>/dev/null | head

If you run WordPress on a control panel stack, consider built-in isolation instead of recreating everything with manual permissions.

For cPanel servers, the approach in this account isolation tutorial complements SFTP hardening.

Step 11: Firewall and rate-limit basics (to protect SFTP endpoints)

SFTP rides on SSH (port 22 by default). Keep it that way. Don’t open extra ports for “FTP.”

If you need to limit who can connect (office IPs, VPN egress), do it at the firewall.

On cPanel, CSF/LFD is the usual baseline. On a plain Linux VPS, use whatever firewall tooling you already manage.

HostMyCode’s VPS security audit tutorial includes a practical checklist for verifying SSH/firewall exposure without downtime.

A workable policy is to lock admin SSH to known IPs, but leave SFTP reachable if you support remote clients.

If you can’t restrict by IP, enforce keys and run Fail2Ban against your auth logs.

Step 12: Back up what SFTP users can change

Hardening reduces risk. It doesn’t prevent mistakes, compromised keys, or “oops I overwrote the theme.”

Plan for rollback.

At minimum, back up what your SFTP users can write to:

  • /home/*/uploads (or bind-mounted upload paths)
  • Any CMS content directories (WordPress uploads, cache if you care)
  • Your SSH config: /etc/ssh/sshd_config and /etc/ssh/sshd_config.d/

For a straightforward recovery path, pair this with incremental rsync backups or the automated approach in nightly encrypted restic backups.

For full-server rollbacks, snapshots are faster; see snapshot backup strategies.

Troubleshooting: common SFTP failures and quick fixes

  • “Connection closed” right after login
    Check logs for chroot permission errors. Fix with: chown root:root /home/client1 && chmod 755 /home/client1.
  • “Permission denied” when uploading
    Your writable directory isn’t owned by the user. Fix: chown client1:sftpusers /home/client1/uploads.
  • User still gets SSH shell
    They’re not matching the group, or another Match block overrides it. Confirm: id client1, then check sshd config order.
  • Keys don’t work
    Wrong permissions on .ssh/authorized_keys. Use 700 on .ssh and 600 on authorized_keys.
  • Disk fills up from uploads
    Uploads directories grow quietly. If you’re low on space, follow this disk space troubleshooting walkthrough and add quotas or cleanup policies.

Operational checklist (copy/paste for change tickets)

  • Create group sftpusers
  • Create user with nologin shell
  • Set chroot base ownership to root:root, mode 755
  • Create a writable directory inside chroot (owned by user), mode 750
  • Configure Subsystem sftp internal-sftp
  • Add Match Group sftpusers block with ForceCommand internal-sftp
  • Validate: sshd -t; reload: systemctl reload ssh
  • Test SFTP upload + confirm SSH shell is blocked
  • Enable keys and disable password auth for the group
  • Back up writable paths and test restore

Summary: a safer file-transfer baseline for hosting in 2026

SFTP-only users with chroot and SSH keys give you a predictable, limited file-transfer surface.

Clients can upload what they need. They can’t poke around the server or turn a stolen password into a full shell.

Once you standardize the directory layout and the Match Group rules, onboarding becomes routine instead of risky.

If you want to implement this on a fresh server (or you’re migrating from shared hosting), start with a HostMyCode VPS. If you’d rather hand off the OS baseline, patching, and ongoing upkeep, managed VPS hosting is the simpler route.

SFTP-only accounts are one of the simplest hardening wins for client-friendly hosting. HostMyCode’s VPS plans give you full root control so you can enforce clean OpenSSH policies, while managed VPS hosting makes sense if you want security and maintenance handled day to day.

FAQ: SFTP-only users on a hosting VPS

Is SFTP the same as FTP?

No. SFTP runs over SSH (encrypted end-to-end) and typically uses port 22. FTP is a different protocol and often requires multiple ports and extra hardening.

Can I chroot users without breaking Git-based deployments?

Yes—keep SFTP users for content uploads only, and use a separate deploy user (with limited sudo or a CI runner) for code. Don’t try to make one account do everything.

Why does chroot require root-owned directories?

If the chroot base is user-writable, users can alter the jail itself. OpenSSH blocks that by design. Root ownership is a safety check.

What’s the safest writable directory for WordPress?

Usually wp-content/uploads. Keep core files read-only where possible, and back up uploads frequently.

Should I disable password authentication entirely?

On most production VPS and dedicated servers in 2026, yes—at least for exposed SSH. If you must keep passwords, combine strong passwords with rate-limiting and monitoring.