To enable FTP on a Raspberry Pi, install the vsftpd (Very Secure FTP Daemon) package via sudo apt install vsftpd, modify the /etc/vsftpd.conf file to allow write access and chroot jails, and restart the service. However, if your goal is simply to transfer files securely without configuring extra ports, SFTP (SSH File Transfer Protocol) is already enabled on port 22 out-of-the-box and requires zero additional setup.

While SFTP is the modern standard, legacy FTP is still required for specific industrial PLCs, older CNC machines, and legacy embedded sensors that only support plain FTP. This guide covers the exact configuration for a robust vsftpd server on the Raspberry Pi 5 (8GB) running Raspberry Pi OS (Bookworm), alongside a hardware GPIO status monitor to visualize your server state on the bench.

Protocol Showdown: FTP vs SFTP vs FTPS on the Pi

Before opening ports on your network, you need to choose the right protocol. Plain FTP sends credentials in cleartext, which is a severe security risk on any internet-facing network. Here is how the three main file transfer protocols compare regarding performance and security on the Raspberry Pi 5's Broadcom BCM2712 SoC.

Protocol Default Ports Encryption Pi 5 CPU Overhead Setup Complexity
Plain FTP 20 (Data), 21 (Command) None (Cleartext) < 1% Low (vsftpd)
SFTP 22 (SSH) AES-256 (SSH Tunnel) ~3-5% Zero (Built-in)
FTPS (Explicit) 21, 990, + Passive Range TLS 1.2/1.3 ~4-6% High (Cert management)
Bench Tip: If you are transferring files from a Windows PC or modern Mac to your Pi, skip the rest of this guide and use WinSCP or Cyberduck over SFTP (Port 22). Only proceed with vsftpd if your client device strictly requires legacy FTP.

Hardware & Parts List for the FTP Status Monitor

Headless Pi servers often sit on a shelf without a monitor. To verify your FTP server is running and to display the Pi's local IP address for quick client connections, we will wire up an I2C OLED display and a GPIO status LED.

Required Components

  • Board: Raspberry Pi 5 (8GB variant) - ~$80 USD
  • Display: 0.96-inch SSD1306 I2C OLED (128x64, 3.3V logic) - ~$12 USD
  • Indicator: 5mm Green LED + 330Ω current-limiting resistor
  • Wiring: Female-to-female Dupont jumper wires
  • Storage: 128GB+ microSD card (UHS-I Class 10 minimum for acceptable FTP write speeds)

Pin Mapping Table

The Raspberry Pi 5 maintains the standard 40-pin header layout. Wire the components exactly as specified below to match the Python code provided later.

Component Pin Pi 5 GPIO / Power Physical Pin # Function
OLED VCC 5V Power Pin 2 Display Power
OLED GND Ground Pin 6 Common Ground
OLED SDA GPIO 2 (I2C1 SDA) Pin 3 I2C Data
OLED SCL GPIO 3 (I2C1 SCL) Pin 5 I2C Clock
LED Anode (+) GPIO 17 Pin 11 Status Output (via 330Ω)
LED Cathode (-) Ground Pin 9 LED Ground

Step-by-Step: Installing and Configuring vsftpd

The default vsftpd configuration is highly restrictive. We need to enable local user logins, write permissions, and chroot jails (which trap users in their home directory for security).

  1. Update and Install:
    sudo apt update && sudo apt install vsftpd -y
  2. Backup the Default Config:
    sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.bak
  3. Edit the Configuration:
    Open the file: sudo nano /etc/vsftpd.conf
    Find and modify the following lines (uncomment them by removing the # if necessary):
    anonymous_enable=NO
    local_enable=YES
    write_enable=YES
    local_umask=022
    chroot_local_user=YES
    allow_writeable_chroot=NO
  4. The Chroot Writable Directory Workaround:
    Modern vsftpd versions will block login if a chrooted user's home directory is writable (throwing a 500 OOPS error). The correct fix is to make the home directory read-only and create a writable subdirectory for uploads.
    sudo chmod a-w /home/pi
    sudo mkdir /home/pi/ftp_uploads
    sudo chown pi:pi /home/pi/ftp_uploads
  5. Configure Passive Mode Ports:
    Add these lines to the bottom of vsftpd.conf to define a narrow passive port range, making firewall rules easier:
    pasv_enable=YES
    pasv_min_port=30000
    pasv_max_port=30100
  6. Restart the Service:
    sudo systemctl restart vsftpd

For deeper configuration parameters, refer to the official vsftpd documentation or the Raspberry Pi Foundation networking guides.

Python FTP Watchdog & OLED Status Code

This Python script targets the Raspberry Pi 5 running Bookworm. It uses the luma.oled library to drive the SSD1306 display and gpiozero to control the status LED. It polls port 21 to verify the FTP daemon is listening and fetches the local IP address.

Prerequisites: sudo apt install python3-gpiozero i2c-tools and pip3 install luma.oled

import socket
import time
import subprocess
from gpiozero import LED
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306

# --- PIN DEFINITIONS ---
# GPIO 17 (Physical Pin 11) controls the green status LED
STATUS_LED = LED(17)

# I2C setup for SSD1306 (Address 0x3C is standard for most 0.96" modules)
try:
    serial = i2c(port=1, address=0x3C)
    device = ssd1306(serial, width=128, height=64)
    display_available = True
except Exception as e:
    print(f'OLED Init Failed: {e}')
    display_available = False

def get_ip_address():
    """Fetches the primary LAN IP address of the Pi."""
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except Exception:
        return 'No Network'

def check_ftp_port(host='127.0.0.1', port=21):
    """Checks if vsftpd is actively listening on port 21."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1.0)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except Exception:
        return False

def main():
    print('Starting FTP Watchdog...')
    while True:
        ftp_running = check_ftp_port()
        ip_addr = get_ip_address()
        
        # Update GPIO LED
        if ftp_running:
            STATUS_LED.on()
        else:
            STATUS_LED.blink(on_time=0.2, off_time=0.2)
            
        # Update OLED Display
        if display_available:
            with canvas(device) as draw:
                draw.text((0, 0), 'FTP Server Status', fill='white')
                status_text = 'ONLINE' if ftp_running else 'OFFLINE'
                draw.text((0, 16), f'State: {status_text}', fill='white')
                draw.text((0, 32), f'Port: 21', fill='white')
                draw.text((0, 48), f'IP: {ip_addr}', fill='white')
                
        time.sleep(5)

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        STATUS_LED.off()
        if display_available:
            device.cleanup()
        print('Watchdog stopped.')

Troubleshooting: Exact Errors and Ranked Fixes

FTP is notoriously finicky regarding directory permissions and firewall states. If your client fails to connect, look for these exact error strings.

Error 1: 550 Permission denied.

This occurs when you attempt to upload a file or create a directory, but the server blocks the write operation.

  • Cause 1 (Most Likely): You are trying to write directly to /home/pi, which we made read-only in Step 4. Fix: Change your client's remote directory to /ftp_uploads.
  • Cause 2: write_enable=YES is missing or commented out in vsftpd.conf. Fix: Edit the config and restart the service.
  • Cause 3: The underlying filesystem is mounted read-only (common if the Pi experienced a hard crash and the SD card corrupted). Fix: Run sudo fsck /dev/mmcblk0p2 and reboot.

Error 2: ftp: connect: Connection timed out or Network unreachable

The client cannot establish the initial TCP handshake on port 21.

  • Cause 1 (Most Likely): UFW (Uncomplicated Firewall) or iptables is blocking port 21. Fix: Run sudo ufw allow 21/tcp and sudo ufw allow 30000:30100/tcp.
  • Cause 2: The vsftpd daemon crashed or failed to start due to a syntax error in the config. Fix: Check sudo systemctl status vsftpd.
  • Cause 3: You are connecting from a different subnet and the Pi's default gateway is missing. Fix: Verify routing with ip route.
The First 3 Things to Check When FTP Fails:
1. Run sudo systemctl status vsftpd to ensure the daemon is active (green).
2. Run sudo ufw status to verify ports 21 and 30000:30100 are marked ALLOW.
3. Run ls -ld /home/pi to confirm the directory permissions are dr-xr-xr-x (not writable by the user).

Extending and Simplifying the Build

How to Simplify: Drop FTP for SFTP

If you control the client devices, uninstall vsftpd (sudo apt purge vsftpd) and use SFTP. SFTP uses the existing SSH daemon on port 22. It requires no extra configuration, no passive port forwarding, and encrypts both the authentication and the data payload. You can map an SFTP drive in Windows using tools like Mountain Duck or SSHFS-Win, making the Pi appear as a native network drive.

How to Extend: USB 3.0 NAS and FTPS

The Raspberry Pi 5's PCIe 2.0 interface and USB 3.0 ports allow for much higher throughput than the microSD card's ~40MB/s ceiling. To extend this build into a true NAS:

  1. Attach a USB 3.0 to NVMe enclosure with a 1TB SSD.
  2. Format it as ext4 and mount it to /mnt/nas_drive via /etc/fstab.
  3. Create a dedicated FTP user whose home directory is mapped to the SSD mount point.
  4. Upgrade to FTPS by generating a self-signed certificate (openssl req -x509 -nodes -days 365 -newkey rsa:2048) and pointing rsa_cert_file in vsftpd.conf to the generated .pem file. This encrypts the session while maintaining compatibility with legacy FTP clients that support AUTH TLS.