SFTP is the least glamorous way to publish a website and one of the most durable. It works with almost any Linux server, most shared hosting and most home NAS boxes. It needs no account with a platform, no build pipeline and no API token that expires. You have a folder of files; SFTP puts them on a server.
This guide explains what SFTP actually is, how to find the four details every SFTP client asks for, how to set up keys properly, and how to fix the errors you’re most likely to meet.
SFTP, FTP and FTPS are three different things
The names are confusingly similar. The protocols are not.
- FTP is the original File Transfer Protocol, from the early days of the internet. It usually listens on port 21 and sends your username and password in plain text. Avoid it.
- FTPS is FTP wrapped in TLS, the same encryption HTTPS uses. It fixes the plain-text problem but keeps FTP’s awkward separate data connections, which firewalls dislike.
- SFTP is the SSH File Transfer Protocol. Despite the name, it isn’t FTP at all. It runs inside an SSH connection, usually on port 22, so it’s encrypted end to end and authenticates the same way SSH does: a password or, better, a key.
If your server lets you log in with ssh, it almost certainly supports SFTP already. OpenSSH, the SSH server on nearly every Linux system, includes it.
The four details you need
Every SFTP client asks for the same things: host, port, username and remote path. Where you find them depends on the kind of server.
| Host | Port | Username | Remote path | |
|---|---|---|---|---|
| VPS | IP address or domain | 22 unless you changed it | the user you created, e.g. deploy | wherever your web server’s root points, e.g. /var/www/example.com |
| cPanel hosting | your domain or the server hostname from your welcome email | 22, or a custom port some hosts use | your cPanel username | usually /home/<username>/public_html |
| Synology NAS | the NAS’s local IP or a hostname | 22 by default, configurable | a DSM user account | the Web Station shared folder, /web |
A few notes on each.
VPS. You set these up yourself, so you know them. The remote path is whatever root says in your nginx or Caddy configuration. If you haven’t got that far, how to host a static blog on your own server walks through it.
cPanel. Not every host enables SSH, and SFTP depends on it. If there’s an SSH Access page in cPanel, you’re probably fine. If not, ask your host. Some use a non-standard port; it’ll be in your account details or their help pages.
Synology. In DSM, go to Control Panel › File Services › FTP and tick Enable SFTP service. The default port is 22. If you use Web Station, it serves files from a shared folder called web, and Synology’s own guidance is that the http group needs at least read permission on it.
Use an SSH key, not a password
Passwords work, but keys are both safer and more convenient. A key pair has a private half that stays on your machine and a public half that you give to the server.
Create one on your Mac or Linux machine:
ssh-keygen -t ed25519 -C "laptop-2026" -f ~/.ssh/id_ed25519_site
Give it a passphrase when asked. Ed25519 has been ssh-keygen’s default since OpenSSH 9.5; spelling it out makes the command behave the same on older systems. A few older tools and hosts only accept RSA. If you meet one, use ssh-keygen -t rsa -b 4096 instead.
Now install the public half on the server. On a VPS where you can still log in with a password:
ssh-copy-id -i ~/.ssh/id_ed25519_site.pub deploy@example.com
On cPanel, go to SSH Access › Manage SSH Keys › Import Key and paste the contents of id_ed25519_site.pub. Then authorise the key. cPanel won’t use a key until you do, and this is the step people miss.
Test it:
ssh -i ~/.ssh/id_ed25519_site deploy@example.com
Check the host key fingerprint
The first time you connect, SSH shows something like this:
The authenticity of host 'example.com (203.0.113.10)' can't be established.
ED25519 key fingerprint is SHA256:JX+wL0OQDpSU1C1rv2FWspDkZeUv7DI8us/vG8Hppbc.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
That fingerprint identifies the server. Typing yes without checking it means trusting whoever answered, which is almost always your server, but checking is how you know.
Get the real fingerprint through a channel an attacker can’t tamper with. Many VPS providers offer a web console; log in there and run:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
If the two match, accept. You can also fetch the key over the network and fingerprint it:
ssh-keyscan -t ed25519 example.com | ssh-keygen -lf -
Be clear about what that proves. ssh-keyscan trusts the network just as much as your first connection does. It’s useful for recording the fingerprint, or comparing it later, but it’s not independent verification.
If the key ever changes, SSH refuses to connect with a loud REMOTE HOST IDENTIFICATION HAS CHANGED warning. Sometimes that’s legitimate: you rebuilt the server. Confirm that’s what happened, then remove the old entry:
ssh-keygen -R example.com
Upload the files
With sftp
The sftp command comes with OpenSSH. From the folder that contains your built site (public here):
sftp -i ~/.ssh/id_ed25519_site -P 22 deploy@example.com
Note the capital -P for the port; ssh uses lower-case -p. Then, at the sftp> prompt:
cd /var/www/example.com
lcd public
put -r *
lcd changes the local directory; put -r * uploads everything in it, recursively. Two limitations: the * skips hidden files such as .htaccess, and sftp never deletes anything on the server. Remove a page locally and it stays online until you delete it by hand.
With rsync
rsync mirrors a folder over SSH, sending only what changed and optionally removing what’s gone:
rsync -avz --delete --dry-run \
-e "ssh -i ~/.ssh/id_ed25519_site -p 22" \
public/ deploy@example.com:/var/www/example.com/
Read the output, then run it again without --dry-run. The trailing slash on public/ means “the contents of this folder”. Without it you get /var/www/example.com/public/. And be careful with --delete: pointed at the wrong path, it’ll empty it.
Two caveats. rsync must also be installed on the server; some locked-down SFTP-only accounts don’t allow it. And since macOS Sequoia 15.4, the rsync that ships with macOS is actually openrsync, which supports fewer options. The command above works with it. For the full feature set, brew install rsync.
Common errors and what they mean
Permission denied (publickey). The server didn’t accept your key. Check that you’re using the right username and the right -i key file, that the public key is in ~/.ssh/authorized_keys on the server (or authorised, on cPanel), and that permissions are tight: chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys. OpenSSH ignores keys in files other users can write to.
Permission denied when uploading. You’re logged in, but your user can’t write to that directory. Fix ownership on the server: sudo chown -R deploy:deploy /var/www/example.com.
No such file or directory. Wrong remote path. Some hosts restrict SFTP users to their home directory, so / means your home rather than the real root, and absolute paths from the documentation won’t match. Connect, run pwd and ls, and work from what you see.
Connection refused or a timeout. Wrong port, SSH disabled, or a firewall in the way. On a NAS at home, reaching it from outside also needs port forwarding on your router.
The upload works, but the site shows 403 Forbidden. The web server can’t read the files. This happens when your local umask or the server’s SFTP settings create files readable only by you (600). The web server needs directories at 755 and files at 644. Fix everything in one go:
ssh -i ~/.ssh/id_ed25519_site deploy@example.com \
'find /var/www/example.com -type d -exec chmod 755 {} + && find /var/www/example.com -type f -exec chmod 644 {} +'
The sftp prompt has a chmod command too, but it isn’t recursive, so it’s only practical for a file or two.
Publishing with Selfish
Selfish, a native app for iPhone, iPad and Mac that’s out now on the App Store, publishes over SFTP with the same four details and either a password or an SSH key (Ed25519 or RSA, with passphrases). It pins the server’s SHA256 host key on first connection and refuses to upload if it changes, skips files that haven’t changed, and keeps credentials in Apple’s Keychain. The publishing docs cover the setup screen by screen.