Why understand both
FTP (RFC 959, 1985) is an ancient protocol. On modern servers SFTP (part of SSH) took its place. But FTP still shows up:
- Old ISP and hosting panels (cPanel, Plesk)
- Embedded devices (routers, firmware update images)
- Anonymous FTP mirrors of Linux distros (
ftp.debian.org→ now HTTPS) - Legacy CI/CD pipelines
SFTP is the standard for new code. One port, encryption, key-based authentication, works through NAT, needs no separate service when SSH is already there.
FTP, two connections
The defining oddity of FTP: two TCP connections per session:
Client Server
│ │
│ TCP connection to port 21 (control)
├───────────────────────────────────►│
│ USER alice │
│ PASS secret │
│ (commands and replies as text) │
│ │
│ STOR file.bin │
│ ←─── server OPENS data connection to client (active)
│ OR: server says "listening on N", client connects (passive)
│ │
- Control connection (port 21), commands as text:
USER,PASS,LIST,RETR,STOR,QUIT. Replies are three digits plus text (220 Welcome,530 Login incorrect,226 Transfer complete). - Data connection, a separate TCP. It can open in two ways.
Active mode
The client sends PORT a,b,c,d,p1,p2: "I am listening on IP a.b.c.d
port p1*256+p2". The server connects to the client.
The problem: a client behind NAT has no public IP and cannot accept an incoming connection. Active mode rarely works on modern clients.
Passive mode (PASV)
The client sends PASV. The server replies 227 Entering Passive Mode (a,b,c,d,p1,p2): "connect to me on this IP/port". The client connects.
The client initiates both connections.
This is the modern default. But the problem moves to the server: its NAT/firewall must forward a range of ports for PASV data channels.
In vsftpd.conf:
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
pasv_address=203.0.113.10 # for NAT in front of the server
Then open the range 40000-40100 plus 21 in the firewall.
FTPS, FTP plus TLS
Do not confuse this with SFTP. FTPS is FTP with a TLS wrapper:
- Implicit FTPS: TLS handshake immediately on connect to 990
- Explicit FTPS: connect to 21 → command
AUTH TLS→ TLS handshake
This solves encryption but does not solve the two-connection
architecture. The data channel must be encrypted too (PROT P), and the
firewall still has to know about the PASV range.
In 2026 FTPS is a niche legacy. The finance sector uses it for compliance when "the standard says FTP with encryption".
SFTP, this is SSH
SFTP (SSH File Transfer Protocol, not to be confused with simple FTP) is
an SSH subsystem (RFC 4254). Inside the SSH connection it runs
sftp-server, which answers a binary protocol for file operations.
No TCP 21, no PASV/active. One TCP port 22, with the same encryption and
authentication as ssh.
It ships enabled in openssh-server by default, in /etc/ssh/sshd_config:
Subsystem sftp /usr/lib/openssh/sftp-server
Usage:
sftp user@server # interactive shell
sftp> ls
sftp> put localfile remotefile
sftp> get remotefile localfile
sftp> bye
sftp -P 2222 user@server # non-standard SSH port
# batch mode
echo "put file.bin /upload/" | sftp user@server
SCP vs SFTP
scp file.bin user@server:/upload/
- scp (Secure Copy), a plain "rcp over SSH"; one file/directory, no interactivity.
- sftp, a full shell with
cd,ls,mkdir, glob.
Since OpenSSH 9.0 (2022) the default scp uses SFTP under the hood
(legacy mode via -O). This closed several CVEs and tidied up the
behavior (glob expansion, for example).
For simple cases, use scp or rsync -e ssh. For interactive work, use
sftp. For large volumes between servers, [[cmd-rsync|rsync]].
SFTP-only users (chroot)
You often want to grant file upload access without a shell. In sshd_config:
Match Group sftponly
ChrootDirectory /var/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
Requirements for the chroot directory:
- Owner = root, mode 755 (sshd checks this, otherwise it bails with
bad ownership or modes) - Inside it, subdirectories owned by the user can be writable
groupadd sftponly
useradd -G sftponly -s /usr/sbin/nologin alice
mkdir -p /var/sftp/alice/upload
chown root:root /var/sftp/alice
chmod 755 /var/sftp/alice
chown alice:alice /var/sftp/alice/upload
The user sees /upload as the root, no shell, only sftp.
FTP vs SFTP vs FTPS
| Feature | FTP | SFTP | FTPS |
|---|---|---|---|
| Transport | 2 TCP (21 + data) | 1 TCP (22) | 2 TCP + TLS |
| Encryption | no | yes (SSH) | yes (TLS) |
| Authentication | password in the clear | key or password | TLS-cert + password |
| NAT-friendly | no (PASV range) | yes | no |
| Part of standard distro | vsftpd, proftpd | openssh-server | vsftpd-with-tls |
| PKI certificates | - | SSH-CA possible | yes |
| Production choice | avoid | standard | legacy only |
Anonymous FTP
A historical feature: connecting as ftp or anonymous with no password
(or email-as-password). It was used for public mirrors.
Today it is a dead practice. Linux mirrors moved to HTTP/HTTPS (rsync under the hood), and Anonymous FTP is disabled on new vsftpd setups:
anonymous_enable=NO
When something goes wrong
Connection refusedafter login on FTP: PASV is not configured or the port range is not open in the firewall.- FTP "hangs" after
LIST: active mode is being tried, the client is behind NAT. Usequote PASV(or set the client to passive-only). - FTPS fails on the data channel:
PROT Pis not set, or the TLS session is not reused. On vsftpd:require_ssl_reuse=NO(or absent). Subsystem request failedon SFTP:Subsystem sftpis commented out in sshd_config, or the path to sftp-server is wrong.bad ownership or modes for chroot directory: the chroot must belong to root and have mode ≤755. No group-write.scpstarted behaving oddly after OpenSSH 9.0: it now uses SFTP under the hood. Usescp -Ofor the legacy protocol.
Alternatives
- [[cmd-rsync|rsync]] over SSH, for synchronization (incremental)
- HTTPS plus [[cmd-curl|curl]] / wget for public download
- Object storage (S3/MinIO) for applications
- WebDAV, HTTP-based, sometimes more convenient