FTP virtual users are a way to grant access to your FTP server without creating actual operating system accounts for each user. This is great for security and manageability, especially when you need to give temporary or limited access to external parties.

Let’s see this in action. We’ll use vsftpd, a popular and secure FTP server, for this example.

Imagine you have a web server and you want to allow a client to upload new images to a specific directory, say /var/www/html/images. Instead of creating a user like client_uploader on the server’s OS, we’ll set up a virtual user client_uploader that only exists within vsftpd.

First, we need a way for vsftpd to authenticate these virtual users. The most common way is using a PAM module that reads user credentials from a flat file. We’ll use pam_userdb.

Here’s how you set up the user database:

  1. Create a text file with usernames and passwords. Let’s call it vuser_pass.txt. Each line will be username:password.

    client_uploader:S3cureP@ssw0rd!
    another_user:AnotherSecret
    
  2. Generate the database files. vsftpd needs these in a binary format.

    sudo db4o_load -T -f /etc/vsftpd/vuser_pass.txt -u 0 -g 0 /etc/vsftpd/vuser_pass.db
    
    • db4o_load is the tool to create Berkeley DB files.
    • -T tells it to read from a text file.
    • -f /etc/vsftpd/vuser_pass.txt specifies your input text file.
    • -u 0 -g 0 sets the user and group ID of the database owner to root. This is important for permissions.
    • /etc/vsftpd/vuser_pass.db is the output binary database file.
  3. Configure vsftpd to use virtual users. Edit your vsftpd.conf file (usually located at /etc/vsftpd.conf).

    # Enable local users (even though we're using virtual ones, this is a dependency)
    local_enable=YES
    
    # Guest login support
    guest_enable=YES
    guest_username=ftpuser
    
    # Use pam_userdb for authentication
    pam_service_name=vsftpd_virtual
    
    # Allow anonymous access (needed for guest login by default, but we'll restrict it)
    anonymous_enable=NO
    
    # Hide the real user's home directory (if guest_username is used)
    local_umask=077
    write_enable=YES
    chroot_local_user=YES
    allow_writeable_chroot=YES
    
    • guest_enable=YES and guest_username=ftpuser: This tells vsftpd to treat all authenticated users as "guests" and map them to a single real system user (ftpuser in this case). This ftpuser needs to exist on the system, but it doesn’t need a shell or a home directory that’s directly accessible. Its primary purpose is to own the files that virtual users will access.
    • pam_service_name=vsftpd_virtual: This points to the PAM configuration file we’ll create next.
  4. Configure PAM. Create a new PAM configuration file, for example, /etc/pam.d/vsftpd_virtual.

    auth    required        pam_userdb.so db=/etc/vsftpd/vuser_pass
    account required        pam_userdb.so db=/etc/vsftpd/vuser_pass
    
    • pam_userdb.so: This is the module that reads from our .db file.
    • db=/etc/vsftpd/vuser_pass: This tells pam_userdb to use the vuser_pass.db file (it assumes the .db extension).
  5. Create the system user for guest mapping.

    sudo useradd -m -d /home/ftpuser -s /sbin/nologin ftpuser
    
    • -m: Creates the home directory.
    • -d /home/ftpuser: Sets the home directory.
    • -s /sbin/nologin: Prevents this user from logging in directly via SSH or other shell-based methods.
  6. Set up directory permissions. The ftpuser needs to own the directories that virtual users will interact with. We also need to ensure that vsftpd can chroot users into their designated directories.

    Let’s say you want client_uploader to only access /var/www/html/images.

    sudo mkdir -p /var/www/html/images
    sudo chown ftpuser:ftpuser /var/www/html/images
    sudo chmod 755 /var/www/html/images
    

    Now, in vsftpd.conf, you’ll need to map the virtual user to this directory. vsftpd doesn’t have a direct per-user directory mapping in the main config for virtual users. Instead, you typically use chroot_local_user=YES and then potentially a local_root directive if you want to control which directory the guest_username maps to, or you rely on the system’s understanding of the guest_username’s home directory.

    A more granular approach involves user_config_dir. Create a directory for user-specific configurations:

    user_config_dir=/etc/vsftpd/user_conf
    

    Then, create a file inside this directory named after your virtual username:

    /etc/vsftpd/user_conf/client_uploader

    local_root=/var/www/html/images
    write_enable=YES
    anon_world_readable_only=NO
    
    • local_root=/var/www/html/images: This is the key. It tells vsftpd that when client_uploader logs in, their "root" directory should be /var/www/html/images.
    • write_enable=YES: Allows uploads for this specific user.
    • anon_world_readable_only=NO: Ensures that the files uploaded are not restricted to world-readable if local_umask is set to a restrictive value.
  7. Restart vsftpd.

    sudo systemctl restart vsftpd
    

Now, when you try to connect with ftp://client_uploader:S3cureP@ssw0rd!@your_server_ip, you will be authenticated using the vuser_pass.db file, mapped to the ftpuser system account, and then chrooted into /var/www/html/images where you can upload files.

The surprising thing about virtual users is how they leverage existing system mechanisms (like PAM and a single, non-login system user) to create a highly isolated and manageable user base without cluttering your /etc/passwd. The local_root directive within user-specific configuration files is what truly isolates each virtual user to their designated directory, making it feel like their own private space.

The next step you’ll likely encounter is managing different permissions or access levels for various virtual users, which often involves diving deeper into vsftpd’s more advanced configuration options or even exploring more sophisticated authentication backends.

Want structured learning?

Take the full Ftp course →