Safeguard
Supply Chain Security•

FTP vs SFTP vs FTPS: How Each Protocol Works and Which to Use

FTP sends passwords in cleartext. FTPS adds TLS to FTP's two connections; SFTP is a separate SSH subsystem on port 22. Use SFTP unless FTPS is required.

Safeguard Security Team
44 min read

FTP, FTPS and SFTP all move files between a client and a server, but only two of them protect anything. FTP (RFC 959, October 1985) sends usernames, passwords, commands and file contents in cleartext over a control connection plus separate data connections. FTPS is the same FTP with TLS layered onto those connections (RFC 4217, October 2005), and SFTP is an unrelated protocol that runs as a subsystem inside an SSH session, normally on TCP port 22.

So the names mislead: FTPS is FTP made secure, while SFTP shares three letters with FTP and nothing else on the wire. That decides how many ports you open, how firewalls behave, how you authenticate and how you lock a partner into one directory. This post walks through each protocol on the wire, checked against the RFCs, the SFTP draft and the OpenSSH, vsftpd and ProFTPD manuals. If you only need the basics of classic FTP, our earlier post on what FTP means and why it is insecure covers that ground; this one is the three-way comparison.

Key takeaways

  • FTP is defined by RFC 959 (October 1985, STD 9), which obsoleted RFC 765. It uses a control connection on TCP port 21 and a new TCP data connection for every file or directory listing. RFC 2577 (May 1999) states plainly that all data and control information, "including passwords", crosses the network unencrypted.
  • FTPS adds TLS. Explicit FTPS starts in cleartext on port 21 and upgrades with AUTH TLS, then uses PBSZ 0 and PROT P to protect data connections (RFC 4217, built on the RFC 2228 security extensions of October 1997). Implicit FTPS starts TLS immediately, conventionally on port 990; IANA registers 990 and 989 for it, but no RFC standardises the implicit mode.
  • SFTP is the SSH File Transfer Protocol. It is requested as the sftp subsystem over an SSH connection channel (RFC 4254, January 2006). OpenSSH implements protocol version 3 from draft-ietf-secsh-filexfer-02 (October 2001). The SFTP drafts never became an RFC; the last revision, draft 13 for version 6, expired in 2007.
  • FTPS is hard to firewall because TLS hides the PASV and PORT messages that announce each data port. SFTP uses one TCP connection.
  • The weakness classes: cleartext credentials, the bounce attack and port stealing (RFC 2577), TLS that silently falls back, skipped certificate or host key checks, and chroot jails that leave forwarding open.
  • For most new work, SFTP with key based authentication and an OpenSSH Match block that forces internal-sftp into a root-owned ChrootDirectory is the simplest secure choice. Use FTPS when a partner or regulation requires it, and HTTPS uploads when the client is a browser or an application calling an API.

A short dated timeline

DateEventSource
1971First proposed file transfer mechanisms for MIT hosts in RFC 114RFC 959, section 2.1
Oct 1985RFC 959, the File Transfer Protocol, obsoletes RFC 765RFC 959
Feb 1994RFC 1579 "Firewall-Friendly FTP" recommends clients use PASVRFC 1579
Oct 1997RFC 2228 FTP Security Extensions: AUTH, ADAT, PROT, PBSZ, CCC, MIC, CONF, ENCRFC 2228
Sep 1998RFC 2428 adds EPRT and EPSV for IPv6 and NATRFC 2428
May 1999RFC 2577 FTP Security Considerations: bounce attack, brute force, port stealingRFC 2577
Oct 2001draft-ietf-secsh-filexfer-02, SSH File Transfer Protocol version 3IETF draft
Oct 2005RFC 4217 Securing FTP with TLSRFC 4217
Jan 2006SSH protocol published as RFC 4251 to RFC 4254RFC 4251, RFC 4254
Jul 2006draft-ietf-secsh-filexfer-13 (SFTP version 6), last revision; expiredIETF datatracker
Mar 2007RFC 3659 adds MLST, MLSD, SIZE, MDTM and REST semantics to FTPRFC 3659
Mar 2021RFC 8996 (BCP 195) deprecates TLS 1.0 and 1.1 and updates RFC 4217RFC 8996
8 Apr 2022OpenSSH 9.0 switches scp to the SFTP protocol by defaultOpenSSH 9.0 release notes

What each protocol is for, its layer and its standard

All three are application layer protocols that run over TCP. They differ in what sits between TCP and the file commands.

FTP: RFC 959, October 1985

RFC 959 describes itself as "the official specification of the File Transfer Protocol". It lists four objectives: to promote sharing of files, to encourage indirect use of remote computers, to shield users from differences between file storage systems, and "to transfer data reliably and efficiently". It obsoletes RFC 765 and is Internet Standard STD 9, the label RFC 2228 uses when it refers to it.

The RFC Editor lists six RFCs that update it, including RFC 2228 (security extensions), RFC 3659 (extensions), RFC 2640 (internationalization) and RFC 7151 (the HOST command). RFC 2428 adds IPv6 and NAT-friendly commands, and RFC 2577 is the security considerations document behind most of the hardening advice below.

FTP sits directly on TCP. There is no framing layer, no encryption and no message authentication. Commands are lines of text ending in CRLF, and replies are three digit codes followed by text.

FTPS: RFC 2228 plus RFC 4217

FTPS is FTP with TLS. RFC 2228 (October 1997, Standards Track) created a generic security framework for FTP: an AUTH command to choose a mechanism, ADAT to carry security data, PBSZ and PROT to protect the data channel, CCC to clear the command channel, MIC, CONF and ENC for protected commands, and a new 6yz reply class for protected replies. RFC 2228 notes that none of these commands is required to be implemented.

RFC 4217 (October 2005, Standards Track) then says exactly how to use those commands with TLS. Its introduction describes it as combining RFC 959, the TLS specification and RFC 2228 "to provide a useful, interoperable, and secure file transfer protocol", in the same way STARTTLS works for SMTP. RFC 8996 (March 2021) updates RFC 4217 as part of deprecating TLS 1.0 and TLS 1.1.

SFTP: an SSH subsystem, specified in an expired draft

SFTP is not FTP. The draft that most implementations follow, draft-ietf-secsh-filexfer-02 (October 2001), says the protocol "provides secure file transfer (and more generally file system access) functionality over a reliable data stream, such as a channel in the SSH2 protocol". It assumes it "runs over a secure channel" and that "the server has already authenticated the user". All of the cryptography and authentication come from SSH:

  • The SSH Transport Layer Protocol (RFC 4253) provides server authentication, confidentiality and integrity. RFC 4253 says the server "normally listens for connections on port 22".
  • The SSH User Authentication Protocol (RFC 4252) authenticates the client user.
  • The SSH Connection Protocol (RFC 4254) multiplexes the encrypted tunnel into channels. A client opens a session channel and sends a subsystem request; RFC 4254 says subsystems are "expected" to "include a general file transfer mechanism".

The SFTP draft was revised up to draft 13 (July 2006), which describes protocol version 6, and the IETF datatracker lists it as an expired Internet-Draft that never reached RFC status. OpenSSH's own specification page lists draft-ietf-secsh-filexfer-02, "SSH File Transfer Protocol version 3", as the version it implements, plus named extensions such as copy-data (from OpenSSH 9.0) and home-directory (from OpenSSH 9.1). In practice, version 3 is what you will meet.

Ports and IANA registrations

ServicePortIANA service nameDefined by
FTP control21/tcpftpRFC 959
FTP default data (active mode)20/tcpftp-dataRFC 959
Explicit FTPS21/tcp (same as FTP)ftpRFC 4217, section 16
Implicit FTPS control990/tcpftpsIANA registration, no RFC
Implicit FTPS data989/tcpftps-dataIANA registration, no RFC
SSH, and therefore SFTP and SCP22/tcpsshRFC 4251, RFC 4253
Simple File Transfer Protocol (not SSH SFTP)115/tcpsftpIANA registration

That last row trips people up. The IANA service name sftp on port 115 belongs to the old Simple File Transfer Protocol, not to the SSH File Transfer Protocol. SSH SFTP has no port of its own; it is whatever port your SSH server listens on.

How FTP works on the wire

Two kinds of connection

RFC 959 describes a protocol interpreter (PI) that handles commands and a data transfer process (DTP) that moves bytes. The user PI opens a control connection to the server on port L, which RFC 959 assigns as 21. Commands and replies travel on that connection for the whole session. Every file transfer and every directory listing uses a separate data connection, opened just for that transfer and closed at the end of it in the default stream mode.

RFC 959 sets default data ports too. The server's default data port is "adjacent to the control connection port (i.e., L-1)", so port 20, and the user's default data port is the same as its control port. Those defaults are rarely used today; clients almost always ask for a specific data port with PORT or PASV.

Command and reply format

There is no binary header in FTP. Each control message is a text line, which makes the "header layout" easy to read and easy for anyone on the path to read too.

ElementFormatExampleMeaning
Command3 or 4 letter verb, optional argument, CRLFUSER fredIdentify the user
PasswordPASS plus the password in cleartext, CRLFPASS secretRFC 2577: sent "in clear text"
Reply3 digit code, space or hyphen, text, CRLF230 User logged inFirst digit gives the class: 1 preliminary, 2 success, 3 intermediate, 4 transient failure, 5 permanent failure
Multi-line replycode plus hyphen on the first line, code plus space on the last211-Extensions supportedUsed by FEAT
PORT argumentsix decimal fields separated by commasPORT h1,h2,h3,h4,p1,p232 bit IPv4 address, then a 16 bit port as two 8 bit fields
PASV reply227 with the same six fields227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)Address and port the server is listening on
EPSV reply229 with only a port, between delimiterssee the RFC 2428 example belowNo address, so NAT does not need to rewrite it

The six-field form encodes the port as p1 times 256 plus p2. RFC 959's minimum implementation requires TYPE ASCII Non-print, MODE Stream and STRUcture File as defaults, and the commands USER, QUIT, PORT, TYPE, MODE, STRU, RETR, STOR and NOOP.

Active mode: the server connects back

In active mode the client tells the server where to connect with PORT, then the server opens the data connection to the client.

Client (control from port U)                Server (control on 21)
  TCP connect  --------------------------->  accept
               <---------------------------  220 Service ready for new user
  USER fred    --------------------------->
               <---------------------------  331 User name okay, need password
  PASS secret  --------------------------->   (cleartext on the wire)
               <---------------------------  230 User logged in, proceed
  PORT 192,0,2,10,195,80  ---------------->   (client listens on 50000)
               <---------------------------  200 Command okay
  RETR report.csv -------------------------->
                         Server data port 20 connects to client port 50000
               <---------------------------  150 File status okay; about to open data connection
               <====== file bytes on the data connection ======
               <---------------------------  226 Closing data connection
  QUIT         --------------------------->
               <---------------------------  221 Service closing control connection

Active mode needs the client to accept an inbound connection. RFC 1579 (February 1994) recommended passive mode for clients behind firewalls, and RFC 2428 explains why: firewalls "do not generally allow incoming connections".

Passive mode: the client connects out

In passive mode the client sends PASV, the server opens a listening port and replies with its address and port, and the client connects to it.

Client                                      Server
  PASV         --------------------------->
               <---------------------------  227 Entering Passive Mode (198,51,100,7,195,81)
  Client connects to 198.51.100.7:50001 ------>  (data connection)
  STOR upload.zip -------------------------->
               <---------------------------  150 File status okay; about to open data connection
  ====== file bytes on the data connection ======>
               <---------------------------  226 Closing data connection

RFC 2428 generalised both commands for IPv6. EPRT takes a protocol number, an address and a port between delimiters, and EPSV returns only a port:

EPRT |1|132.235.1.2|6275|
EPRT |2|1080::8:800:200C:417A|5282|
229 Entering Extended Passive Mode (|||6446|)

RFC 2428 says EPSV "MUST be used" when control and data connections run between the same two machines, notes that it avoids NAT rewriting the address, and defines EPSV ALL, after which the server must reject every other data connection setup command for the session.

Why FTP is hard on firewalls and NAT

Because each data port is announced inside the control connection, FTP-aware firewalls and NAT devices must read the control channel to open the right port, and NAT must rewrite addresses inside PORT and PASV. That works for plaintext FTP and breaks once the control channel is encrypted.

How FTPS works on the wire

Explicit FTPS: AUTH TLS on port 21

RFC 4217 section 4 says the server "listens on the normal FTP control port" and "the session initiation is not secured at all". The client asks for TLS by sending AUTH TLS; the server answers 234 if it will proceed, and both sides run a TLS handshake on the existing control connection. RFC 4217 keeps TLS-C as a synonym the server "SHOULD accept" for compatibility. After AUTH, RFC 2228 and RFC 4217 require the user to log in again and reset transfer parameters, "almost as if a REIN command was issued".

This is the timing diagram from RFC 4217 section 12.1, lightly simplified:

Client control                               Server control (port 21)
  connect()  -------------------------------->  accept()
             <--------------------------------  220
  AUTH TLS   -------------------------------->
             <--------------------------------  234
  TLS handshake  <=========================>  TLS handshake
  PBSZ 0     -------------------------------->   (now inside TLS)
             <--------------------------------  200
  PROT P     -------------------------------->
             <--------------------------------  200
  USER fred  -------------------------------->
             <--------------------------------  331
  PASS pass  -------------------------------->   (encrypted)
             <--------------------------------  230

Then, for each transfer, the client sends PASV or EPSV (or PORT), opens the data connection, and runs a fresh TLS handshake on that data connection before any file bytes move.

PBSZ and PROT: protecting the data connection

AUTH TLS protects only the control connection. RFC 4217 section 8 says that to protect the data channel "the PBSZ command, followed by the PROT command sequence, MUST be used". PBSZ 0 tells the server that TLS is a streaming mechanism and no buffering applies. PROT then sets the level. RFC 2228 defines four levels, but for TLS only two are used:

PROT valueRFC 2228 nameUsed with TLS?What the data connection gets
CClearYesNo TLS: "unauthenticated", no confidentiality or integrity
SSafeNoIntegrity without privacy (not used by TLS)
EConfidentialNoPrivacy without integrity (not used by TLS)
PPrivateYesFull TLS negotiation on each data connection

RFC 4217 section 9 is explicit that "the initial state of the data connection MUST be 'Clear'". A client that sends AUTH TLS but never sends PROT P gets an encrypted login and cleartext files. That is legal under the RFC, and it is the most common FTPS misconfiguration.

Who is the TLS client on a data connection

In active mode the server opens the TCP data connection to the client, which raises the question of which side starts the TLS handshake. RFC 4217 section 7 answers it: "the FTP client, as defined in RFC 959, is always the TLS client", whichever side called connect(). Server-to-server transfers, where a client tells server A to send directly to server B, are explicitly excluded because neither server can be the TLS client. RFC 2228's security considerations say third party transfers "cannot be secured using these extensions".

CCC: clearing the control channel

RFC 4217 section 5 describes CCC, which turns TLS off on the control connection after login while keeping data connections protected. The stated reason is NAT and firewall devices that "expect to be able to examine the content of the control connection". RFC 2228 warns that once commands are unprotected "an attacker could insert a command on the control stream, and the server would have no way to know that it was invalid". RFC 4217 says clients "should not assume that a server will allow the CCC command" and suggests servers refuse it before client authentication. Treat CCC as a compatibility workaround, not a feature.

Implicit FTPS on port 990

Implicit FTPS skips AUTH TLS. The client starts a TLS handshake as soon as the TCP connection opens, before any FTP greeting. IANA lists ftps on 990/tcp ("ftp protocol, control, over TLS/SSL") and ftps-data on 989/tcp, but RFC 4217 does not define implicit mode; it only defines the explicit AUTH TLS flow on port 21. Server documentation treats implicit mode as a separate listener. The vsftpd manual says implicit_ssl makes "an SSL handshake the first thing" expected on all connections and that explicit or plaintext support needs "a separate vsftpd listener process". ProFTPD's UseImplicitSSL option is documented with an example virtual host on port 990, which the manual calls the "standard" implicit FTPS port, in quotes.

Why FTPS fights firewalls

FTPS keeps FTP's per-transfer data ports, but the 227 or 229 reply that announces each one is now encrypted, so a middlebox cannot read it. Operators pin the server's passive port range (vsftpd's pasv_min_port and pasv_max_port exist "to assist firewalling") and open exactly that range, or fall back to CCC with the risk above. SFTP avoids the question by using one connection.

How SFTP works on the wire

Inside an SSH connection

SFTP starts only after SSH has done its work. The SSH transport layer runs key exchange and server host key verification, user authentication runs (public key, password or another method), and then the connection layer opens a session channel. On that channel the client sends an SSH_MSG_CHANNEL_REQUEST of type subsystem with the name sftp, as RFC 4254 section 6.5 and the SFTP draft's section 2 describe. From then on, SFTP packets are just bytes in that encrypted channel.

Client                                         Server (TCP 22)
  TCP connect  ---------------------------------->
  SSH version exchange, key exchange, host key check (RFC 4253)
  User authentication, e.g. publickey (RFC 4252)
  SSH_MSG_CHANNEL_OPEN "session"  --------------->
                <--------------------------------  CHANNEL_OPEN_CONFIRMATION
  CHANNEL_REQUEST "subsystem" "sftp" ------------>
                <--------------------------------  CHANNEL_SUCCESS
  SSH_FXP_INIT  version 3  ---------------------->
                <--------------------------------  SSH_FXP_VERSION 3 + extensions
  SSH_FXP_OPEN  id=1 "upload.zip" WRITE|CREAT|TRUNC ->
                <--------------------------------  SSH_FXP_HANDLE id=1 handle
  SSH_FXP_WRITE id=2 handle offset=0 data  ------>
  SSH_FXP_WRITE id=3 handle offset=32768 data --->   (pipelined)
                <--------------------------------  SSH_FXP_STATUS id=2 OK
                <--------------------------------  SSH_FXP_STATUS id=3 OK
  SSH_FXP_CLOSE id=4 handle  -------------------->
                <--------------------------------  SSH_FXP_STATUS id=4 OK

There is no second TCP connection, no data port and nothing for a firewall to learn from the payload. Port 22 (or whatever your SSH server uses) is the only port.

Packet format

Every SFTP packet has the same small header, from section 3 of draft-ietf-secsh-filexfer-02:

FieldTypeMeaning
lengthuint32Length of the rest of the packet, not counting this field
typebytePacket type, such as SSH_FXP_OPEN (3) or SSH_FXP_STATUS (101)
request iduint32Present in every request and response except INIT and VERSION; matches replies to requests
payloadtype specificFilename, flags, handle, offset, data, attributes and so on

The draft defines request types from SSH_FXP_INIT (1) to SSH_FXP_SYMLINK (20), response types SSH_FXP_STATUS (101), HANDLE (102), DATA (103), NAME (104) and ATTRS (105), and SSH_FXP_EXTENDED (200) and EXTENDED_REPLY (201) for vendor extensions named in the form name@domain. Servers "SHOULD support packets of at least 34000 bytes", which allows reads and writes of up to 32768 bytes each.

Requests, handles and pipelining

SFTP looks more like a remote file system API than like FTP. A client opens a file with SSH_FXP_OPEN and flag bits such as SSH_FXF_READ, SSH_FXF_WRITE, SSH_FXF_CREAT and SSH_FXF_TRUNC, receives an opaque handle, then reads and writes at explicit offsets. Other requests cover directory listing, stat, rename, remove, mkdir and symlinks.

The draft allows many outstanding requests at once. The server must make the results look as if overlapping requests on the same file ran in order, but responses may arrive in any order, matched by request id. That pipelining is what makes SFTP fast over high-latency links; the OpenSSH scp manual says its SFTP mode allows 64 concurrent read or write requests by default.

SCP is now SFTP underneath

The legacy SCP protocol ran a remote scp command through the user's shell. The OpenSSH 9.0 release notes (8 April 2022) explain the problem: legacy scp/rcp "performs wildcard expansion of remote filenames ... through the remote shell", so shell metacharacters in filenames needed double quoting or "they could be interpreted as shell commands on the remote side". OpenSSH 9.0 switched scp to "using the SFTP protocol by default", and the current scp manual says -O forces "the legacy SCP protocol" only for servers that need it. On a current OpenSSH client, scp and sftp are two interfaces to the same subsystem.

Versions and history

FTP

FTP is older than TCP/IP as we use it today. RFC 959's own history section traces it to RFC 114 in 1971 and a series of ARPANET documents through the 1970s before the 1985 specification, which added the optional commands CDUP, SMNT, STOU, RMD, MKD, PWD and SYST. Since then FTP has changed only by extension, as the timeline above shows: passive mode guidance, the security framework, IPv6 and NAT commands, and RFC 3659's SIZE, MDTM and machine-readable MLST and MLSD listings, which servers advertise through FEAT and which spare automation from screen-scraping LIST output.

Plain FTP survives in public download mirrors, embedded devices and legacy integrations, none of which changes its lack of confidentiality.

FTPS

RFC 4217 standardised explicit negotiation on port 21 and notes that it can run over "either SSL or TLS"; the implicit model on port 990 lives on only through IANA registrations and server support. RFC 8996 (March 2021) updates RFC 4217 so TLS 1.0 and 1.1 must not be used. In 2026 an FTPS server should offer TLS 1.2 and TLS 1.3 only; our post on TLS 1.3 vs TLS 1.2 and what to disable covers cipher choices, and what TLS is covers the basics.

SFTP

The SFTP draft's own change log lists what each version added. Version 1 was implementation changes only. Version 2 added SSH_FXP_RENAME. Version 3 added READLINK, SYMLINK, the extended request types, and error message and language tag fields in SSH_FXP_STATUS. Later drafts went on to protocol version 6, but because the drafts expired without becoming an RFC, the version that OpenSSH implements is version 3, with named extensions for anything newer. OpenSSH's specification page and sftp-server's -Q requests output are the reliable guide to what your server actually supports.

Which versions are in use

ProtocolWhat you will see in practiceWhat to avoid
FTPRFC 959 plus FEAT, EPSV, MLSDAny use that carries credentials or sensitive files
FTPSExplicit AUTH TLS on 21; implicit on 990 for older clientsTLS 1.0 and 1.1 (RFC 8996), SSLv2 and SSLv3, PROT C, unrestricted CCC
SFTPVersion 3 over SSH-2, with OpenSSH extensionsLegacy SCP mode (scp -O) unless a server requires it

Security properties and known weakness classes

What each protocol guarantees

PropertyFTPExplicit FTPS, PROT PSFTP over SSH
Confidentiality of credentialsNoneTLS on the control channelSSH transport encryption
Confidentiality of file dataNoneTLS on each data connectionSSH transport encryption
Integrity of commands and dataNone (TCP checksums only)TLS record integritySSH MAC or AEAD
Server authenticationNoneX.509 certificate, if the client checks itSSH host key, if the client checks it
Client authenticationPassword in cleartextPassword inside TLS, optionally client certificatesPublic key, certificates, password or other SSH methods
Binding of data to control sessionIP address checks onlyTLS session reuse or matching certificates, if enforcedSingle channel, inherent
Firewall friendlinessPoor (dynamic ports, inspection)Worst (dynamic ports, encrypted announcements)Good (one TCP port)

Cleartext credentials and data

RFC 2577 section 6 says standard FTP sends all data and control information, including passwords, "in unencrypted form", and section 5 says it sends passwords "in clear text using the PASS command". Anyone on the path can read them. Our post on what an eavesdropper can see in TCP and UDP explains why TCP itself adds no protection.

The bounce attack and port abuse

FTP lets the client name any address and port in a PORT command. RFC 2577 section 2 explains the consequence: a client can tell an FTP server to open a data connection to a third machine's service, so the server becomes a relay that hides who is really connecting and can bypass address-based access rules. RFC 2577 cites a 1997 CERT advisory on it. The fixes in section 3 are design level: do not open data connections to ports below 1024 (reply 504 instead), protect upload areas so attackers cannot stage content, or disable PORT and rely on PASV. Modern servers go further and refuse PORT addresses that differ from the client's own; vsftpd's port_promiscuous option, off by default, disables "the PORT security check that ensures that outgoing data connections can only connect to the client". RFC 4217 notes TLS makes bouncing harder because the FTP server is always the TLS server.

Port stealing and data connection hijack

RFC 2577 section 8 describes port stealing. If a server hands out passive ports in a predictable order, another client can connect to the next port before the legitimate client does, taking a file meant for someone else or inserting a forged file. The recommended fix is random data ports. RFC 2577 section 4 adds that servers should check that the control and data connections come from the same address. vsftpd's pasv_promiscuous setting turns that check off and the manual warns "Only enable if you know what you are doing!"

FTPS raises the bar only if the data connection is tied to the control session. RFC 4217 section 15.1.2 notes that without client certificates "it would still be possible for a malicious client to hijack a data connection". Server implementations close that gap by requiring TLS session reuse: vsftpd's require_ssl_reuse (default YES) requires data connections to show they "know the same master secret as the control channel", and ProFTPD's mod_tls has required session reuse since 1.3.3rc1 unless NoSessionReuseRequired is set.

Opportunistic TLS and silent downgrade

Explicit FTPS begins in cleartext. RFC 4217 section 4.1 says a client that receives an AUTH rejection "MAY choose to continue with the session unprotected". If a client is configured to try TLS but carry on without it, anyone who can tamper with the control connection can strip the AUTH TLS offer and the client will send its password in the clear. The defence is on both ends: the client must require TLS, and the server must refuse to accept USER and PASS before TLS, which RFC 4217 section 4.2 says is how a server enforces a secured session.

Certificate and host key validation gaps

Encryption without authentication only moves the eavesdropper into the middle. RFC 4217 section 15.1.1 recommends that FTPS server certificates carry the server's name the way HTTPS certificates do, that the data connections use the same certificate as the control connection, and warns that with anonymous Diffie-Hellman "there is little protection against man-in-the-middle attacks". Automated FTPS clients that accept any certificate to make an integration work are a recurring gap. Our explainer on certificate authorities covers what a client should check.

SFTP has the same problem one layer down: a script that disables host key checking will authenticate to whoever answers on port 22. Pin host keys in known_hosts. The live post on the Terrapin SSH prefix truncation attack shows how SSH transport weaknesses are handled, and Ed25519 vs RSA SSH keys covers key choice.

Command injection after CCC

RFC 2228 warns that if unprotected commands are allowed on any connection, "an attacker could insert a command on the control stream". CCC creates exactly that state after login. If you allow it at all, ProFTPD's TLSRequired auth+data shows the narrowest policy: TLS for authentication and data, with the control channel cleared only after login.

Brute force and username discovery

RFC 2577 notes that FTP allows unlimited password attempts. It recommends closing the control connection with reply 421 after three to five failures, a 5 second delay after a bad PASS, and always replying 331 to USER so valid usernames cannot be told apart from invalid ones.

Anonymous access and dangerous extensions

RFC 2577 section 9 lists features that "has been abused in the past": anonymous FTP that can read or write too much, the optional SITE EXEC extension that runs commands on the server, and debug code left enabled. vsftpd's anonymous_enable defaults to YES in its manual, so check it explicitly.

Over-permissive chroots and forced commands

The sshd_config manual says ChrootDirectory requires that "all components of the pathname are root-owned directories which are not writable by group or others", and warns that "misconfiguration can lead to unsafe environments which sshd(8) cannot detect". It also says ForceCommand "does not limit other kinds of access", such as TCP, agent, socket or X11 forwarding, which "must be explicitly disabled". A partner account with ForceCommand internal-sftp but forwarding left on can still use your server as a network pivot. On the FTP side, the vsftpd manual warns that chroot_local_user "has security implications, especially if the users have upload permission, or shell access".

SFTP handle validation and server-side access control

The SFTP draft says the protocol gives access to arbitrary files, "only constrained by the server implementation", and that servers "MUST check the validity of each handle". SFTP has no access model of its own; it depends on SSH authentication, account permissions and chroot.

Secure configuration, with defender-side checks

Only run these commands against systems you own or are authorised to test. Every command and directive below comes from the official manual linked in the Sources section.

1. See what is listening

The ss manual (man7.org) documents -t for TCP sockets, -l for listening sockets, -n to skip name resolution and -p to show processes:

ss -tlnp

Look for 21, 990, 989 and 22, and for any unexpected passive port range. Plain FTP on 21 that faces the internet, with no business reason, is the first thing to remove. Our cloud security group review for SSH and RDP applies the same exposure logic to port 22.

2. Check FTPS from the client side with OpenSSL

The OpenSSL s_client manual documents -starttls ftp, which sends the FTP-specific messages to switch to TLS, along with -servername, -verify_return_error and -brief:

# Explicit FTPS on port 21: does the server accept AUTH TLS, and which TLS version?
openssl s_client -connect ftp.example.com:21 -starttls ftp \
  -servername ftp.example.com -verify_return_error -brief

# Implicit FTPS on port 990: TLS from the first byte
openssl s_client -connect ftp.example.com:990 \
  -servername ftp.example.com -verify_return_error -brief

With -verify_return_error the handshake aborts if the chain does not verify. Run it again without -brief to read the full certificate chain and confirm the subject names the host your clients connect to. If the explicit test fails because the server never offers TLS, you are running plain FTP.

3. Harden vsftpd for FTPS

These directives are documented in the vsftpd.conf manual on the vsftpd site:

listen_port=21
anonymous_enable=NO
local_enable=YES
ssl_enable=YES
force_local_logins_ssl=YES
force_local_data_ssl=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=YES
port_promiscuous=NO
pasv_promiscuous=NO
pasv_min_port=50000
pasv_max_port=50100
xferlog_enable=YES

ssl_enable defaults to NO in the manual. force_local_logins_ssl and force_local_data_ssl stop passwords and data from crossing in the clear once SSL is on. The passive range lets you open a narrow, known set of ports. Two caveats: the published manual still describes TLS in terms of a ssl_tlsv1 switch and lists an old default for ssl_ciphers, so check the man page that ships with your distribution's vsftpd build for its current TLS version and cipher options, and set ssl_ciphers explicitly rather than relying on the default. If you need implicit FTPS, the manual says to run a separate listener with implicit_ssl=YES.

4. Harden ProFTPD for FTPS

From the ProFTPD mod_tls documentation:

<IfModule mod_tls.c>
  TLSEngine on
  TLSRequired on
  TLSProtocol TLSv1.2 TLSv1.3
  TLSLog /var/log/proftpd/tls.log
</IfModule>

TLSRequired on "enables SSL/TLS requirements on both control and data channels"; its default is off. TLSProtocol lists allowed versions, and the documentation shows TLSv1.2 TLSv1.3 as an example. Leave NoSessionReuseRequired out of TLSOptions so data connections must reuse the control connection's TLS session. Use TLSOptions UseImplicitSSL only in a separate virtual host for clients that need port 990.

5. Build an SFTP-only account with OpenSSH

Every keyword here is documented in sshd_config(5):

Subsystem sftp internal-sftp

Match Group sftponly
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp -l INFO
    DisableForwarding yes
    PermitTTY no
    AuthenticationMethods publickey

What each line does, per the manual:

  • Subsystem sftp internal-sftp uses "an in-process SFTP server", which "may simplify configurations using ChrootDirectory". The manual notes "by default no subsystems are defined", although most distribution config files add one.
  • Match Group sftponly applies the following keywords only to members of that group, overriding the global section.
  • ChrootDirectory /srv/sftp/%u jails each user, with %u expanded to the username. Every path component must be root-owned and not writable by group or others, so give the user a writable subdirectory inside the jail rather than ownership of the jail root.
  • ForceCommand internal-sftp ignores whatever command the client asked for. internal-sftp "accepts the same command line arguments as sftp-server", and because LogLevel and SyslogFacility do not apply to it, logging is set with -l. Add -R for read-only drop boxes.
  • DisableForwarding yes "disables all forwarding features, including X11, ssh-agent(1), TCP and StreamLocal", which closes the gap ForceCommand leaves open.
  • PermitTTY no blocks terminal allocation; its default is yes.
  • AuthenticationMethods publickey requires key authentication. PasswordAuthentication defaults to yes in the manual, so set it to no globally if nothing else needs it.

For the wider sshd baseline, see our sshd settings for remote access from anywhere and how to set up a bastion host.

6. Verify the effective sshd configuration for a specific user

sshd(8) documents -t (test mode) and -T (extended test mode, which prints the effective configuration), and -C to apply Match rules for a given user, host and address:

sudo sshd -t
sudo sshd -T -C user=partner1,host=partner.example.com,addr=192.0.2.10 \
  | grep -Ei 'chrootdirectory|forcecommand|subsystem|disableforwarding|permittty|authenticationmethods|passwordauthentication'

If chrootdirectory shows none for a partner account, the Match block is not applying to it.

7. Restrict SFTP operations and check algorithms

sftp-server(8) documents -Q requests to list the request types the server supports, and -P and -p to deny or allow specific ones. The manual suggests that a server facing untrusted clients "may wish to disable the copy-data or users-groups-by-id operations". The binary's path varies by distribution:

/usr/libexec/sftp-server -Q requests
ForceCommand internal-sftp -l INFO -P copy-data,users-groups-by-id

ssh(1) documents -Q to list supported algorithms, which you can compare against what sshd offers in sshd -T:

ssh -Q cipher
ssh -Q mac
ssh -Q kex

8. Use SFTP for scp too

On OpenSSH 9.0 and later clients, scp already uses SFTP. Treat any script that passes -O as a finding to review, since it reintroduces the legacy protocol and its remote shell expansion.

When to use which: a decision table

OptionBest forSecurity propertiesPorts and networkOperational cost
FTPPublic, anonymous, read-only downloads on an isolated host; legacy devices on a closed networkNone: cleartext credentials and data, bounce and port stealing risks21 plus dynamic data ports; needs FTP-aware firewallingLow to run, high to defend; avoid for anything authenticated
Explicit FTPSPartners or regulations that mandate FTPS; existing FTP tooling that can add TLSTLS on control and data when PROT P is enforced; X.509 server and optional client auth21 plus a fixed passive range opened explicitlyCertificate lifecycle, passive range management, client TLS settings
Implicit FTPSOlder clients that only speak FTPS on 990Same as explicit once connected; no cleartext phase990 plus passive rangeSame as explicit, plus a separate listener
SFTPServer-to-server and partner file exchange, automation, admin transfersSSH encryption and integrity; host key and key based auth; chroot via OpenSSHOne TCP port, usually 22Key and host key management; careful Match and chroot setup
SCP (OpenSSH 9.0+)Quick one-off copies from a shellSame as SFTP, since it now uses SFTPSame as SFTPMinimal; avoid -O
HTTPS upload (PUT or multipart POST, or an object storage API)Browser uploads, applications and APIs, public-facing intakeTLS 1.2 or 1.3; auth via tokens or the app's login; app-level authorization443 onlyYou own the application code and its input handling

A short rule of thumb: pick SFTP for system-to-system transfers, HTTPS for anything a browser or API client touches, FTPS only when someone else's requirement names it, and FTP only for public data you would be happy to see on a billboard.

Common mistakes

  • Treating SFTP and FTPS as the same thing. They share no wire format, no port and no client library. Asking a partner for "secure FTP" without saying which leads to failed connections and quick workarounds.
  • Encrypting the login but not the files, or letting TLS be optional. Without PROT P, data moves in the clear, because RFC 4217 makes "Clear" the initial data state; and if either side treats TLS as optional, a tampered control channel can remove it. Enforce both on the server (force_local_data_ssl, TLSRequired on) and require TLS in clients.
  • Turning off certificate or host key checks to make automation work. That removes server authentication and invites a man in the middle. Fix the trust store or pin the host key instead.
  • Disabling session reuse to fix one client. NoSessionReuseRequired or require_ssl_reuse=NO makes data connection hijack easier. Upgrade the client first.
  • Opening a huge passive range. A range of tens of thousands of ports defeats the point of a firewall. Pin a narrow range sized to your concurrent transfers.
  • Chroot directories the user can write to. sshd checks at session startup that every path component is root-owned and not writable by group or others, and loosening ownership to get around that check breaks the jail's assumptions. Keep the jail root root-owned and give users a subdirectory.
  • Forgetting forwarding on SFTP-only accounts. ForceCommand internal-sftp does not stop TCP or agent forwarding. Add DisableForwarding yes.
  • Assuming port 115 is SFTP. IANA's sftp service name on 115 is the Simple File Transfer Protocol. SSH SFTP runs on your SSH port.
  • Forgetting the libraries. The FTP, FTPS and SSH code in your own applications comes from client libraries and their TLS and crypto dependencies. Those need the same patching attention as the servers.

FAQ

What port does SFTP use?

SFTP uses the same port as SSH, which is TCP 22 by default. It has no port of its own because it runs as a subsystem inside an SSH connection. The IANA name sftp on port 115 refers to a different, older protocol.

What port does FTPS use?

Explicit FTPS uses port 21, the normal FTP control port, and upgrades to TLS with AUTH TLS. Implicit FTPS conventionally uses port 990 for control and 989 for data. Both also need passive data ports, which you should pin to a narrow range.

Is FTP encrypted?

No. RFC 2577 states that standard FTP sends all data and control information, including passwords, unencrypted. Use FTPS or SFTP for anything that needs confidentiality.

Is SFTP the same as FTPS?

No. FTPS is FTP with TLS added to its control and data connections. SFTP is the SSH File Transfer Protocol, a binary request and response protocol carried inside one SSH channel. They need different clients, ports and credentials.

SFTP vs FTPS: which is more secure?

Both can be strong when configured well: FTPS relies on TLS and X.509 certificates, and SFTP relies on SSH and host keys. SFTP is usually easier to secure in practice, because it uses one port, has no cleartext phase and no data channel to bind, and OpenSSH offers built-in chroot and forced-command controls. FTPS is reasonable when a partner requires it and you enforce TLS on both channels.

What is the difference between active and passive FTP?

In active mode the client sends PORT and the server connects back to the client for each data transfer. In passive mode the client sends PASV or EPSV, the server opens a port and the client connects to it. Passive mode works better through client-side firewalls and NAT, which is why RFC 1579 recommended it.

Is SCP secure, and is it the same as SFTP?

SCP runs over SSH, so its transfers are encrypted. Since OpenSSH 9.0 (April 2022), the scp command uses the SFTP protocol by default instead of the legacy SCP protocol, which relied on the remote shell for filename expansion. The -O flag brings back the legacy mode for old servers.

Can I chroot SFTP users?

Yes. In OpenSSH, use a Match block with ChrootDirectory and ForceCommand internal-sftp. Every component of the chroot path must be owned by root and not writable by group or others, and you should add DisableForwarding yes because forced commands do not stop forwarding.

Where Safeguard fits

Safeguard does not scan, proxy or filter FTP, FTPS or SFTP traffic, and it does not configure your vsftpd, ProFTPD or OpenSSH servers. Every hardening step above is yours to apply. The honest connection is the software inside your own applications and images. File transfer integrations are usually built with FTP and SSH client libraries, which pull in TLS and cryptography libraries as transitive dependencies. Safeguard's software composition analysis builds a full transitive dependency inventory of those libraries (generally available), generates and analyses SBOMs in SPDX and CycloneDX (generally available), and enriches vulnerabilities with EPSS and CISA KEV data with reachability analysis to show whether your code calls the affected path (generally available). Container scanning covers the images where an SFTP or FTPS client actually ships. If you replace FTP with HTTPS uploads, Safeguard's DAST (generally available) can test the upload endpoints on targets you have verified. Our post on using SBOMs to find affected products fast walks through the workflow when the next SSH or TLS library advisory lands.

Fix it without waiting for upstream

Protocol bugs usually live in a library or daemon your software depends on: an SSH or SFTP client library, a TLS implementation, or an FTP client module. Keeping those dependencies current, and having a plan when upstream is slow, is part of keeping file transfer safe.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency and SBOM workflows, not file transfer monitoring. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at a service that pushes files to partners, the scan lists the SSH, FTP and TLS libraries inside it with versions and known vulnerabilities.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the package to bump is an SSH or FTP client library pinned in your manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects include a particular SSH or TLS library version, and get the answer from your own scan data.

Click through it at your own pace, or open it full screen.

Quick check

An FTPS client sends AUTH TLS, logs in and downloads a file without ever sending PROT P. Was the file encrypted?

No. AUTH TLS protects only the control connection. RFC 4217 makes the initial data connection state Clear, and only PBSZ 0 followed by PROT P switches data connections to TLS. The password was protected; the file was not. Enforce data protection on the server with force_local_data_ssl in vsftpd or TLSRequired on in ProFTPD.

A partner account has ForceCommand internal-sftp and a root-owned ChrootDirectory. What can it still do?

Unless forwarding is disabled, it can still request TCP, agent, socket or X11 forwarding, because the sshd_config manual says ForceCommand does not limit those. Add DisableForwarding yes (and PermitTTY no) in the same Match block, then confirm with sshd -T -C for that user.

Sources

Last reviewed: 23 October 2026. Server defaults vary by version and distribution; check the manual that ships with your build before relying on a default named here.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.