vsftpd
Overview
vsftpd, the Very Secure FTP Daemon, is one of the most widely deployed FTP servers and the default FTP daemon of most Linux distributions. This SecureAnyCloud image runs it FTPS-first: explicit TLS is required on both the control channel and the data channel, so credentials and file contents are never sent in the clear.
The image installs vsftpd 3.0 from the official Ubuntu 24.04 apt archive and runs it under systemd as vsftpd.service. The server listens for the FTP control channel on tcp port 21 and uses a passive data port range of 40000 to 40009. There is no web interface and no database: the single file /etc/vsftpd.conf is the whole configuration.
The image is secure by default and has no known bootstrap credential. On the first boot of every VM, a one-shot service creates a unique FTP user with a strong random password and generates a unique self-signed TLS certificate for that machine. It also sets the passive mode address to the instance address and writes the user, password and host to a root-owned credentials file.
What you get:
- vsftpd 3.0 from the Ubuntu 24.04 apt archive, run under systemd as
vsftpd.service - FTPS (explicit TLS) required on the control and data channels (
force_local_logins_sslandforce_local_data_ssl), so a plaintext FTP login is refused - A per-instance unique FTP user and password and a unique self-signed TLS certificate, generated on first boot and written to
/root/vsftpd-credentials.txt(mode 0600) - Every user chrooted to its own home directory under
/srv/vsftp, so an account sees only its own files - No anonymous access, and virtual-user authentication only, so no system account can log in over FTP
- An FTP user that is a virtual user, with its password in a stand-alone file rather than the system password database, so no two instances share a credential and no default login is baked into the image
- A defined passive port range (40000 to 40009) and a passive address set to this VM's address, for passive mode behind network address translation (NAT)
Prerequisites
- A VM launched from this SecureAnyCloud vsftpd image on Azure
- An Azure network security group (NSG) that allows inbound tcp 21 (FTP control) and tcp 40000 to 40009 (FTPS passive data), scoped to your client addresses only. Do not expose FTP to the whole internet.
- SSH access to the VM as
azureuser
FTPS uses one connection for control and a separate connection for each transfer, so both port 21 and the passive range must be open to your clients. The passive range is small by design. If you expect many concurrent transfers, widen it in /etc/vsftpd.conf and in the NSG.
Verify the FTPS server is running
Connect over SSH as azureuser. Confirm that vsftpd.service is active, that forced TLS is set in the configuration, and that the server is listening on port 21.
sudo systemctl is-active vsftpd grep -E '^ssl_enable|^force_local_(logins|data)_ssl' /etc/vsftpd.conf
sudo ss -tlnH | grep -E ':21 'The output shows active, the three TLS directives set to YES, and a listener bound to 0.0.0.0:21 for the FTP control channel:
active ssl_enable=YES force_local_logins_ssl=YES force_local_data_ssl=YES LISTEN 0 32 0.0.0.0:21 0.0.0.0:*Retrieve the per-instance credentials and test a secure transfer
On the first boot of your VM, the image generated a unique FTP user, a unique password and a unique TLS certificate. The user, password and host are stored in the root-owned credentials file:
sudo cat /root/vsftpd-credentials.txtThe file contains FTP_USER, FTP_PASSWORD and FTP_HOST, each unique to this VM.
Test a secure transfer end to end with curl. The --ssl-reqd option negotiates explicit TLS, and --insecure accepts the self-signed certificate. The server advertises this VM's public address for passive mode, so --ftp-skip-pasv-ip tells curl to reuse the loopback address for the local test.
The block below reads the generated user and password, uploads a file over FTPS, downloads it back, and shows the contents:
FTP_USER=$(sudo grep '^FTP_USER=' /root/vsftpd-credentials.txt | cut -d= -f2-)
FTP_PASSWORD=$(sudo grep '^FTP_PASSWORD=' /root/vsftpd-credentials.txt | cut -d= -f2-)
echo "sac FTPS round trip $(date)" > /tmp/upload.txt curl --ssl-reqd --insecure --ftp-skip-pasv-ip -u "$FTP_USER:$FTP_PASSWORD" -T /tmp/upload.txt ftp://127.0.0.1/upload.txt curl --ssl-reqd --insecure --ftp-skip-pasv-ip -u "$FTP_USER:$FTP_PASSWORD" ftp://127.0.0.1/upload.txt -o /tmp/download.txt cat /tmp/download.txtThe upload and download both succeed over explicit TLS, and the downloaded contents match what you uploaded. This shows that the FTPS pipeline works end to end: TLS negotiation, the per-instance virtual user and password, and the chrooted home directory.
sac FTPS round trip Mon Jul 27 19:03:40 UTC 2026Confirm that plaintext, anonymous and out-of-jail access are refused
A secure FTP server must refuse anything less than TLS. This image requires explicit TLS, refuses anonymous logins, and chroots each user to its home directory. The block below tests all three. A plaintext login, an anonymous login and an attempt to read a file outside the user's home directory each fail.
FTP_USER=$(sudo grep '^FTP_USER=' /root/vsftpd-credentials.txt | cut -d= -f2-)
FTP_PASSWORD=$(sudo grep '^FTP_PASSWORD=' /root/vsftpd-credentials.txt | cut -d= -f2-)
curl -sS --ftp-skip-pasv-ip -u "$FTP_USER:$FTP_PASSWORD" ftp://127.0.0.1/ -o /dev/null || echo "plaintext FTP refused (TLS required)"
curl -sS --ssl-reqd --insecure --ftp-skip-pasv-ip -u "anonymous:x@x" ftp://127.0.0.1/ -o /dev/null || echo "anonymous login refused"
curl -sS --ssl-reqd --insecure --ftp-skip-pasv-ip -u "$FTP_USER:$FTP_PASSWORD" ftp://127.0.0.1/etc/passwd -o /dev/null || echo "chroot enforced: /etc/passwd is not reachable"Each attempt is rejected, and the fallback message after each || prints:
plaintext FTP refused (TLS required) anonymous login refused chroot enforced: /etc/passwd is not reachableThe plaintext login is refused because TLS is required. The anonymous login is refused because anonymous_enable is off. The request for /etc/passwd fails because the user is chrooted and its filesystem root is its own home directory.
Review the hardening configuration
All of the hardening lives in one readable file, /etc/vsftpd.conf. List the active (non-comment) directives:
grep -vE '^[[:space:]]*#|^[[:space:]]*$' /etc/vsftpd.confThe configuration enforces:
ssl_enable=YESwithforce_local_logins_ssl=YESandforce_local_data_ssl=YES: explicit TLS is required on control and dataanonymous_enable=NO: no anonymous accesschroot_local_user=YESwithallow_writeable_chroot=YESand a per-userlocal_root: every user is chrooted to its own homeguest_enable=YESwithguest_username=vftpandvirtual_use_local_privs=YES: virtual users only, so no system account can log in- TLS 1.2 with
ssl_ciphers=HIGH - The
pasv_min_port/pasv_max_portrange 40000 to 40009
The passive address for this VM is written to pasv_address on first boot. After you edit the file, apply the changes with sudo systemctl restart vsftpd.
Connect from an FTPS client
From your own workstation, connect with any FTPS-capable client using explicit TLS, also called FTP over TLS or AUTH TLS. Use the FTP_USER, FTP_PASSWORD and FTP_HOST values from the credentials file, use port 21, and enable explicit TLS. The certificate is self-signed, so accept it on first connection or replace it with your own certificate. For example, with curl:
# Replace the host and password with the values from /root/vsftpd-credentials.txt.
curl --ssl-reqd --insecure -u FTP_USER:FTP_PASSWORD -T report.pdf ftp://FTP_HOST/report.pdf
curl --ssl-reqd --insecure -u FTP_USER:FTP_PASSWORD ftp://FTP_HOST/report.pdf -o report.pdfGraphical clients such as FileZilla and WinSCP work the same way. Choose the protocol FTP with Require explicit FTP over TLS. Set the host to FTP_HOST, the port to 21, and passive mode. Make sure your Azure NSG allows inbound tcp 21 and tcp 40000 to 40009 from your client address.
Add more users and replace the certificate
Each FTP account is a vsftpd virtual user. It consists of a line in /etc/vsftpd/virtual.passwd of the form username:hash, where the hash comes from openssl passwd -6, plus a home directory under /srv/vsftp/<username> owned by the vftp system account.
To add another isolated FTP account, append a line to that file and create its home directory. The user can then log in over FTPS, chrooted to that directory, with no system account involved.
To use a certificate signed by a certificate authority instead of the per-instance self-signed one, replace /etc/vsftpd/ssl/vsftpd.crt and /etc/vsftpd/ssl/vsftpd.key and reload the service. For larger deployments, you can add quotas and bandwidth controls in the vsftpd configuration.
Support
SecureAnyCloud provides 24/7 technical support for this vsftpd image by email (sales@secureanycloud.com) and live chat. We help with:
- Connecting FTPS clients
- Managing virtual users and directories
- Configuring chroot and quotas
- Opening the control port and passive range in your network security group
- Setting the passive address behind network address translation
- TLS certificate management, version upgrades and troubleshooting
Critical issues receive a one-hour average response time.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on Azure
Launch vsftpd 3.0 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com