FTP, FTPS, and SFTP
From the original two-channel design of FTP to the encrypted simplicity of SFTP: how file transfer protocols work, why FTP is dangerous, and what to use in 2026.
The Protocol That Predates Encryption
FTP defined the baseline for file transfer: separate control and data channels, a text-based command protocol, and a small vocabulary of operations (get, put, list, delete). Every subsequent file transfer protocol either built on FTP semantics or explicitly reacted against FTP's weaknesses.
Understanding FTP is not just archaeology. It is the prerequisite for understanding why FTPS, SFTP, and SCP were designed the way they were, and why they make the different choices they do.
FTP: Two Channels, One Design Mistake
The Control Channel (Port 21)
The control channel is a persistent TCP connection to port 21 on the server. Commands (USER, PASS, LIST, RETR, STOR) and responses (3-digit numeric codes like 220, 331, 230) flow over this channel as ASCII text for the lifetime of the session. The control channel stays open even while file data is transferring on a separate connection.
The Data Channel: Active vs. Passive Mode
This is where FTP's fundamental complexity lives. To transfer a directory listing or a file, FTP must open a second TCP connection — the data channel. There are two modes:
Active Mode (PORT)
In active mode, the server initiates the data connection. The client sends a PORT command specifying its IP address and a port it is listening on. The server then connects from its port 20 to the client's specified port. Problem: most clients are behind NAT/firewalls that block inbound connections. Active mode is essentially broken in modern internet environments.
Passive Mode (PASV)
In passive mode, the client initiates both connections. The client sends PASV. The server responds with an IP:port for the client to connect to. The client makes a second TCP connection to that address for data. This works through client-side firewalls but requires the server to open a range of high ports — typically 49152–65535 — in the firewall. NAT devices still need to track the embedded IP in the PASV response (FTP Application Layer Gateways handle this).
# Active mode data channel:
Client: PORT 192,168,1,100,204,98 # client IP + port 52322
Server: connects FROM :20 TO client:52322
# Firewall must allow inbound TCP to client (breaks NAT)
# Passive mode data channel:
Client: PASV
Server: 227 Entering Passive Mode (192,168,1,1,195,149)
# Port = 195*256 + 149 = 50069
Client: connects TO server:50069
# Server's firewall must allow inbound to port 50069FTP Response Codes
FTP responses are three-digit codes where the first digit indicates category: 1xx (positive preliminary — action started), 2xx (positive completion), 3xx (positive intermediate — more input needed), 4xx (transient negative — retry may succeed), 5xx (permanent negative).
Common codes: 220 (service ready), 331 (password required), 230 (login success), 150 (data channel opening), 226 (transfer complete), 425 (can't open data connection), 530 (not logged in).
FTP Session Walkthrough (Passive Mode)
Click any step to see what it means. Yellow = data channel, white = control channel.
Anonymous FTP
Many public FTP servers historically allowed anonymous access — username anonymous, password is your email address (convention, not enforced). Anonymous FTP was the primary distribution mechanism for open-source software before the web. ftp.gnu.org, kernel.org, and university mirrors all ran anonymous FTP. HTTP/HTTPS largely replaced it in the 2000s.
Why FTP is Dangerous
Cleartext Credential Transmission
USER and PASS commands are sent as ASCII text over TCP. Any network observer — on the same LAN segment, a compromised router, a malicious ISP, a coffee-shop attacker — can read your username and password. There is no optional encryption mode in plain FTP.
Cleartext Data Transmission
All file data transfers over the data channel without any encryption. If you upload a database export, a source code archive, or confidential documents via FTP, every byte is readable to network observers.
No Server Authentication
Plain FTP has no mechanism to verify that the server you connected to is the server you intended to connect to. An attacker with control of DNS or ARP can redirect your FTP client to a malicious server that collects your credentials and proxies the real server.
PORT Bounce Attack
In active mode, the PORT command specifies a destination IP and port for the server to connect to. An attacker could use an FTP server as a TCP proxy by issuing PORT commands pointing at third-party hosts — the server would then make connections on behalf of the attacker. Modern FTP servers mitigate this by refusing PORT commands that specify a different IP from the control channel source, but legacy servers remain vulnerable.
FTPS: FTP Over TLS
Explicit FTPS (FTPES)
Explicit FTPS starts with a plain FTP connection to port 21. The client then issues the AUTH TLS command to upgrade the control channel to TLS. This is analogous to SMTP's STARTTLS. The advantage: backward compatibility — clients that don't support TLS can still connect (though they get no security). The server can be configured to require TLS by rejecting non-TLS sessions.
# Explicit FTPS negotiation on port 21
Client → Server: [TCP connect to :21]
Server → Client: 220 FTP server ready
Client → Server: AUTH TLS
Server → Client: 234 AUTH TLS successful
[TLS handshake begins on control channel]
Client → Server: USER alice (now encrypted)
Client → Server: PASS s3cr3t (now encrypted)
Server → Client: 230 Logged in
Client → Server: PBSZ 0 (protection buffer size = 0)
Client → Server: PROT P (private = encrypt data channel too)
Client → Server: PASV
Server → Client: 227 Entering Passive (...)
[Client opens encrypted data channel]Implicit FTPS
Implicit FTPS connects to port 990 and begins TLS immediately — no AUTH TLS command needed. The TLS handshake happens before any FTP commands are exchanged, just like HTTPS vs HTTP. This is simpler but breaks backward compatibility with plain FTP clients. Port 990 is not as widely supported as port 21.
FTPS Data Channel Protection
After securing the control channel, the client must explicitly request data channel encryption with PROT P (private/encrypted). The PBSZ 0 command sets protection buffer size to 0 (required for streaming TLS). If a client omits PROT P, data transfers may still be unencrypted even with an encrypted control channel.
SFTP: Not FTP at All
SFTP Architecture
SFTP runs as a subsystem over an SSH connection. When you connect with an SFTP client to port 22, the SSH handshake completes normally, then the client requests the sftp subsystem via an SSH_MSG_CHANNEL_REQUEST. The server spawns sftp-server as a subprocess connected to the channel. All SFTP communication happens as SSH_MSG_CHANNEL_DATA messages within the encrypted SSH session.
The fundamental difference from FTP: one TCP connection, one SSH session, all data encrypted, firewall-friendly. There are no separate data channels, no mode switching, no PORT/PASV negotiation.
SFTP Protocol: Binary, Stateful, Request-Response
Unlike FTP's ASCII command protocol, SFTP is a binary protocol. Each message has a type byte followed by a request-id (allowing pipelining and out-of-order responses) and binary-encoded fields. Operations are stateful: you open file handles (SSH_FXP_OPEN), read/write with handles (SSH_FXP_READ, SSH_FXP_WRITE), then close them (SSH_FXP_CLOSE).
# SFTP message types (binary protocol, not text)
SSH_FXP_INIT (1) → Client sends protocol version
SSH_FXP_VERSION (2) ← Server responds with supported version (3–6)
SSH_FXP_OPEN (3) → Open file handle (flags: read/write/create)
SSH_FXP_CLOSE (4) → Close handle
SSH_FXP_READ (5) → Read bytes from open handle
SSH_FXP_WRITE (6) → Write bytes to open handle
SSH_FXP_LSTAT (7) → Stat without following symlinks
SSH_FXP_FSTAT (8) → Stat open handle
SSH_FXP_SETSTAT (9) → Set file attributes (chmod, times)
SSH_FXP_OPENDIR (11) → Open directory for listing
SSH_FXP_READDIR (12) → Read directory entries
SSH_FXP_REMOVE (13) → Delete file
SSH_FXP_MKDIR (14) → Create directory
SSH_FXP_REALPATH (16) → Resolve relative path
SSH_FXP_STAT (17) → Stat following symlinks
SSH_FXP_RENAME (18) → Rename/move file
SSH_FXP_STATUS (101) ← Server status response (ok/error codes)SFTP Pipelining
SFTP supports request pipelining — the client can send multiple requests without waiting for responses. Each request has a unique 32-bit request-id; responses may arrive in any order and are matched by id. Modern clients like OpenSSH's sftp and paramiko send 64 outstanding requests by default, dramatically improving throughput on high-latency links.
sftp -B 65536) on high-bandwidth links can multiply throughput significantly. On a 100ms latency link, 32KB window = max ~320 KB/s; 64MB window = potential gigabit speed.SFTP Interactive Command Reference
Filter by category, then click a command to see syntax and example.
SCP: The Simpler Sibling
SCP Legacy Protocol
The original SCP protocol used a simple in-band signalling mechanism: a single TCP-connected SSH session, where the first byte indicates direction (send or receive), and file data is prefixed by a one-line metadata header. It was simple, fast, and had a critical security flaw: the server could inject arbitrary paths in the metadata header, causing files to land in unexpected locations. This was patched in 2019 (CVE-2019-6111) but the protocol was aging poorly.
Modern SCP Uses SFTP Under the Hood
OpenSSH 9.0 (2022) switched the scp command to use the SFTP protocol internally by default. The command-line interface is unchanged, but the wire protocol is SFTP. This eliminates the path injection vulnerability and provides all of SFTP's benefits while keeping the familiar scp user@host:/path file syntax.
# scp uses SFTP protocol since OpenSSH 9.0
# -O flag forces legacy SCP protocol if needed for old servers
scp -O file.txt oldserver:/path/
# For new servers, just use scp normally (SFTP underneath)
scp -r local_dir/ user@server:/remote/
# Tune SFTP buffer size for performance
scp -l 100000 large_file.iso user@server:/iso/ # limit to 100 kbps
# (Use sftp -B 65536 for larger buffer size control)rsync over SSH: Delta Transfers
rsync is not a protocol on its own — it is an algorithm for computing file differences and a tool that can use various transports. When used with SSH (rsync -e ssh or simply rsync user@host:/path), rsync runs the rsync daemon on the remote via the SSH channel. The rsync delta algorithm computes rolling checksums of file blocks; only changed blocks are transferred over the wire.
# rsync performance on changed files:
# File: 1 GB, changed 1 MB
# scp transfer: ~1 GB over wire
# rsync transfer: ~1 MB over wire (only the delta)
# This makes rsync ideal for:
# - Incremental backups
# - Deploying website changes (only modified files transferred)
# - Sync large media libraries
# - Mirror package repositoriesProtocol Comparison: When to Use What
File Transfer Protocol Comparator
Select a protocol to compare features and trade-offs.
Decision Framework
Use SFTP for: all new deployments, automation that needs to connect through firewalls, environments managed by SSH keys or certificates, cloud-to-cloud or cloud-to-on-prem file transfer.
Use FTPS for: systems with existing FTP infrastructure that can add TLS but cannot be replaced with SFTP (e.g., old Windows FTP servers, EDI trading partners requiring FTP semantics), B2B file exchange where the trading partner mandates FTPS.
Use rsync over SSH for: incremental backups, large file synchronization, website deployments, any scenario where only changed portions of files need to transfer.
Avoid plain FTP: on any network other than a loopback or fully isolated, air-gapped internal network. There is no legitimate reason to use plain FTP on the internet in 2026.
SFTP Server Configuration
SFTP-Only Users (No Shell Access)
A common pattern: create users who can transfer files via SFTP but cannot run a shell. This is done by setting their shell to /usr/sbin/nologin or /bin/false and configuring sshd to use the internal-sftp subsystem with a chroot jail.
# /etc/ssh/sshd_config
# Enable internal-sftp subsystem
Subsystem sftp internal-sftp
# SFTP-only chroot configuration
Match Group sftp-users
ChrootDirectory /data/sftp/%u # %u = username
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
AllowAgentForwarding no# Set up an sftp-only user
useradd -m -s /usr/sbin/nologin -G sftp-users alice
mkdir -p /data/sftp/alice/uploads
# Chroot requires the directory to be owned by root, mode 755
chown root:root /data/sftp/alice
chmod 755 /data/sftp/alice
# User's writable directory
chown alice:alice /data/sftp/alice/uploads
chmod 755 /data/sftp/alice/uploads/data/sftp/alice, then /data, /data/sftp, and /data/sftp/alice must all be root:root 755. If any component is writable by the user, the chroot fails with a cryptic "broken pipe" error.SFTP with Key Authentication
SFTP inherits all SSH authentication methods. For automated transfers, public key auth is standard. The authorized_keys file must be placed at the path OpenSSH expects — which, with a chroot, requires special care: authorized_keys must be at the real (non-chrooted) path, not inside the chroot, unless AuthorizedKeysFile is configured accordingly.
# AuthorizedKeysFile location with chroot
# Default path: %h/.ssh/authorized_keys (%h = user's real home dir)
# With chroot at /data/sftp/alice, the .ssh/authorized_keys
# must be at /home/alice/.ssh/authorized_keys (real path)
# or configure AuthorizedKeysFile /etc/ssh/keys/%uAutomating File Transfer: Scripts and Libraries
sftp Batch Mode
# Run sftp commands from a batch file
sftp -b commands.sftp user@server
# commands.sftp content:
cd /uploads
put /local/export_20260524.csv
ls -la
byePython with Paramiko
import paramiko
# Connect and transfer
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.RejectPolicy()) # Never AutoAdd
client.connect(
'sftp.partner.com',
username='transfer_user',
key_filename='/secrets/sftp_ed25519',
port=22
)
sftp = client.open_sftp()
sftp.put('/local/report.csv', '/uploads/report.csv')
sftp.close()
client.close()Node.js with ssh2
const { Client } = require('ssh2');
const fs = require('fs');
const conn = new Client();
conn.on('ready', () => {
conn.sftp((err, sftp) => {
if (err) throw err;
sftp.fastPut(
'/local/data.csv',
'/uploads/data.csv',
{ concurrency: 8, chunkSize: 65536 },
(err) => {
if (err) throw err;
conn.end();
}
);
});
}).connect({
host: 'sftp.partner.com',
port: 22,
username: 'transfer_user',
privateKey: fs.readFileSync('/secrets/id_ed25519'),
hostVerifier: (key) => key.equals(EXPECTED_HOST_KEY_FINGERPRINT),
});RejectPolicy that silently accepts any host key in production automation (equivalent of StrictHostKeyChecking=no). Always pre-load known_hosts or verify the server fingerprint in code. An automated process that accepts any host key is vulnerable to MITM.Firewall and NAT Challenges
FTP NAT Problems
The PASV response embeds the server's IP address in the payload: 227 Entering Passive (192,168,1,1,195,149). If the server is behind NAT, this IP is unreachable from the internet. Solutions: configure the FTP server's masquerade_address or pasv_address to return the public IP; or use a FTP Application Layer Gateway (ALG) in the firewall that rewrites the PASV response.
SFTP has none of this complexity. Single TCP connection, single port 22, works through any NAT without special firewall configuration.
Passive Port Ranges
FTPS servers must have a defined passive port range open in the firewall. Common configuration: ports 50000–50100 (or wider). This increases the attack surface compared to SFTP's single-port design.
# vsftpd passive port configuration
# /etc/vsftpd.conf
pasv_enable=YES
pasv_min_port=50000
pasv_max_port=50100
pasv_address=203.0.113.1 # public IP
# Firewall rule
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
iptables -A INPUT -p tcp --dport 50000:50100 -j ACCEPTSFTP Behind Firewalls
SFTP requires only port 22 inbound to the server. It works through:
— NAT without any special handling (single outbound connection from client)
— Stateful firewalls (one TCP connection, no dynamic port opening needed)
— Load balancers (sticky sessions or connection persistence on port 22)
— Web proxies that support CONNECT tunneling to port 22
File Transfer Security Best Practices
Authentication Security
For all SSH/SFTP servers: disable password authentication (PasswordAuthentication no), require public key auth. For service accounts: generate a dedicated key pair per service, store the private key in a secrets manager, rotate annually or after any suspected compromise.
Least Privilege Access
Use SFTP chroot jails to restrict users to specific directories. Use the ForceCommand internal-sftp directive to prevent shell access for SFTP-only users. Create per-service accounts rather than sharing credentials.
File Encryption in Transit and at Rest
SFTP/FTPS encrypt in transit. For sensitive data, also consider end-to-end encryption of the files themselves using PGP/GPG. Trading partners can encrypt files with your public PGP key before uploading; you decrypt after download. This protects even if the transport is compromised.
# Encrypt a file for a partner's PGP key before SFTP upload
gpg --recipient partner@company.com --output report.csv.gpg --encrypt report.csv
sftp -b - user@partner-server << 'EOF'
put report.csv.gpg /incoming/
EOFLogging and Auditing
Enable verbose logging in sshd: LogLevel VERBOSE records every authentication attempt, file operation (with internal-sftp), and disconnection. Ship logs to a SIEM. For SFTP-only servers, internal-sftp logs file operations (open, read, write, close) to syslog — essential for compliance and incident response.
Modern File Transfer Alternatives
S3 Pre-Signed URLs
AWS S3 pre-signed URLs grant time-limited access to upload or download specific objects without AWS credentials. Generate a URL that expires in 15 minutes; share it with a partner; they PUT/GET directly to S3 over HTTPS. No SFTP server required.
# Generate a 15-minute PUT pre-signed URL
aws s3 presign s3://my-bucket/incoming/data.csv --expires-in 900 --method PUT
# Partner uploads directly:
curl -X PUT --upload-file data.csv "https://my-bucket.s3.amazonaws.com/incoming/data.csv?X-Amz-..."
# For AWS Transfer Family: managed SFTP backed by S3
# - Users connect via SFTP to AWS endpoint
# - Files land in S3 automatically
# - No EC2/server to manageAWS Transfer Family
AWS Transfer Family provides a managed SFTP/FTPS/FTP endpoint backed by S3 or EFS. Users connect to the endpoint with standard SFTP clients; files appear in S3 buckets. No server management, automatic scaling, built-in CloudWatch metrics. Ideal for B2B file exchange with trading partners who require SFTP without the overhead of running your own server.
Managed File Transfer (MFT) Platforms
Enterprise MFT platforms (GoAnywhere, IBM Sterling, Axway) add business-level features on top of SFTP: scheduled transfers, event-driven workflows, non-repudiation receipts, audit trails, compliance reporting (PCI-DSS, HIPAA), and integration with enterprise directories. They are the right choice when FTP/SFTP is a core business process with regulatory requirements.
Misconceptions About File Transfer Protocols
scp command to use SFTP under the hood in version 9.0. The command scp is not deprecated — it is now safer. Only the legacy wire protocol is discouraged. Use scp freely; it now uses SFTP unless you force the old protocol with -O.IQ Depth Check: File Transfer Protocol Mastery
FTP sends all data, including credentials, in plaintext and uses two TCP connections (control on port 21, data on a separate port). SFTP encrypts everything using SSH (port 22), uses a single TCP connection, and is not related to FTP despite the similar name — it is a completely different protocol designed from scratch.
In active mode, the server initiates the data connection back to the client — this breaks NAT and client-side firewalls because it requires an inbound connection to the client. In passive mode, the server opens a random high port and tells the client which port to connect to; the client initiates both connections. Passive mode works through client-side NAT and firewalls because all connections are client-initiated. It is more commonly used because most clients are behind NAT/firewalls, but it still requires the server's firewall to allow a range of high ports.
SFTP is a request-response binary protocol where each request has a 32-bit request-id. The client can send multiple requests without waiting for responses — up to a configurable window of outstanding requests (OpenSSH default: 64). Responses arrive with matching request-ids and can be processed out of order. On a high-latency link (e.g., 100ms RTT), without pipelining you are limited to one round-trip per operation: 10 read requests × 100ms = 1 second minimum. With 64 concurrent requests, all 64 can be in flight simultaneously, reducing 64 round-trips to effectively 1. This is why tuning
-B (buffer size) on sftp can dramatically improve throughput on WAN links.When sshd performs a
chroot(2) syscall to jail an SFTP user, the kernel changes the process's notion of root. POSIX requires that a process doing chroot must be root (CAP_SYS_CHROOT). OpenSSH additionally enforces that the chroot directory and all ancestors are not writable by anyone other than root. The reason: if a user could write to any directory in the chroot path, they could create symlinks that escape the jail via hardlink/symlink race conditions. Specifically, a writable directory allows creating a symlink from a name inside the chroot to an absolute path outside; combined with directory traversal, this breaks the containment. The check is done in session.c (OpenSSH source) via safe_path(), which walks the directory tree verifying owner and mode. If any component fails, sshd logs "bad ownership or modes for chroot directory" and closes the connection — intentionally unhelpful to prevent information disclosure to attackers about the exact failure.🎯 Key Takeaways
- ✓FTP uses two TCP channels: a persistent control channel (port 21) and a per-transfer data channel (active: server-initiated; passive: client-initiated to an ephemeral server port).
- ✓Plain FTP transmits credentials and data in cleartext — never use it on the internet or any untrusted network.
- ✓FTPS adds TLS to FTP: Explicit FTPS upgrades port 21 with AUTH TLS; Implicit FTPS uses port 990 with immediate TLS. The two-channel architecture remains.
- ✓SFTP is a completely separate protocol — not FTP with encryption. It runs as an SSH subsystem over a single TCP connection on port 22 with no separate data channel.
- ✓SFTP is firewall-friendly: single port 22, client-initiated, works through NAT without special configuration.
- ✓The sftp binary protocol supports pipelining (multiple outstanding requests), enabling high-throughput transfers on high-latency links with appropriate buffer tuning.
- ✓Modern OpenSSH scp uses SFTP internally (since version 9.0); the legacy SCP protocol had path injection vulnerabilities.
- ✓rsync over SSH uses delta transfer — only changed file blocks are transmitted — making it ideal for incremental backups and deployments.
- ✓SFTP chroot jails require the chroot directory and all ancestors to be owned by root with no world/group write permission; sshd performs this check before calling chroot(2).
- ✓AWS Transfer Family, S3 pre-signed URLs, and MFT platforms are modern alternatives to self-managed SFTP servers for cloud-native and enterprise file transfer.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.