Why you are here
You probably have a server that needs file transfer access, and you want it to not be completely insecure. Plain FTP sends passwords in cleartext. Everyone on the network path can grab them. That is just a fact. I spent several years dealing with the fallout of companies running vsftpd with default settings and wondering why their credentials were leaking. The fix is not complicated, but it is easy to get wrong if you rush through it. Let me start with something most tutorials skip. You need to decide what kind of user isolation you actually want before you install anything. There is virtual user mode, where you store credentials in a database file instead of /etc/passwd, and there is system user mode with local_users_readonly. The virtual user approach is the one that actually scales. It is also the one that trips people up because the PAM configuration is non-obvious. I hit a wall once on a Debian box where vsftpd refused to authenticate virtual users despite the db_load command completing without errors. The issue was that I had not set the PAM service name correctly in vsftpd.conf. The default PAM file for vsftpd expects a certain configuration format, and if your pam_userdb.so line points to the wrong database path, authentication silently fails and vsftpd just drops the connection. The workaround was checking /var/log/auth.log and noticing the exact error rather than guessing. You need this line in /etc/pam.d/vsftpd:
auth required pam_userdb.so db=/etc/vsftpd/virtual_users
account required pam_userdb.so db=/etc/vsftpd/virtual_users Once that is in place, the binary database has to exist. Generate it with: sudo db_load -T -t hash -f /etc/vsftpd/virtual_users.txt /etc/vsftpd/virtual_users.db
sudo chmod 600 /etc/vsftpd/virtual_users.db
The .txt file is just alternating usernames and passwords, one pair per line. Keep it secure too. Then set the ownership so vsftpd can read it: sudo chown root:root /etc/vsftpd/virtual_users.db
Get the Full Details

Core configuration settings
Here is the vsftpd.conf section that actually matters for security. I will not waste your time listing every possible option. Focus on these: anonymous_enable=NO
local_enable=YES
write_enable=NO
chroot_local_user=YES
allow_writeable_chroot=YES
ssl_enable=YES
force_local_logins_ssl=YES
force_local_data_ssl=YES
rsa_cert_file=/etc/ssl/certs/vsftpd.pem
rsa_private_key_file=/etc/ssl/private/vsftpd.pem
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
ssl_ciphers=HIGH Setting write_enable=NO at the global level is important. It means local users cannot write anywhere unless a specific directory has explicit write permissions granted through a different mechanism. chroot_local_user=YES locks everyone into their home directory. The allow_writeable_chroot flag was added in version 3.0.3 specifically to resolve a security regression where chroot required the directory to be non-writeable, which broke a lot of legitimate setups.
I learned about the require_ssl_reuse=NO setting the hard way. Some older FTP clients do not support SSL session reuse, and if you leave this at the default of YES, those clients will fail to connect with cryptic SSL handshake errors. Setting it to NO is slightly less efficient because the client renegotiates the SSL session for both control and data channels, but it prevents connection failures for legacy clients.
SSL certificate generation
You can buy a certificate. You can use Let's Encrypt. For vsftpd, a self-signed certificate is perfectly fine because you are not trying to protect against man-in-the-middle attacks in the traditional web sense. You are encrypting the transport layer. Generate one with: sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/vsftpd.pem -out /etc/ssl/certs/vsftpd.pem Fill in whatever you want for the common name. The client will show a certificate warning, which is expected and normal. If you are worried about certificate pinning in your organization, generate a proper CA-signed cert instead, but that is an operational decision, not a technical requirement.

Firewall and network considerations
FTP is historically problematic with firewalls because it uses separate control and data connections. The control channel runs on port 21, and the data channel negotiates dynamically unless you force passive mode. With SSL enabled, this becomes more complex because the data channel port must also be encrypted. Configure these settings: pasv_min_port=30000
pasv_max_port=31000
port_enable=NO
pasv_enable=YES
connect_from_port_20=NO Disable active mode entirely. It is rarely needed and introduces unnecessary complexity and security exposure. Open only the passive port range in your firewall, along with port 21 for control. If you are using ufw on Ubuntu, that looks like:
sudo ufw allow 21/tcp
sudo ufw allow 30000:31000/tcp Some cloud providers require you to also configure security group rules. AWS Security Groups, for example, do not have a built-in port range syntax in the console UI for older instances, so you might need to specify each port individually or upgrade to an newer instance type that supports range notation. This is annoying but not unique to vsftpd.
Logging and monitoring
Enable detailed logging so you can actually see what is happening: log_ftp_protocol=YES
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
syslog_enable=YES The log_ftp_protocol flag records every FTP command and response in the vsftpd log. This is extremely useful for debugging but can fill up disk space quickly if you have heavy traffic. I rotate the log weekly with a simple logrotate configuration. Without rotation, a busy server can push several hundred megabytes of log data per month.

Set up a basic watch on the log file to alert on repeated authentication failures. A script that counts failed login attempts per IP over a rolling 10-minute window and blocks the IP after exceeding a threshold is straightforward to implement with fail2ban. Configure the filter to match vsftpd auth failure lines and the jail to use the iptables-multiport action.
Known limitations and when to walk away
vsftpd does not support TLS 1.3. If your organization requires TLS 1.3 for compliance reasons, vsftpd is the wrong tool regardless of how well you configure it. You would need to look at ProFTPD with mod_tls or SwitchFOSS vsftpd forks that backported the support, neither of which is as battle-tested. This is not a minor gap. TLS 1.3 eliminates several attack vectors that existed in TLS 1.2, and ignoring it is a real risk for regulated environments. Another limitation is that vsftpd virtual user databases do not support password expiry natively. If you need passwords to rotate automatically, you are managing that outside of vsftpd, typically through a separate script that updates the hash file and reloads the service. It works but adds operational overhead. VSFTP also struggles with IPv6-only environments if your DNS and routing are not properly configured for dual-stack. I encountered a case where the server listened on both IPv4 and IPv6, but the passive port range was only reachable over IPv4 because the firewall rule had not been duplicated for the IPv6 chain. Clients connecting over IPv6 would authenticate successfully but then hang indefinitely during the data transfer phase. The fix was adding equivalent ip6tables rules for the passive port range.
Putting it together
After you write the configuration, test it before closing any remote sessions: sudo systemctl restart vsftpd
sudo ss -tlnp | grep vsftpd Verify that vsftpd is listening on port 21 and that the passive port range is open. Test the connection from a remote machine using an FTP client that supports explicit TLS, such as curl or FileZilla. Do not rely on anonymous FTP tests since you disabled anonymous access. Try authenticating with a virtual user and verify that the connection fails without SSL and succeeds with SSL forced.

If you find yourself managing a large number of virtual users with complex permission structures, consider whether an SFTP solution on top of SSH might serve you better. SFTP inherits all of SSH authentication mechanisms, supports key-based login natively, and does not require separate SSL certificate management. The migration path from vsftpd to SFTP is usually painless for most workflows, though it does require client reconfiguration on the user side.