Skip to main content

Chapter 11 · Transferring Files with vsftpd

Chapter Overview

In this chapter, I first introduce the File Transfer Protocol (FTP) and how to deploy the vsftpd service. I then walk through the most useful parameters in the main vsftpd configuration file and demonstrate two authentication modes in depth: anonymous access and local‑user mode.

Along the way I practice SELinux policy tuning, explain the theory and setup of the Trivial File Transfer Protocol (TFTP), and share practical tips from real deployments and troubleshooting so that I can handle production issues with confidence.

11.1 The File Transfer Protocol

For most people, the first reason to network computers is to obtain data, and file transfer is a core way to do that. Today’s internet connects a huge variety of devices: PCs, workstations, servers, minicomputers, mainframes, and supercomputers. Even personal computers may run different operating systems such as Windows, Linux, UNIX, or macOS. To move files among such diverse systems, the File Transfer Protocol (FTP) was created.

FTP is a client/server protocol for transferring files over the internet. By default it uses ports 20 and 21: port 20 carries data, and port 21 receives FTP commands and parameters from clients. FTP servers are usually deployed on internal networks; they are easy to set up and convenient to manage. Many FTP clients also support multi‑source downloading and resuming, which keeps FTP popular. Figure 11‑1 shows the basic FTP topology.

Figure 11-1 FTP transfer topology

An FTP server is a host that stores files and serves them to the internet according to the FTP standard; an FTP client is a host that initiates a connection to the server to create a data path. FTP has two operating modes:

Active mode — the server actively connects back to the client.
Passive mode — the server waits for the client to initiate the data connection (default).

Back in Chapter 8, I noted that firewalls typically filter traffic entering the internal network from the internet. In some environments I must switch FTP to active mode to pass through the firewall. Figure 11‑2 illustrates active‑mode flow.

Figure 11-2 Active-mode FTP

Because protocols such as FTP, HTTP, and Telnet transmit data in clear text, they are insecure by design. To meet the need for safer file transfer on Linux, I use the vsftpd service. vsftpd (very secure ftp daemon) is an open‑source FTP server for Linux that is both secure and fast. I can let clients log in anonymously or require local Linux user accounts.

After I configure local repositories, I install vsftpd:

root@linuxprobe:~# dnf install vsftpd
Updating Subscription Management repositories.
BaseOS 2.7 MB/s | 2.7 kB 00:00
AppStream 2.7 MB/s | 2.8 kB 00:00
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
vsftpd x86_64 3.0.5-8.el10 AppStream 174 k

Transaction Summary
================================================================================
Install 1 Package

Total size: 174 k
Installed size: 348 k
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : vsftpd-3.0.5-8.el10.x86_64 1/1
Running scriptlet: vsftpd-3.0.5-8.el10.x86_64 1/1
Installed products updated.

Installed:
vsftpd-3.0.5-8.el10.x86_64

Complete!

Allow FTP through firewalld:

root@linuxprobe:~# firewall-cmd --permanent --zone=public --add-service=ftp 
success
root@linuxprobe:~# firewall-cmd --reload
success

The main vsftpd configuration file (/etc/vsftpd/vsftpd.conf) is 126 lines long, but most lines are comments beginning with a hash (#). I don’t need to read every comment. To focus on active parameters, I filter out the comments with grep -v and overwrite the original file with only the effective settings, leaving just 12 lines:

root@linuxprobe:~# mv /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf_bak
root@linuxprobe:~# grep -v "#" /etc/vsftpd/vsftpd.conf_bak > /etc/vsftpd/vsftpd.conf
root@linuxprobe:~# cat /etc/vsftpd/vsftpd.conf
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=NO
listen_ipv6=YES
pam_service_name=vsftpd
userlist_enable=YES

Table 11‑1 lists commonly used parameters in vsftpd’s main configuration and their purposes. I’ll use them shortly in the labs.

Table 11‑1 Common vsftpd parameters and purposes

ParameterPurpose
listen=[YES|NO]Whether to run as a standalone listener
listen_address=IPAddress to listen on
listen_port=21Server listen port
download_enable=[YES|NO]Allow downloading files
userlist_enable=[YES|NO] userlist_deny=[YES|NO]Treat the listed users as allowed or denied
max_clients=0Max concurrent clients (0 means unlimited)
max_per_ip=0Max connections from one IP (0 means unlimited)
anonymous_enable=[YES|NO]Allow anonymous logins
anon_upload_enable=[YES|NO]Allow anonymous uploads
anon_umask=022umask applied to anonymous uploads
anon_root=/var/ftpAnonymous FTP root directory
anon_mkdir_write_enable=[YES|NO]Allow anonymous directory creation
anon_other_write_enable=[YES|NO]Allow other anonymous writes (rename, delete, etc.)
anon_max_rate=0Anonymous max throughput (bytes/sec; 0 means unlimited)
local_enable=[YES|NO]Allow local user logins
local_umask=022umask applied to local‑user uploads
local_root=/var/ftpLocal‑user FTP root
chroot_local_user=[YES|NO]Chroot users into the FTP root for safety
local_max_rate=0Local‑user max throughput (bytes/sec; 0 means unlimited)

11.2 The vsftpd service

vsftpd supports two authentication modes:

Anonymous access mode — the least secure; anyone can log in without a password. Use only for non‑sensitive public files.

Local‑user mode — authenticate with Linux system accounts and passwords; more secure and simple to configure.

The ftp package is a CLI client for FTP on Linux. I install it so I can test from the command line:

root@linuxprobe:~# dnf install ftp
Updating Subscription Management repositories.
Last metadata expiration check: 0:08:48 ago on Tue 25 Mar 2025 09:00:02 AM CST.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
ftp x86_64 0.17-96.el10 AppStream 62 k

Transaction Summary
================================================================================
Install 1 Package

Total size: 62 k
Installed size: 107 k
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : ftp-0.17-96.el10.x86_64 1/1
Running scriptlet: ftp-0.17-96.el10.x86_64 1/1
Installed products updated.

Installed:
ftp-0.17-96.el10.x86_64

Complete!

If I want to test from Windows, I can use FileZilla, FireFTP, SmartFTP, WinSCP, or Cyberduck—these GUIs are more powerful than the basic ftp command.

11.2.1 Anonymous access mode

As noted, anonymous mode is the least secure. Anyone can log in without a password, so I use it only for public, non‑critical files. If I must use it, I pair it with firewalld policies (from Chapter 8) so only internal clients can reach the server.

By default vsftpd disables anonymous mode. To practice, I enable anonymous users to upload and download and to create, delete, and rename files. Remember that enabling these permissions introduces risk; this is only for the lab.

Table 11‑2 lists the relevant parameters.

Table 11‑2 Parameters to open permissions for anonymous users

ParameterPurpose
anonymous_enable=YESEnable anonymous access
anon_umask=022umask for anonymous uploads
anon_upload_enable=YESAllow anonymous uploads
anon_mkdir_write_enable=YESAllow anonymous directory creation
anon_other_write_enable=YESAllow anonymous renames and deletes
root@linuxprobe:~# vim /etc/vsftpd/vsftpd.conf
1 anonymous_enable=YES
2 anon_umask=022
3 anon_upload_enable=YES
4 anon_mkdir_write_enable=YES
5 anon_other_write_enable=YES
6 local_enable=YES
7 write_enable=YES
8 local_umask=022
9 dirmessage_enable=YES
10 xferlog_enable=YES
11 connect_from_port_20=YES
12 xferlog_std_format=YES
13 listen=NO
14 listen_ipv6=YES
15 pam_service_name=vsftpd
16 userlist_enable=YES

Restart and enable vsftpd so the configuration takes effect and persists across reboots:

root@linuxprobe:~# systemctl restart vsftpd
root@linuxprobe:~# systemctl enable vsftpd
Created symlink '/etc/systemd/system/multi-user.target.wants/vsftpd.service' → '/usr/lib/systemd/system/vsftpd.service'.

Now I connect from a client with the ftp command. In anonymous mode, the username is anonymous and the password is empty. After login, the default directory is /var/ftp. I switch to pub and try to create a directory to verify write permissions:

root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): anonymous
331 Please specify the password.
Password:Press Enter here
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd pub
250 Directory successfully changed.
ftp> mkdir files
550 Create directory operation failed.
ftp> exit
221 Goodbye.

The system refused directory creation. Firewalld allows FTP and the vsftpd config enables anonymous writes—so what went wrong? Time to troubleshoot.

In anonymous mode the default directory is /var/ftp. Checking permissions shows only root has write access, so the mkdir fails. I change the owner to the system account ftp and try again:

root@linuxprobe:~# ls -ld /var/ftp/pub
drwxr-xr-x. 2 root root 6 Aug 13 2024 /var/ftp/pub
root@linuxprobe:~# chown -R ftp /var/ftp/pub
root@linuxprobe:~# ls -ld /var/ftp/pub
drwxr-xr-x. 2 ftp root 6 Aug 13 2024 /var/ftp/pub
root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): anonymous
331 Please specify the password.
Password:Press Enter here
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd pub
250 Directory successfully changed.
ftp> mkdir files
550 Create directory operation failed.
ftp> exit
221 Goodbye.

The error message has changed. Previously it was Permission denied, which pointed to UNIX permissions. Now it says Create directory operation failed, which suggests SELinux interference.

I list FTP‑related SELinux booleans:

root@linuxprobe:~# getsebool -a | grep ftp
ftpd_anon_write --> off
ftpd_connect_all_unreserved --> off
ftpd_connect_db --> off
ftpd_full_access --> off
ftpd_use_cifs --> off
ftpd_use_fusefs --> off
ftpd_use_nfs --> off
ftpd_use_passive_mode --> off
httpd_can_connect_ftp --> off
httpd_enable_ftp_server --> off
tftp_anon_write --> off
tftp_home_dir --> off

The culprit is typically ftpd_full_access. I enable it and make the change persistent across reboots:

root@linuxprobe:~# setsebool -P ftpd_full_access=on

Now the operations succeed:

root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): anonymous
331 Please specify the password.
Password: Just press Enter here
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd pub
250 Directory successfully changed.
ftp> mkdir files
257 "/pub/files" created
ftp> rename files database
350 Ready for RNTO.
250 Rename successful.
ftp> rmdir database
250 Remove directory operation successful.
ftp> exit
221 Goodbye.

Earlier I changed ownership on /var/ftp/pub to allow writes. Another approach is to grant write permission via mode bits. Do not set /var/ftp itself to 777, however—vsftpd has built‑in safety checks and will refuse to run with a writable chroot root. If I do this by mistake I will see an error like the following; I should always adjust permissions on subdirectories such as /var/ftp/pub instead:

root@linuxprobe:~# chmod -R 777 /var/ftp
root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): anonymous
331 Please specify the password.
Password:Press Enter here
500 OOPS: vsftpd: refusing to run with writable root inside chroot()
Login failed.
421 Service not available, remote server has closed connection

Tips:
Before conducting the next experiment, be sure to restore the virtual machine to its original state to avoid conflicts between multiple experiments.

11.2.2 Local‑user mode

Local‑user mode is more secure and easy to set up. If I used anonymous mode previously, I first disable it and then enable local‑user mode. Table 11‑3 lists the relevant parameters.

Table 11‑3 Parameters for local‑user mode

ParameterPurpose
anonymous_enable=NODisable anonymous mode
local_enable=YESEnable local‑user logins
write_enable=YESAllow writes on the server
local_umask=022umask for newly created files and directories; higher means fewer permissions
userlist_deny=YESEnable a deny list using ftpusers and user_list
userlist_enable=YESEnable the user list mechanism (allow or deny, depending on userlist_deny)

By default the needed parameters are present. The local_umask parameter may be new. A umask is a permission mask (complement) that influences the permissions of newly created files. In Linux, a regular file’s base permissions start at 666 and a directory’s at 777, but the final permissions are base – umask. With the default umask 022, files end up 644 and directories 755. Lowering the umask grants more permissions; raising it removes more.

I configure the file as follows and restart vsftpd:

root@linuxprobe:~# vim /etc/vsftpd/vsftpd.conf
1 anonymous_enable=NO
2 local_enable=YES
3 write_enable=YES
4 local_umask=022
5 dirmessage_enable=YES
6 xferlog_enable=YES
7 connect_from_port_20=YES
8 xferlog_std_format=YES
9 listen=NO
10 listen_ipv6=YES
11 pam_service_name=vsftpd
12 userlist_enable=YES
root@linuxprobe:~# systemctl restart vsftpd
root@linuxprobe:~# systemctl enable vsftpd
Created symlink '/etc/systemd/system/multi-user.target.wants/vsftpd.service' → '/usr/lib/systemd/system/vsftpd.service'.

I should now be able to log in with a local account. If I try root and see an immediate rejection before entering a password, that’s by design:

root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): root
530 Permission denied.
Login failed.
ftp> exit
221 Goodbye.

vsftpd ships with two user‑list files, ftpusers and user_list, which deny access to sensitive system accounts such as root. This prevents brute‑force attacks against those accounts via FTP. I inspect the files and remove root only if I am certain it is safe in my environment:

root@linuxprobe:~# cat /etc/vsftpd/user_list 
# vsftpd userlist
# If userlist_deny=NO, only allow users in this file
# If userlist_deny=YES (default), never allow users in this file, and
# do not even prompt for a password.
# Note that the default vsftpd pam config also checks /etc/vsftpd/ftpusers
# for users that are denied.
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
news
uucp
operator
games
nobody

root@linuxprobe:~# cat /etc/vsftpd/ftpusers
# Users that are not allowed to login via ftp
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
news
uucp
operator
games
nobody

After removing root from the lists, login succeeds:

root@linuxprobe:~# ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): root
331 Please specify the password.
Password: Enter the user's password here
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> exit
221 Goodbye.

In local‑user mode, login drops me into the user’s home directory, which the user owns, so UNIX permissions are usually fine. If operations are still denied after restoring the VM to a clean state, I re‑enable the SELinux boolean that allows FTP writes:

root@linuxprobe:~# getsebool -a | grep ftp
ftpd_anon_write --> off
ftpd_connect_all_unreserved --> off
ftpd_connect_db --> off
ftpd_full_access --> off
ftpd_use_cifs --> off
ftpd_use_fusefs --> off
ftpd_use_nfs --> off
ftpd_use_passive_mode --> off
httpd_can_connect_ftp --> off
httpd_enable_ftp_server --> off
tftp_anon_write --> off
tftp_home_dir --> off
root@linuxprobe:~# setsebool -P ftpd_full_access=on

Now file creation, rename, and deletion work:

root@linuxprobe:~#  ftp 192.168.10.10
Connected to 192.168.10.10 (192.168.10.10).
220 (vsFTPd 3.0.5)
Name (192.168.10.10:root): root
331 Please specify the password.
Password: Enter the user's password here
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> mkdir files
257 "/root/files" created
ftp> rename files database
350 Ready for RNTO.
250 Rename successful.
ftp> rmdir database
250 Remove directory operation successful.
ftp> exit
221 Goodbye.

11.3 TFTP

TFTP (Trivial File Transfer Protocol) transfers simple files between clients and servers over UDP. As its name suggests, it is a simplified, lightweight file transfer service—think of it as a minimal subset of FTP.

TFTP is far less capable than FTP (it cannot even traverse directories) and is weaker in security. Because it runs over UDP port 69, its transfers are also less reliable than FTP’s. However, TFTP requires no client authentication and thus reduces processing and bandwidth overhead, making it efficient for many small files.

I install the service and client utilities:

root@linuxprobe:~# dnf install tftp-server tftp 
Updating Subscription Management repositories.
BaseOS 2.7 MB/s | 2.7 kB 00:00
AppStream 2.7 MB/s | 2.8 kB 00:00
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
tftp x86_64 5.2-47.el10 AppStream 36 k
tftp-server x86_64 5.2-47.el10 AppStream 44 k

Transaction Summary
================================================================================
Install 2 Packages

Total size: 80 k
Installed size: 113 k
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : tftp-server-5.2-47.el10.x86_64 1/2
Running scriptlet: tftp-server-5.2-47.el10.x86_64 1/2
Installing : tftp-5.2-47.el10.x86_64 2/2
Running scriptlet: tftp-5.2-47.el10.x86_64 2/2
Installed products updated.

Installed:
tftp-5.2-47.el10.x86_64 tftp-server-5.2-47.el10.x86_64

Complete!

Then I restart TFTP, enable it at boot, and allow UDP/69 in the firewall (many default firewalls block it):

root@linuxprobe:~# systemctl restart tftp
root@linuxprobe:~# systemctl enable tftp
root@linuxprobe:~# firewall-cmd --zone=public --permanent --add-port=69/udp
success
root@linuxprobe:~# firewall-cmd --reload
success

TFTP’s root directory is /var/lib/tftpboot. I use the tftp client to test transfers. Table 11‑4 shows useful commands.

Table 11‑4 tftp client commands

CommandPurpose
?Help
putUpload a file
getDownload a file
verboseVerbose output
statusShow current status
binaryTransfer in binary mode
asciiTransfer in ASCII mode
timeoutSet retransmission timeout
quitExit
root@linuxprobe:~# echo "i love linux" > /var/lib/tftpboot/readme.txt
root@linuxprobe:~# tftp 192.168.10.10
tftp> get readme.txt
tftp> quit
root@linuxprobe:~# ls
anaconda-ks.cfg Documents Music Public Templates
Desktop Downloads Pictures readme.txt Videos
root@linuxprobe:~# cat readme.txt
i love linux

TFTP has more uses than shown here. In Chapter 19 I will combine TFTP with other software to build a complete automated system deployment solution.

Review Questions

  1. Briefly describe FTP and its port usage.
    Answer: FTP transfers files over the internet and uses ports 20 (data) and 21 (commands and parameters).

  2. What two authentication modes does vsftpd provide?
    Answer: Anonymous access (anyone can log in without a password) and local‑user mode (authenticate with Linux accounts).

  3. When logging into an anonymous FTP server with the ftp CLI, what username should you use?
    Answer: anonymous. In most GUI clients, no username or password is required.

  4. In anonymous mode, what is the default FTP root directory?
    Answer: /var/ftp, which contains a pub subdirectory by default.

  5. In local‑user mode, what is the default FTP root directory?
    Answer: The user’s home directory.

  6. If I suspect SELinux is blocking a service, how do I disable it temporarily?
    Answer: setenforce 0 (until reboot).

  7. If I don’t want users to roam the filesystem after login, what parameter confines them?
    Answer: chroot_local_user jails them in the FTP root.

  8. The FTP service works locally but fails from a remote host; pings between hosts succeed. What is the most likely cause?
    Answer: The firewall. Add an allow rule for FTP in firewalld.

  9. How does TFTP differ from FTP?
    Answer: TFTP is a simplified, lightweight service with fewer features, weaker security, and UDP‑based transfers.

  10. In TFTP, which commands upload and download files?
    Answer: put and get.