Running your own FTP server allows you to share files with other users or systems on a network in a controlled, structured way. This article explains, from first principles, how FTP servers work, and walks through installing and configuring vsftpd (Very Secure FTP Daemon) — the most widely used FTP server on Linux — including securing it with TLS, setting up user permissions, and troubleshooting common issues.
Why vsftpd?
There are several FTP server implementations for Linux (proftpd, pure-ftpd, vsftpd), but vsftpd is the most common default choice because of its strong security track record, simplicity, and wide distribution support.
flowchart LR
A["FTP Client"] -- "Control: port 21" --> B["vsftpd Server"]
A -- "Data: passive port range" --> B
B --> C["/srv/ftp Filesystem"]Step 1: Installing vsftpd
sudo apt update
sudo apt install vsftpd # Debian/Ubuntusudo dnf install vsftpd # RHEL/Fedora
Step 2: Starting and Enabling the Service
sudo systemctl enable --now vsftpd
sudo systemctl status vsftpdStep 3: Understanding the Main Configuration File
The primary configuration file is /etc/vsftpd.conf (Debian/Ubuntu) or /etc/vsftpd/vsftpd.conf (RHEL/Fedora). Always back it up before editing:
sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.bakStep 4: Basic Secure Configuration
Edit the config file:
sudo nano /etc/vsftpd.confKey settings to review and set:
# Disable anonymous login (recommended for most use cases)
anonymous_enable=NO
# Allow local system users to log in
local_enable=YES
# Allow uploads
write_enable=YES
# Restrict users to their home directory (chroot jail)
chroot_local_user=YES
# Set a reasonable default umask for uploaded files
local_umask=022
# Enable logging
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
# Passive mode port range (important for firewall rules)
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
After editing, restart the service:
sudo systemctl restart vsftpd
Step 5: Opening Firewall Ports
FTP needs the control port (21) and the entire passive port range open:
sudo ufw allow 21/tcp
sudo ufw allow 40000:40100/tcp
Or with firewalld on RHEL/CentOS:
sudo firewall-cmd --permanent --add-port=21/tcp
sudo firewall-cmd --permanent --add-port=40000-40100/tcp
sudo firewall-cmd --reloadStep 6: Creating an FTP-Only User
For security, it’s common to create dedicated accounts for FTP access that don’t have shell access to the whole system:
sudo useradd -m -d /home/ftpuser -s /usr/sbin/nologin ftpuser
sudo passwd ftpuserSetting chroot_local_user=YES (from Step 4) confines this user to their own home directory, preventing them from browsing the rest of the filesystem.
Step 7: Securing FTP With TLS (FTPS)
Plain FTP transmits credentials and data unencrypted. Enabling TLS (turning FTP into FTPS) is strongly recommended for anything beyond an isolated internal network.
Generate a self-signed certificate (or use one from Let’s Encrypt for a production server):
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/vsftpd.pem \
-out /etc/ssl/private/vsftpd.pem
Add TLS settings to vsftpd.conf:
ssl_enable=YES
rsa_cert_file=/etc/ssl/private/vsftpd.pem
rsa_private_key_file=/etc/ssl/private/vsftpd.pem
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NORestart the service:
sudo systemctl restart vsftpdClients now must connect using FTPS (explicit TLS) rather than plain FTP.
Step 8: Configuring Anonymous Access (Optional, Use With Caution)
For public file distribution (like a public software mirror), anonymous access can be useful, but should be read-only and carefully sandboxed:
anonymous_enable=YES
anon_root=/srv/ftp/public
anon_upload_enable=NO
anon_mkdir_write_enable=NO
no_anon_password=YES
A Complete Example Configuration
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
connect_from_port_20=YES
chroot_local_user=YES
allow_writeable_chroot=YES
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
ssl_enable=YES
rsa_cert_file=/etc/ssl/private/vsftpd.pem
rsa_private_key_file=/etc/ssl/private/vsftpd.pem
force_local_data_ssl=YES
force_local_logins_ssl=YES
Comparison: vsftpd vs. Other FTP Servers
| Server | Ease of Setup | Security Track Record | Common Use Case |
|---|---|---|---|
| vsftpd | Easy | Excellent | General-purpose, security-focused default |
| ProFTPd | Moderate (highly configurable) | Good | Complex, module-heavy setups |
| Pure-FTPd | Easy | Good | Simple virtual-user hosting setups |
Real-World Use Case: Internal File Drop for a Branch Office
A company wants a branch office to upload daily sales reports to headquarters over FTPS.
Step 1: Create a dedicated user:
sudo useradd -m -s /usr/sbin/nologin branch01
sudo passwd branch01Step 2: Restrict them to an uploads-only directory using chroot and directory permissions:
sudo mkdir -p /home/branch01/uploads
sudo chown branch01:branch01 /home/branch01/uploads
sudo chmod 750 /home/branch01/uploadsStep 3: Confirm chroot_local_user=YES and force_local_data_ssl=YES are set, then restart:
sudo systemctl restart vsftpdStep 4: The branch office connects using an FTPS-capable client (e.g., FileZilla in FTPS mode) and uploads their daily report to the uploads folder.
Best Practices
- Disable anonymous access unless you specifically need public, unauthenticated file sharing.
- Always enable TLS (FTPS) for anything beyond a fully isolated, trusted internal network.
- Chroot users into their home directories to prevent them from browsing or modifying files outside their designated area.
- Use dedicated FTP-only accounts with
nologinshells rather than full system accounts. - Restrict the passive port range to a small, known range, and open exactly that range in your firewall — don’t open large arbitrary port ranges.
- Monitor
/var/log/vsftpd.logregularly for failed login attempts or unusual activity. - Consider SFTP instead of FTP/FTPS entirely for new deployments, since it reuses your existing SSH infrastructure and is generally simpler to secure.
Troubleshooting
Problem: 500 OOPS: vsftpd: refusing to run with writable root inside chroot()
Modern vsftpd refuses to start if a chrooted user’s home directory is writable by that same user (a security protection). Fix by either making the home directory read-only and creating a writable subdirectory, or explicitly allowing it:
allow_writeable_chroot=YESProblem: Client can log in but ls hangs
This is a classic passive-mode/firewall issue. Confirm the passive port range is open in your firewall and matches the pasv_min_port/pasv_max_port settings.
Problem: Uploads fail with “Permission denied”
Check both the Linux filesystem permissions on the target directory and write_enable=YES in the config:
ls -ld /home/ftpuser/uploadsProblem: TLS handshake fails
Verify certificate paths are correct and readable by the vsftpd process:
sudo journalctl -u vsftpd -n 50Problem: Service won’t start after config changes
Check syntax and permissions on the config file, and review logs:
sudo systemctl status vsftpd
sudo journalctl -u vsftpd -n 50Conclusion
Setting up a Linux FTP server with vsftpd is straightforward once you understand the key building blocks: the control/data channel model, chroot jails for user isolation, passive port ranges for firewall compatibility, and TLS for encryption. Following the security-first defaults in this guide — disabling anonymous access, enforcing FTPS, and chrooting users — gives you a solid, production-ready file transfer service, while still leaving open the option to migrate to SFTP for even simpler, SSH-based security in the future.