By Chandler Gray• Published: • 5 min read

Setting Up an FTP Server on Ubuntu with vsftpd

Table of Contents

I run my servers inside Proxmox, so in this case the FTP service lives on an Ubuntu Server VM there. The same steps apply whether you’re on bare metal or any other hypervisor.

This sets up plain FTP, which sends the username, the password and the file contents across the network unencrypted. On a home LAN behind a router, that’s a decision you can make for yourself, but not on anything exposed to the internet. The vsftpd.conf man page has an ssl_enable option that encrypts both the login and the data connections, or you can use SFTP over SSH instead of FTP.


Create the VM

Install Ubuntu Server LTS and give it a static LAN IP so it’s easy to reach later. I used a bridged adapter in Proxmox so the VM behaves like any other machine on my network.

If the VM can’t be reached from another machine, double-check the network adapter mode and make sure the IP address is in the right subnet.


Update the OS

Bring the system up to date before adding anything else.

sudo apt update && sudo apt upgrade -y

Mirror issues can cause failures. Running the command again usually clears it up.


Install vsftpd

Install the FTP daemon package.

sudo apt install vsftpd -y

If the service doesn’t appear to exist, confirm with systemctl status vsftpd.


Create an FTP User

I added a dedicated account just for FTP traffic so it’s separate from system logins.

sudo adduser ftpuser

Set a password when prompted.

Later login failures (530 Login incorrect) often mean the account is disabled in /etc/ftpusers or local_enable isn’t set in the config.


Set Up the Directory Structure

vsftpd refuses to start a session where the chroot directory itself is writable by the user. To work around this, I made a locked root folder with a writable subdirectory for uploads.

The man page doesn’t document that check, so the error message is the best guide to it. It does have a warning on chroot_local_user, which is the setting that jails users. It says the option “has security implications, especially if the users have upload permission, or shell access.” Keeping the root locked and the writable area down to one directory is one way to limit that.

sudo mkdir -p /srv/ftp/uploads
sudo chown root:root /srv/ftp
sudo chmod 755 /srv/ftp
sudo chown ftpuser:ftpuser /srv/ftp/uploads

Common errors:

  • 500 OOPS: vsftpd: refusing to run with writable root inside chroot usually means permissions on /srv/ftp are wrong. It must be owned by root and not writable.

Configure vsftpd

Open the config:

sudo nano /etc/vsftpd.conf

Here’s what I used:

listen=YES
listen_ipv6=NO

anonymous_enable=NO
local_enable=YES
write_enable=YES
chroot_local_user=YES

That config sets up the account and the chroot but doesn’t pin the passive port range, so vsftpd picks from a wide default and the firewall rules further down won’t line up with it. The man page gives the default for both pasv_min_port and pasv_max_port as “0 (use any port),” and says a range “can be used to specify a narrow port range to assist firewalling,” which is the reason to set them at all. Add these two so the range matches what you open in UFW:

pasv_min_port=30000
pasv_max_port=30049

The man page says chroot_local_user=YES places local users “in a chroot() jail in their home directory after login.” adduser gives ftpuser a home directory under /home, so to use the /srv/ftp layout above, point the user at it. The local_root option sets the directory vsftpd changes into after a local login:

local_root=/srv/ftp
  • If directory listings stall, the passive port range isn’t open.
  • If the server advertises 127.0.1.1 in passive mode, add pasv_address=<your_LAN_IP> to the config.

Restart and Enable the Service

Restart the daemon and enable it at boot.

sudo systemctl restart vsftpd
sudo systemctl enable vsftpd

If it fails to enable, check the logs:

journalctl -u vsftpd -n 50 --no-pager

Adjust the Firewall

If UFW is enabled, open the command port and the passive range.

sudo ufw allow 21/tcp
sudo ufw allow 30000:30049/tcp

UFW must be active for these rules to matter. Verify with sudo ufw status.


Test Locally

Before trying a client, I tested from the server itself.

curl -v --user 'ftpuser:YourPassword' ftp://127.0.0.1/
curl -v --user 'ftpuser:YourPassword' -T /etc/hosts ftp://127.0.0.1/uploads/
ls -l /srv/ftp/uploads
  • 530 Login incorrect: check username, password, and config.
  • 550 Permission denied: verify /srv/ftp/uploads is owned by ftpuser.

Connect from Another Machine

Connect with an FTP client using your VM’s IP address.

  • Host: <LAN_IP>
  • Port: 21
  • Protocol: FTP
  • Username: ftpuser
  • Password: your password
  • Transfer mode: Passive

I only use these FTP servers on my local network, so I haven’t gone overboard with hardening them for internet exposure. Network security isn’t my specialty, so I stick to the basics. If you’ve got suggestions for tightening things up, either for FTP specifically or my home lab in general, I’d be glad to hear them.