Master SSH keys, understand the differences between FTPS and SFTP, configure TLS certificates, and encrypt every connection so your credentials and files never cross the wire in the clear.
Every time you connect to a remote server to upload or download files, you create a trust channel between two machines. If that channel is unencrypted, every byte that crosses it â including your username, password, and the content of your files â is visible to anyone with access to the network path. Plain FTP, the original File Transfer Protocol published in 1971, was never designed with modern threats in mind. It transmits authentication credentials in cleartext ASCII, which means a passive observer on any router, Wi-Fi access point, or compromised ISP hop between you and your server can read your login details in a packet capture tool like Wireshark in seconds.
The real-world consequences of using plain FTP are more serious than most developers assume. Credential harvesting is the most common attack: an attacker captures the FTP login, then uses it to upload malicious PHP backdoors, inject SEO spam into your pages, or deface the entire site. Because the credentials are legitimate, server-side intrusion detection sees nothing suspicious. The second class of risk is session hijacking. An attacker who can observe the TCP stream can also inject commands into an active FTP session, redirecting uploads to a location they control or substituting file contents mid-transfer. The third risk is content disclosure: if you upload database dumps, API keys in environment files, or customer data, all of it is readable in transit.
There is also a compliance dimension. If your site handles any payment information, personal data under GDPR, HIPAA-regulated health records, or SOC 2 audited systems, cleartext file transfer is almost always a direct violation of the applicable standard. PCI DSS requirement 4.1, for instance, explicitly mandates strong cryptography for any transmission of cardholder data over open or public networks. Even if your workflow never knowingly touches regulated data, operational security best practice is to assume that any folder you can write to could someday contain something sensitive, and to protect the channel accordingly. The remaining steps in this guide walk through the two mainstream secure alternatives â SFTP and FTPS â and show you how to configure both the client and the server so that unencrypted transfer is not even an option.
The three protocols sound similar but are fundamentally different in architecture, encryption model, port usage, and operational behavior. Understanding those differences is the single most important piece of theory in this guide, because choosing the wrong one or confusing them during server configuration is the root cause of a large percentage of "my secure FTP won't connect" support tickets.
Plain FTP uses TCP port 21 for the control channel and a separate dynamic port for each data transfer. It has two modes â active and passive â which govern who opens the data connection. Everything, including the USER and PASS commands, is sent as plaintext.
FTPS (FTP over SSL/TLS) is the original FTP protocol wrapped in a TLS encryption layer, the same technology that protects HTTPS. It comes in two flavors. Explicit FTPS starts on port 21 as a plain FTP session, then issues an AUTH TLS command to upgrade the connection to encrypted. Implicit FTPS uses port 990 and requires TLS from the very first byte. FTPS still uses separate control and data channels, which creates well-known firewall and NAT traversal pain because the data port is negotiated dynamically.
SFTP (SSH File Transfer Protocol) despite the name is not FTP at all. It is a completely separate protocol that runs as a subsystem of SSH on port 22. There is only one encrypted channel for both commands and data, no separate data port negotiation, and authentication can use either passwords or SSH keys. Because it is just SSH, it inherits the entire SSH security model including host key verification, key-based auth, and extensive server-side configuration options.
Protocol | Port | Encryption | Channels | Firewall-Friendly ----------|------------|--------------|-----------|------------------- FTP | 21 + dyn | None | Two | Difficult FTPS-Exp | 21 + dyn | TLS (opt) | Two | Difficult FTPS-Imp | 990 + dyn | TLS (forced) | Two | Difficult SFTP | 22 only | SSH | One | Easy
A subtle operational difference worth noting: FTPS preserves the FTP command set, so advanced features like resume-on-failure using REST, directory listings via LIST, and site-specific SITE commands behave exactly as they do on plain FTP. SFTP has its own command set and some clients implement subsets of it, which occasionally leads to surprising incompatibilities with very old servers or esoteric features like FXP (server-to-server) transfers.
SSH keys are a cryptographic replacement for passwords. Instead of typing a secret that could be guessed, phished, or stolen, you prove your identity with a mathematical signature that can only be produced by someone holding the matching private key. The private key lives on your workstation and never leaves it; the public key is placed on every server you want to log into. Keys are stronger, more convenient, and harder to steal than passwords, and once you have them set up you will wonder how you tolerated typing passwords for so long.
The modern recommended algorithm is Ed25519. It produces short, fast keys based on elliptic curve cryptography, is resistant to a wide range of implementation mistakes that have historically weakened RSA, and is supported by every version of OpenSSH released in the last decade. RSA is still acceptable if you must support very old servers, but use at least 4096 bits. Avoid DSA and ECDSA with NIST curves â DSA is deprecated and ECDSA has well-known concerns about the curve parameters.
# Ed25519 (recommended) ssh-keygen -t ed25519 -C "[email protected]" # RSA fallback for legacy servers ssh-keygen -t rsa -b 4096 -C "[email protected]"
On Windows 10 and later, OpenSSH is included out of the box. Open PowerShell or Windows Terminal and run ssh-keygen -t ed25519. The key pair is saved to C:\Users\<you>\.ssh\id_ed25519 and id_ed25519.pub. On macOS, open Terminal and run the same command; keys land in ~/.ssh/. On Linux, the OpenSSH client is almost always preinstalled; the same command works identically.
When ssh-keygen asks for a passphrase, enter one. A passphrase encrypts the private key file on disk so that if someone steals your laptop, they cannot immediately use your key. Combined with an SSH agent, you only type the passphrase once per session.
~/.ssh/ âââ id_ed25519 (private key â never share) âââ id_ed25519.pub (public key â safe to share) âââ known_hosts (fingerprints of servers you have connected to) âââ config (optional per-host settings)
Install the public key on the server by appending its contents to ~/.ssh/authorized_keys in the target user's home directory. The ssh-copy-id [email protected] helper does this in one step. After installation, test with ssh [email protected]; if it logs you in without asking for a password, keys are working.
700, private keys 600, and authorized_keys 600. If auth silently fails, check permissions first.With SSH keys in place and installed on the server, you can now configure a graphical SFTP client to use them. The two most popular free clients are FileZilla (cross-platform) and WinSCP (Windows). Both support password and key-based authentication for SFTP, and both are capable of saving connection profiles so you never have to re-enter server details after the first setup.
FileZilla setup. Open File â Site Manager and click New site. Set the Protocol to SFTP â SSH File Transfer Protocol. Enter the server hostname and leave Port blank (it defaults to 22). Set Logon Type to Key file, enter your username, and browse to your private key file. On the first connection FileZilla will prompt you to accept the server's host key fingerprint â compare it to a fingerprint you obtained through a trusted channel (such as the server provider's control panel) before clicking OK. Accepting an unknown fingerprint without verifying is how man-in-the-middle attacks succeed.
Protocol: SFTP Host: sftp.example.com Port: 22 Logon Type: Key file User: deploy Key file: C:\Users\you\.ssh\id_ed25519
WinSCP setup. WinSCP uses the PuTTY key format internally. If you generated an OpenSSH-format key with ssh-keygen, open PuTTYgen, use Conversions â Import key, load your private key, and save it as a .ppk file. In WinSCP, create a new site with File protocol SFTP, enter the hostname and user name, click Advanced â SSH â Authentication, and point the Private key file field at the .ppk you just saved. Save the session and connect.
Command line SFTP. Both the OpenSSH sftp binary and the newer rsync-over-ssh workflow are excellent for scripted transfers. Run sftp [email protected] for an interactive shell, or sftp [email protected]:/var/www/ localdir for a direct fetch. Common interactive commands are put, get, ls, cd, lcd, and bye.
Host prod / HostName sftp.example.com / User deploy / IdentityFile ~/.ssh/id_ed25519. Then sftp prod is enough to connect, and FileZilla/WinSCP will also honor the config on recent versions.If the connection fails, start by running ssh -v [email protected] from a terminal. The verbose output almost always identifies the problem â wrong key offered, wrong permissions on the authorized_keys file, server refusing publickey auth, or a firewall dropping port 22.
Although SFTP is the preferred choice for most workflows, you will occasionally encounter environments â regulated hosts, legacy enterprise systems, or clients with vendor-mandated FTPS â where you must configure FTP over TLS. Done correctly, FTPS is just as secure as SFTP; done incorrectly, it is barely better than plain FTP. The key points are selecting the right mode (explicit or implicit), installing a trusted TLS certificate, and locking the server to refuse plaintext fallback.
Choose explicit FTPS unless forced otherwise. Explicit FTPS (sometimes labeled FTPES in client UIs) uses the standard FTP port 21 and upgrades the control connection to TLS via the AUTH TLS command. Implicit FTPS uses port 990 and starts in TLS from the first byte. Explicit is the modern standard; implicit is considered deprecated by the IETF but is still supported for legacy compatibility.
Install a real TLS certificate. Self-signed certificates work but trigger loud warnings in every client, and users quickly learn to click through them, defeating the point. Use Let's Encrypt via certbot for free, automatically renewing certificates. Point your FTP server configuration at the certificate chain file and private key.
ssl_enable=YES force_local_data_ssl=YES force_local_logins_ssl=YES ssl_tlsv1_2=YES ssl_tlsv1_3=YES ssl_sslv2=NO ssl_sslv3=NO rsa_cert_file=/etc/letsencrypt/live/ftp.example.com/fullchain.pem rsa_private_key_file=/etc/letsencrypt/live/ftp.example.com/privkey.pem require_ssl_reuse=NO
The force_local_data_ssl and force_local_logins_ssl options are critical. Without them, a misconfigured or malicious client can downgrade the session to plaintext for the data channel, exposing file content even though the credentials themselves were encrypted. Always force TLS for both channels.
Passive mode port range. FTPS uses a dynamic data port, which is problematic across NAT and firewalls. Pin passive mode to a known range in your server config, then open those ports in the firewall. A typical configuration reserves 40000â40100 for passive FTPS data channels. Clients behind restrictive corporate firewalls may still fail to connect because deep packet inspection cannot peek inside the encrypted control channel to open the data port dynamically â this is the main reason SFTP is more popular.
Test your FTPS configuration with a trusted client such as FileZilla. Use Explicit FTP over TLS as the protocol, connect, and watch the log pane. You should see AUTH TLS, PBSZ 0, PROT P, and a successful TLS handshake. If PROT P fails, your data channel is still plaintext â fix the config before putting the server into production.
A hardened server is the other half of the secure-file-transfer equation. No matter how carefully you configure your client, a server that allows anonymous access, permits weak ciphers, or lacks any form of filesystem isolation remains a soft target. This step walks through hardening the two most common Linux FTP daemons â vsftpd and ProFTPD â plus the standard OpenSSH SFTP subsystem.
Disable anonymous and plain FTP access. On vsftpd, set anonymous_enable=NO and the FTPS-forcing options from Step 5. On ProFTPD, remove or comment the <Anonymous> block in /etc/proftpd/proftpd.conf and add TLSRequired on. For OpenSSH SFTP, edit /etc/ssh/sshd_config and set PasswordAuthentication no and PermitRootLogin no.
Chroot jail every user. A chroot restricts an SFTP or FTPS user to their own home directory, so even if credentials leak the attacker cannot browse the rest of the server. In OpenSSH, add a Match block to sshd_config:
Match Group sftponly
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
Subsystem sftp internal-sftp
PasswordAuthentication noCreate the sftponly group, add the user to it, and ensure the chroot directory itself is owned by root with mode 755 (the user's writable content lives one level deeper, e.g., /home/deploy/files/). This arrangement is a hard SSH requirement â if the chroot directory is user-writable, SSH refuses to activate the chroot and the user cannot log in.
Limit cipher suites and key exchange algorithms. OpenSSH ships with reasonable defaults, but you can tighten them further by adding KexAlgorithms curve25519-sha256,[email protected] and Ciphers [email protected],[email protected] to sshd_config. For vsftpd and ProFTPD, specify ssl_ciphers=HIGH:!aNULL:!MD5:!3DES or the equivalent to exclude weak algorithms.
Keep the daemon patched. Set unattended security updates (unattended-upgrades on Debian/Ubuntu, dnf-automatic on RHEL/Fedora) so that CVE fixes in openssh-server, vsftpd, and ProFTPD land within hours of release. The number of production servers compromised through unpatched FTP daemons every year is embarrassingly large.
sshd -t to syntax-check sshd_config, then systemctl reload sshd. Keep a second terminal already connected while you reload â if the new config breaks login, that session is your lifeline back in.Encryption and keys are the foundation, but a mature secure-transfer setup goes further. The practices below are what distinguish a hobbyist deployment from a production-grade one: automated brute-force defense, rate limiting, audit logging, two-factor authentication, and routine review of what is actually happening on the server.
Install Fail2ban. Fail2ban watches your log files for repeated authentication failures and automatically bans the offending IP address at the firewall level for a configurable period. On a server with SSH exposed to the internet, you will typically see dozens to thousands of brute-force attempts per day; Fail2ban stops them cold with almost no configuration.
[sshd] enabled = true port = 22 maxretry = 4 findtime = 10m bantime = 24h [vsftpd] enabled = true port = 21,990,40000:40100 maxretry = 4
Rate-limit new connections at the firewall. Even with Fail2ban, you can shed load from distributed scans by having iptables or nftables drop more than, say, 10 new SYN packets to port 22 from the same source per minute. This blocks the connection before any userspace service has to parse it.
Enable detailed audit logging. OpenSSH logs to the system auth log by default, but for forensic quality set LogLevel VERBOSE in sshd_config. This causes each session to record the key fingerprint used for authentication, which is essential if you ever need to prove which key was involved in an incident. For FTPS, enable transfer logging (xferlog_enable=YES in vsftpd) so every file put and get is recorded.
Add two-factor authentication for human users. Google Authenticator (libpam-google-authenticator) integrates cleanly with OpenSSH. Add auth required pam_google_authenticator.so to /etc/pam.d/sshd and set ChallengeResponseAuthentication yes and AuthenticationMethods publickey,keyboard-interactive in sshd_config. The user is then required to provide both a valid SSH key and a six-digit TOTP code â a dramatic step up in account security for very little effort.
authorized_keys entry â using command="/usr/lib/openssh/sftp-server",no-pty,no-port-forwarding,no-X11-forwarding â so a stolen CI key cannot be used for a shell session.Rotate keys and audit regularly. At least once a year, review every authorized_keys file on every server and remove entries for people and systems that no longer need access. Rotate the keys of departing team members immediately. Keep an inventory of which key belongs to which human or service; this is easy with the Ed25519 fingerprint printed by ssh-keygen -lf keyfile.pub.
Monitor and alert. Feed auth logs into a central log aggregator (Loki, ELK, or a hosted service) and set alerts on patterns like repeated public-key failures, successful logins from new IPs, or logins outside business hours. Secure configuration is a baseline; active monitoring is what catches the attack that slips past the baseline.
With these seven steps in place â understanding the risks, choosing the right protocol, generating strong keys, configuring clients and servers correctly, forcing TLS where applicable, hardening the daemon, and layering operational defenses on top â you now have a secure file-transfer stack that stands up to real-world threats. Continue to the Automation guide to learn how to script these secure connections into your deployment pipeline without weakening any of the controls you just put in place.