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.

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.

HostPortUsernameRemote path
VPSIP address or domain22 unless you changed itthe user you created, e.g. deploywherever your web server’s root points, e.g. /var/www/example.com
cPanel hostingyour domain or the server hostname from your welcome email22, or a custom port some hosts useyour cPanel usernameusually /home/<username>/public_html
Synology NASthe NAS’s local IP or a hostname22 by default, configurablea DSM user accountthe 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.