Why FTP Is a Security Risk
FTP (File Transfer Protocol, RFC 959) was designed in 1971 — fourteen years before the public internet and decades before encryption was considered necessary. Its core problems are fundamental to the protocol, not fixable with configuration:
- Credentials transmitted in plaintext — username and password visible to anyone on the network path, including cloud provider employees, ISPs, and packet-sniffers
- Data transmitted in plaintext — every file transferred is readable in transit
- Passive mode firewall complexity — FTP uses two connections (control on port 21, data on a negotiated high port), requiring stateful firewall inspection that often fails
- Compliance violations — PCI DSS Requirement 2.2.7 explicitly prohibits FTP for transmitting cardholder data; HIPAA similarly bans unencrypted file transfer
SFTP (SSH File Transfer Protocol, RFC 4253) is not FTP over SSH — it is a completely different protocol that runs as a subsystem of SSH on port 22. All data is encrypted and authenticated using the same proven cryptography as SSH.
Step 1: SFTP Server Setup
OpenSSH sshd Configuration
SFTP is built into OpenSSH's sshd. Enable and harden it in /etc/ssh/sshd_config:
# /etc/ssh/sshd_config
# Enable SFTP subsystem (usually already present):
Subsystem sftp /usr/lib/openssh/sftp-server
# Create a dedicated SFTP group and restrict their access:
Match Group sftpusers
ChrootDirectory /data/sftp/%u # Jail each user to their own directory
ForceCommand internal-sftp # Prevent shell access
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
# Create the SFTP group and add users:
sudo groupadd sftpusers
sudo useradd -m -G sftpusers -s /sbin/nologin ftp_user1
sudo passwd ftp_user1
# Create chroot directory (must be owned by root, not writable by others):
sudo mkdir -p /data/sftp/ftp_user1/uploads
sudo chown root:root /data/sftp/ftp_user1
sudo chmod 755 /data/sftp/ftp_user1
sudo chown ftp_user1:sftpusers /data/sftp/ftp_user1/uploads
sudo chmod 750 /data/sftp/ftp_user1/uploads
# Reload sshd:
sudo systemctl reload sshd
Key-Based Authentication (Preferred)
Password authentication works but key-based auth is significantly more secure:
# Generate an SSH key pair on the client machine:
ssh-keygen -t ed25519 -C '[email protected]' -f ~/.ssh/sftp_key
# Install the public key on the server:
sudo mkdir -p /data/sftp/ftp_user1/.ssh
sudo tee /data/sftp/ftp_user1/.ssh/authorized_keys < ~/.ssh/sftp_key.pub
sudo chown -R ftp_user1:ftp_user1 /data/sftp/ftp_user1/.ssh
sudo chmod 700 /data/sftp/ftp_user1/.ssh
sudo chmod 600 /data/sftp/ftp_user1/.ssh/authorized_keys
Note: The .ssh directory must live *inside* the chroot. With ChrootDirectory /data/sftp/%u, the authorized_keys path is /data/sftp/ftp_user1/.ssh/authorized_keys.
Step 2: Client Migration
GUI Clients
| Old FTP Client | SFTP Replacement | Config Change |
|---|---|---|
| FileZilla (FTP) | FileZilla (SFTP) | Protocol: SFTP, Port: 22 |
| WinSCP (FTP) | WinSCP (SFTP) | File Protocol: SFTP |
| Cyberduck (FTP) | Cyberduck (SFTP) | Open Connection → SFTP |
FileZilla SFTP site configuration:
Host: sftp.your-domain.com
Protocol: SFTP - SSH File Transfer Protocol
Port: 22
Logon: Key file (browse to .ppk or .pem file)
Command-Line SFTP
# Interactive session:
sftp -i ~/.ssh/sftp_key [email protected]
sftp> put local_file.csv uploads/
sftp> get uploads/report.csv .
sftp> ls uploads/
sftp> bye
# Batch/non-interactive upload (scriptable):
sftp -i ~/.ssh/sftp_key [email protected] << 'EOF'
put /local/data.csv uploads/data.csv
bye
EOF
Automation with Python (paramiko)
import paramiko
from pathlib import Path
def upload_file(hostname: str, username: str, key_path: str,
local_path: str, remote_path: str) -> None:
key = paramiko.Ed25519Key.from_private_key_file(key_path)
transport = paramiko.Transport((hostname, 22))
transport.connect(username=username, pkey=key)
with paramiko.SFTPClient.from_transport(transport) as sftp: # type: ignore
sftp.put(local_path, remote_path)
print(f'Uploaded {local_path} → {remote_path}')
transport.close()
upload_file(
hostname='sftp.your-domain.com',
username='ftp_user1',
key_path='/path/to/sftp_key',
local_path='/tmp/report.csv',
remote_path='uploads/report.csv',
)
rsync over SSH
For directory synchronization (replaces ncftp or FTP mirror tools):
# Sync local directory to remote SFTP server:
rsync -avz -e 'ssh -i ~/.ssh/sftp_key' \
/local/data/ \
[email protected]:uploads/
# With delete (mirror mode — removes files deleted locally):
rsync -avz --delete -e 'ssh -i ~/.ssh/sftp_key' \
/local/data/ \
[email protected]:uploads/
Step 3: Legacy System Compatibility
Some legacy systems (embedded hardware, old ERP software) can only speak FTP and cannot be easily updated. Options:
FTP-to-SFTP Proxy Gateway
Run a local FTP gateway that accepts FTP on the internal network and translates to SFTP outbound:
# Install ftpproxy or use sftpgateway:
pip install sftpgateway
# Config: /etc/sftpgateway.conf
# [server]
# listen_host = 127.0.0.1 # Accept FTP locally only
# listen_port = 2121
# [backend]
# sftp_host = sftp.your-domain.com
# sftp_port = 22
# key_file = /etc/sftpgateway/sftp_key
Dual-Mode Transition Period
Run both servers simultaneously during migration:
Phase 1 (weeks 1-4): Both FTP and SFTP servers active
Monitor: count FTP vs SFTP connections
Phase 2 (weeks 5-8): FTP server read-only, SFTP for writes
Notify remaining FTP clients
Phase 3 (week 9+): FTP server shut down
Block port 21 at firewall
Monitor FTP usage to identify stragglers:
# vsftpd access log analysis:
grep 'OK UPLOAD\|OK DOWNLOAD' /var/log/vsftpd.log | awk '{print $7}' | \
sort | uniq -c | sort -rn | head -20
Step 4: FTPS — When It Makes Sense
FTPS (FTP + TLS, RFC 4217) is a third option: it adds TLS encryption to the standard FTP protocol. It makes sense when:
- You have many legacy clients that support FTPS but not SFTP
- You need to preserve FTP semantics (permissions, list formats) exactly
- Your team has existing FTP PKI infrastructure
FTPS modes:
| Mode | Port | How It Works |
|---|---|---|
| **Implicit FTPS** | 990 | TLS is mandatory from connection start (recommended) |
| **Explicit FTPS** (FTPES) | 21 | Plain FTP connection, then `AUTH TLS` upgrade |
# vsftpd config for Explicit FTPS (the most common modern setup):
# /etc/vsftpd.conf
ssl_enable=YES
force_local_data_ssl=YES # Require TLS for data channel
force_local_logins_ssl=YES # Require TLS for authentication
ssl_tlsv1_2=YES
ssl_tlsv1_3=YES
ssl_sslv2=NO
ssl_sslv3=NO
ssl_ciphers=HIGH # No weak ciphers
rsa_cert_file=/etc/ssl/certs/ftps.crt
rsa_private_key_file=/etc/ssl/private/ftps.key
SFTP vs FTPS recommendation: Prefer SFTP. It uses a single port (22), works cleanly through firewalls, supports key-based auth natively, and requires no separate TLS certificate management beyond your existing SSH setup.