By Chandler Gray• Published: • 6 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.

One thing to say before any of the steps. What this sets up is 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. Exposed to the internet it isn’t, and the fix is either ssl_enable=YES in the vsftpd config with a certificate, which makes it FTPS, or skipping FTP entirely and using SFTP over the SSH server that Ubuntu already has running. I use this on my local network only, and I’d want the encrypted version for anything else.


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.

I couldn’t find that refusal written down in the vsftpd.conf man page, so I’m going off the error it printed at me rather than documentation. The man page does have a warning on chroot_local_user that I’d read before turning it on, that the option “has security implications, especially if the users have upload permission, or shell access,” and that “these security implications are not vsftpd specific. They apply to all FTP daemons which offer to put local users in chroot() jails.” The layout below satisfies the writable-root check, and it also keeps the writable area down to one directory, which is the part that matters given that warning.

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
  • 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. The passive port range is the piece I understand least. I opened 30000:30049 because that’s what a guide used, and I couldn’t tell you why those numbers specifically or how many ports you actually need, beyond that each passive transfer takes one and fifty is more than I’ll ever use at home. 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.