FTP vs SFTP vs FTPS: ports and safety
FTP uses TCP 21 plus a separate data channel, FTPS adds TLS, SFTP rides SSH on port 22. Which ports to open, and why plain FTP leaks your password.
FTP vs SFTP vs FTPS: the short answer
The difference between FTP, SFTP and FTPS is which port each one uses and how much of the session is encrypted. FTP (file transfer protocol) sends commands on TCP (transmission control protocol) port 21 and moves the file itself over a second, separate connection: from port 20 in active mode, or over a high numbered port the server picks in passive mode. FTPS is that same FTP with TLS (transport layer security) wrapped around it, normally on port 21 after an AUTH TLS command, or on port 990 in the older implicit style. SFTP is not FTP with an S added. It is a subsystem of SSH (secure shell), so it runs inside an ordinary SSH session on TCP port 22.
Plain FTP sends the password and the file contents as readable text. Anyone who can see the packets reads both, so it should not carry a login on any network. Use SFTP. If sshd is running on your VPS you already have it, and it needs no firewall rule you do not already have.
Which ports does each protocol use?
- FTP: TCP 21 for the control channel, plus TCP 20 in active mode or one high port per transfer from the server's passive range. Nothing is encrypted.
- FTPS, explicit: TCP 21, upgraded in place by the
AUTH TLScommand, plus a passive data port for each transfer. This is the mode RFC 4217 defines, and the one to ask for. - FTPS, implicit: TCP 990, TLS from the first byte, with data historically on 989. Older than the standard, still common on managed file transfer platforms.
- SFTP: TCP 22 only. Commands and file data share one encrypted connection.
Two of those need a range of ports open, and one needs a port that is already open. If control channel and data channel are new terms, remember that a port is only a number a process listens on, and a protocol may use as many of them as it likes. FTP uses two at a time, and that single design decision explains almost every FTP problem you will ever debug.
Why is plain FTP a security risk?
FTP was finalised as RFC 959 in 1985, when nobody assumed the network in between was hostile. The protocol has no encryption at all. The login crosses the wire as two plain commands, and you can watch them go past on the server itself:
sudo tcpdump -i any -A 'tcp port 21'A client logging in then appears in the capture as readable lines:
220 (vsFTPd 3.0.5)
USER alice
331 Please specify the password.
PASS correct-horse-battery
230 Login successful.Every hop between the client and the server sees exactly that. A cafe access point, or a switch in a data centre that someone else operates, can read the password because nothing in the protocol hides it. The file bytes travel the same way, so the contents are readable too. There is also no integrity check, which means an attacker who can modify packets in flight can change a file during the download and neither end will notice.
The damage rarely stops at the file. An FTP account on a small server is usually a real Linux account, so a captured FTP password is often a shell password as well. That is the case for giving each service its own unprivileged account, and the case for not putting a cleartext protocol in front of any account at all.
Why does FTP need a port range when SFTP does not?
FTP splits every session in two. Port 21 carries the commands, and each transfer or directory listing opens a brand new TCP connection for the data. Where that second connection comes from is the whole problem.
In active mode the client sends a PORT command naming the address and port to call back on, and the server opens the data connection from its port 20 to the client. That is an inbound connection to the client machine. Home routers and client firewalls drop unsolicited inbound connections, so active mode fails for most people today, usually with 425 Failed to establish connection.
In passive mode the client sends PASV and the server answers with an address and a port for the client to connect to:
227 Entering Passive Mode (203,0,113,10,156,64)The last two numbers are the port, split into a high byte and a low byte: 156 times 256, plus 64, is port 40000. The client then opens the data connection to 203.0.113.10 on port 40000. Two consequences follow, and both of them generate support tickets.
First, the server firewall has to allow the whole passive range, not only port 21. A single ufw allow 21/tcp rule gives you a session that logs in cleanly and then hangs the moment you type ls, because the listing is a data connection to a port that is being dropped. Second, if the server sits behind NAT (network address translation), the address in that 227 reply is the server's private address, which the client cannot reach. FileZilla reports this as Server sent passive reply with unroutable address. Using server address instead. and other clients simply stall until they time out.
The fix is a fixed passive range, an explicit public address in the daemon config, and firewall rules that match. That is a guide of its own: setting a passive port range and opening it on the firewall covers the vsftpd and ufw side in full.
SFTP has none of this. Commands and file data are channels inside the one SSH connection on port 22, so there is no second connection to permit and no range to open. The firewall rule is the rule you wrote when you built the box, which is usually the only rule in a default deny ufw policy with SSH allowed.
FTPS: FTP with TLS, and the NAT problem it creates
FTPS leaves FTP exactly as it is and negotiates TLS on top. Explicit FTPS connects to port 21 in the clear, the client sends AUTH TLS, and the session is encrypted from that point on. Implicit FTPS starts the TLS handshake immediately on port 990, with no plaintext at any stage.
You can test either one from a client:
openssl s_client -connect ftp.example.com:21 -starttls ftp
openssl s_client -connect ftp.example.com:990A working server prints its certificate chain and ends with Verify return code: 0 (ok). Any other verify code means the certificate is self signed or issued for a different name, and strict clients will refuse it. curl says the same thing as curl: (60) SSL certificate problem. When the certificate comes from an internal authority, the client has to trust that authority first: adding your own CA to the Ubuntu trust store is the fix, not -k.
Encrypting the login is only half the job. TLS on the control channel does not protect the data channel by itself. That takes a PROT P command from the client, and the default state is PROT C, which is clear. So a client can authenticate over TLS and still upload the file in the open. On vsftpd the server side of that is force_local_data_ssl=YES, which refuses a plaintext data connection instead of quietly accepting it.
For a scripted transfer, make TLS mandatory on the client side too:
curl --ssl-reqd -u alice ftp://ftp.example.com/reports/june.csv -o june.csv--ssl-reqd fails the transfer if the server does not offer TLS. The similar --ssl option only tries, and continues in cleartext when the server declines, which is the last failure you want to be silent.
Encrypting the control channel also blinds the firewall. The Linux connection tracking helper nf_conntrack_ftp works by reading the PASV and PORT exchange as it passes and opening the data port on demand. Once that exchange is inside TLS, the helper sees encrypted bytes and can do nothing. That is why FTPS nearly always needs a static passive port range opened by hand, and why the daemon must be told its own public address. This is the trade in FTPS: encrypting the control channel is exactly what stops the firewall from helping.
SFTP: a subsystem of SSH you already run
SFTP is a subsystem of SSH version 2. There is no separate daemon and no separate port. sshd starts a helper when a client asks for the sftp subsystem, and one line in /etc/ssh/sshd_config decides which helper that is:
grep -i subsystem /etc/ssh/sshd_configOn Ubuntu and Debian this prints Subsystem sftp /usr/lib/openssh/sftp-server. Change the path to internal-sftp and the file server runs inside the sshd process. That matters as soon as you want to lock a user into one directory, because internal-sftp needs no copied binaries and no /dev nodes inside the chroot: it never launches a separate program.
Connect with the same credentials you use for SSH:
sftp alice@203.0.113.10
sftp -P 2222 alice@203.0.113.10sftp takes a capital -P for the port, unlike ssh. You should land on an sftp> prompt. If you get subsystem request failed on channel 0 instead, SSH itself is fine and the Subsystem line is missing or commented out.
Since OpenSSH 9.0, released in April 2022, scp uses the SFTP protocol underneath by default. So scp, sftp and rsync -e ssh all ride the one port you have already opened.
How do I give someone SFTP access without a shell?
A file transfer user does not need a shell, and with internal-sftp they never get one.
sudo groupadd -f sftponly
sudo useradd -d /srv/sftp/alice -s /usr/sbin/nologin -G sftponly alice
sudo install -d -o root -g root -m 755 /srv/sftp/alice
sudo install -d -o alice -g alice -m 755 /srv/sftp/alice/upload
sudo install -d -o root -g root -m 755 /etc/ssh/authorized_keysAdd this at the end of /etc/ssh/sshd_config. Everything after a Match line belongs to that block until the next Match, so a Match placed in the middle of the file silently captures the settings below it.
Match Group sftponly
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding noCheck the file and restart:
sudo sshd -t
sudo systemctl restart sshsshd -t parses the config and prints nothing when it is valid. Restarting without that check is how people lock themselves out of a VPS.
One rule about the chroot causes most of the failures here. ChrootDirectory and every directory above it must be owned by root and must not be writable by group or others. Make /srv/sftp/alice owned by alice and the login fails at once, with journalctl -u ssh recording fatal: bad ownership or modes for chroot directory. That is why the user's writable space is the upload directory one level down. Keys still work, but the chrooted home is root owned, so managing ~/.ssh/authorized_keys there is awkward. Point sshd at a root owned location instead, with AuthorizedKeysFile .ssh/authorized_keys /etc/ssh/authorized_keys/%u, and drop the public key in /etc/ssh/authorized_keys/alice. While you are in that file, the rest of turning off password logins and direct root SSH applies to these accounts too, because an SFTP user is an SSH user.
How do I tell which protocol a server speaks?
Start on the server and look at what is listening:
sudo ss -tlnpPort 22 with sshd behind it means SFTP is available. Port 21 with vsftpd or proftpd means FTP, and whether that daemon also offers FTPS depends on its config, not on the port. Port 990 means implicit FTPS.
From a client, ask the server directly:
nc -v ftp.example.com 21An FTP daemon answers with a 220 banner as soon as the connection opens. Type AUTH TLS and press enter. A reply starting with 234 means the server will negotiate TLS. A 5xx reply means it will not, so that server offers cleartext FTP only. Press Ctrl+C to leave. Doing the same against port 22 prints a line starting with SSH-2.0-, which is SSH, and SFTP rides on it.
An open port 21 on a VPS you did not set up that way deserves a second look before you allow it anywhere: working out whether a port is really open and what is behind it covers the difference between a filtered port and a closed one.
Which one should you use?
Use SFTP for anything with a login. It arrives with openssh-server, it works through the firewall rule you already have, it uses the SSH keys you already manage, and it lets you disable passwords entirely. There is no situation where SSH is working and plain FTP is the better answer.
Use FTPS when a counterparty gives you no choice. Banks and EDI (electronic data interchange) systems still hand out FTPS credentials, and their side will not change for you. Insist on explicit FTPS on port 21 with PROT P enforced, and plan the passive port range as part of the setup instead of meeting it as a surprise on go-live day.
Do not run plain FTP with real accounts, on any network, including one you believe is private. A network you call internal still has guests and borrowed laptops on it.
When is plain FTP still the right answer?
One case survives honestly: anonymous public mirrors. There is no password to steal, because the login is the literal user anonymous, and the files are public by definition, so cleartext costs nothing. Integrity is handled outside the transfer, by publishing checksums and signatures beside the files, which you should verify anyway.
Even that case is closing. Debian shut down its public FTP servers on 1 November 2017, and kernel.org turned FTP off the same year, both in favour of HTTPS and rsync. New mirrors are not built on FTP any more. For the longer version of how the protocol got here, the move from FTP to SSH based transfer fills in the rest.
FAQ
Is SFTP the same as FTPS?
No. They share no code and no port. FTPS is FTP with TLS negotiated on top, using port 21 for control and a separate data connection for every transfer. SFTP is a subsystem of SSH on port 22, where one encrypted connection carries commands and data together. A client that speaks one does not speak the other, so a partner who asks for FTPS cannot be handed an SFTP endpoint instead.
Which ports do I need to open for FTP?
For plain FTP or explicit FTPS: TCP 21, plus the passive port range the daemon is configured to use, for example 40000 to 40100. For implicit FTPS: TCP 990 plus that same passive range. For SFTP: TCP 22 and nothing else. If you open only port 21, the client logs in and then hangs on the first directory listing, because the listing needs a data connection to a port your firewall is dropping.
Why does my FTP client connect but hang on the directory listing?
The control connection on port 21 is working and the data connection is not. Either the passive port range is closed on the firewall, or the server is behind NAT and is advertising its private address in the 227 Entering Passive Mode reply, which the client cannot reach. Set a fixed passive range, tell the daemon its public address, and open that range. The mirror image of this problem is a server that cannot open a connection back to the client, which is fixed by switching the client from active to passive mode.
Can I give someone SFTP access without giving them a shell?
Yes. Set the account shell to /usr/sbin/nologin and add a Match Group sftponly block at the end of /etc/ssh/sshd_config with ForceCommand internal-sftp and a ChrootDirectory. internal-sftp runs inside sshd, so the chroot needs no copied binaries. The chroot directory must be owned by root and must not be group writable, or sshd refuses the session and logs fatal: bad ownership or modes for chroot directory.
Is FTPS faster than SFTP?
Usually neither protocol is the limit. SFTP carries the file inside an SSH channel that applies its own flow control window on top of TCP, so one transfer over a long, high latency link can settle below line rate. FTPS opens a plain TLS connection per file with no extra window, so it can move a large file faster on that kind of link. On an ordinary VPS connection the disk and the network dominate both. Measure with your own file before you change protocols for speed.