How to give a client or developer SFTP access to one website only
Someone needs to upload files to one of your sites. Give them a login that can only move files, only in that site's folder. No shell, no other sites, and easy to remove later.
Why SFTP, not FTP
Plain FTP sends the password and the files unencrypted, and it needs extra ports open. SFTP runs over SSH, which your server already has on port 22. Everything is encrypted, and every common program can use it: FileZilla, WinSCP, Cyberduck and the code editors with a "remote files" option. So you don't need to install an FTP server at all.
What you build here:
- A user that can only use SFTP. Trying to open a shell fails.
- The user is locked in a folder (a "chroot"). It sees the site's files and nothing else on the server.
- The files it uploads belong to the site's group, so PHP can read them and the site keeps working.
One thing to know first: anyone who can upload PHP files to a site can run code as that site. Only give this to people you trust with the site.
Before you start
- The site runs as its own user with its own PHP-FPM pool, as in our guide to one user and pool per site. The examples use that guide's names: the site user
shop, the folder/var/www/shop.exampleand the website files inpublic. - The person getting access is called
anna. Use your own names. - You log in with your own user that has sudo, not root. See use a sudo user instead of root.
- Tested on Ubuntu 24.04 (OpenSSH 9.6) and Debian 13 (OpenSSH 10.0).
1. Make the user
sudo useradd --no-create-home --home-dir /srv/sftp/shop --shell /usr/sbin/nologin --groups shop anna
Anna can't log in to a shell, and she joins the shop group. That group is how she gets to the site's files. Check with id anna: the list of groups should include shop.
2. Make the folder she is locked in
SSH has one strict rule here: the lock folder, and every folder above it, must belong to root and must not be writable by anyone else. If not, SSH refuses the login (more on that error below). So the site folder itself can't be the lock folder, because it belongs to shop.
The usual way round it: make a small root-owned folder, and show the site's files inside it with a "bind mount". That makes the same files appear in a second place. Nothing is copied.
sudo mkdir -p /srv/sftp/shop/site
sudo chown root:root /srv/sftp /srv/sftp/shop
sudo chmod 755 /srv/sftp /srv/sftp/shop
Add the bind mount to /etc/fstab, so it comes back after a restart, then mount it now:
echo '/var/www/shop.example/public /srv/sftp/shop/site none bind 0 0' | sudo tee -a /etc/fstab
sudo systemctl daemon-reload
sudo mount /srv/sftp/shop/site
ls /srv/sftp/shop/site should now list the site's files. Anna will see a folder called site, and nothing else from the server. Only public is shown, so the site's sessions and tmp folders stay out of reach.
3. Let her change the site's files
Anna writes through the shop group, so the folders and files need group write rights. Two commands:
sudo find /var/www/shop.example/public -type d -exec chmod g+ws {} +
sudo find /var/www/shop.example/public -type f -perm -g=r -exec chmod g+w {} +
- The first lets the group write in every folder. The
s(called "setgid") makes new files and folders take the folder's group,shop, not Anna's own group. That's what keeps them readable by PHP. - The second adds write rights only to files the group can already read. Files you locked to the owner, such as WordPress's
wp-config.phpat600, stay locked, so Anna can't read the database password. If she does need that file, change it on purpose.
PHP-FPM still runs as shop and still owns the site's files. Nothing changes for the site itself.
4. Add her SSH key
Ask Anna for her public key: the file ending in .pub. If she has none, she runs ssh-keygen -t ed25519 on her computer (see our SSH keys guide). Keep her keys in a root-owned place outside her folder, so she can't change them:
sudo mkdir -p /etc/ssh/authorized_keys
sudo nano /etc/ssh/authorized_keys/anna
Paste her key as one line and save. Then:
sudo chmod 755 /etc/ssh/authorized_keys
sudo chmod 644 /etc/ssh/authorized_keys/anna
5. Tell SSH to lock her in
sudo nano /etc/ssh/sshd_config.d/50-sftp-shop.conf
Match User anna
ChrootDirectory /srv/sftp/shop
ForceCommand internal-sftp -u 0002 -d /site
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
DisableForwarding yes
PermitTunnel no
PermitTTY no
What each line does:
Match User anna: the lines below apply to Anna only. Your own login doesn't change.ChrootDirectory: the folder she is locked in. To her, it looks like the whole server.ForceCommand internal-sftp: whatever she asks for, she only gets SFTP.-u 0002makes new folders group-writable, and-d /sitestarts her in the site's files.AuthorizedKeysFile: read her key from the file you made in step 4.DisableForwarding,PermitTunnel no: she can't use the connection to reach other services on the server, such as the database or another site.PermitTTY no: no terminal.
Check the file, see what SSH will use for Anna, and apply it:
sudo sshd -t
sudo sshd -T -C user=anna,host=x,addr=203.0.113.5 | grep -Ei 'chroot|forcecommand|disableforwarding'
sudo systemctl reload ssh
sshd -t prints nothing when the file is fine. The second command should show your folder and internal-sftp. Run it with user= your own name too: there it should say chrootdirectory none. Reloading doesn't drop your open session.
Several people, or several sites?
For a few people on the same site, list them: Match User anna,ben. Or make a group, for example sftp-shop, add each person to it, and use Match Group sftp-shop. Each site gets its own folder in /srv/sftp, its own bind mount and its own Match block.
6. Only if she can't use a key: a password
Keys are safer. If Anna's program can't use one, turn passwords on for her only. Add this line at the end of the Match block from step 5:
PasswordAuthentication yes
Then set a long, random password and apply:
sudo passwd anna
sudo sshd -t && sudo systemctl reload ssh
Because the line sits inside Match User anna, password login stays off for everyone else, including after you turned it off. Check: sudo sshd -T -C user=root,host=x,addr=203.0.113.5 | grep passwordauth should still say no.
An account with a password will get guessing attempts, so also set up Fail2ban. Its normal SSH rule covers SFTP too: in our test, five wrong SFTP passwords got the address blocked.
7. Test it
From a computer with Anna's key (or ask her to do it), connect:
sftp anna@your-server
Try a few things:
sftp> pwd
Remote working directory: /site
sftp> put index.php
sftp> mkdir assets
sftp> cd /
sftp> ls
site
sftp> cd /var/www
realpath /var/www: No such file
sftp> get /etc/passwd
File "/etc/passwd" not found.
She can upload and make folders, but outside site there is nothing: not your other sites, and not the rest of the server. Now check that a shell is refused:
ssh anna@your-server
This service allows sftp connections only.
On the server, check the new files. They belong to anna, with the group shop:
ls -l /var/www/shop.example/public
Open the site in a browser to see that PHP runs the uploaded files. In our tests, uploaded PHP files ran as shop, and files Anna overwrote kept their owner shop.
Good to know
- New files are readable, but not always writable, by the site. Most programs upload with the rights the file had on Anna's computer, often
644. PHP can read that, but the site can't change the file. Usually that's what you want. If the site must change it, Anna can runchmod 664 filein SFTP, or you run the two commands from step 3 again. - Files the site makes, such as WordPress uploads or a cache, belong to
shopand are often only readable by the group. If Anna needs to change them, run the step 3 commands again. - If the site deploys from Git, changes should go through the repository instead. Uploaded files get overwritten on the next deploy.
- If your SSH logins ask for a code (see our two-step SSH guide), add
AuthenticationMethods publickeyto herMatchblock, or she can't log in. - On Ubuntu, a small
.cachefolder can appear next tositeafter her first login. It's harmless.
Remove access later
When the work is done, remove the login. First the SSH settings and the key:
sudo rm /etc/ssh/sshd_config.d/50-sftp-shop.conf /etc/ssh/authorized_keys/anna
sudo sshd -t && sudo systemctl reload ssh
Connections that are already open stay open after a reload. End them:
sudo pkill -u anna
Give her files back to the site user, so they don't end up with an unknown owner:
sudo find /var/www/shop.example -user anna -exec chown shop {} +
Remove the bind mount and the folder. rmdir only removes an empty folder, so it stops you if the site's files are still mounted there:
sudo umount /srv/sftp/shop/site
sudo sed -i '\#/srv/sftp/shop/site#d' /etc/fstab
sudo rmdir /srv/sftp/shop/site
sudo rm -r /srv/sftp/shop
sudo userdel anna
If you only want to pause access, remove the key file (or lock the password with sudo passwd -l anna) and end open sessions. Everything else can stay.
If something goes wrong
- The connection closes right away, often with "Connection closed" or "Broken pipe". Look at the SSH log:
sudo journalctl -u ssh -n 20. If it saysbad ownership or modes for chroot directory, the lock folder or a folder above it isn't owned by root, or is group- or world-writable. Fix it with thechownandchmod 755from step 2. Never make the lock folder writable for Anna: use thesitefolder inside it. - "Permission denied (publickey)": the key file is wrong, or not readable. Check that
/etc/ssh/authorized_keys/annaholds her.pubkey on one line, and that the file is644and owned by root. - She logs in but sees an empty
sitefolder: the bind mount isn't there, for example after a restart with a typo in/etc/fstab. Runfindmnt /srv/sftp/shop/siteandsudo mount -a. - "Permission denied" when she uploads: the folder or file isn't group-writable, or she isn't in the
shopgroup. Run the commands in step 3 and checkid anna. A new group only counts from her next login. - The site shows an error for her new files: check their group with
ls -l. It must beshop. If it isn't, the folder lacks the setgid bit from step 3.
Official sources
- sshd_config manual (OpenSSH):
Match,ChrootDirectory,ForceCommand,DisableForwarding - sftp-server manual (OpenSSH): the
-uand-doptions, whichinternal-sftpalso takes - sftp manual (OpenSSH)
- Bind mounts in the mount manual (Debian)
Next steps: if you haven't yet, turn off password login for everyone else, then work through the security checklist for a new PHP server.
ServerNest is in development. In our preview, each app gets its own SFTP login that uses an SSH key, stays inside the app's folder and can't open a shell. Join early access to try it first.