Setup an FTP-server with quotas on RHEL or CentOS 6 or 7 with proftpd

Using FTP actually should be avoided whenever that’s possible but sometimes it’s just the most handy and convenient way of transferring files. In most cases, your FTP-users will be able to upload files to the FTP-server. To avoid that some users would fill up the complete machine, you can use quotas. In this post, I’ll describe how to setup a basic proftpd FTP-server with quotas on RHEL or CentOS 6 and 7.

Suprisingly, there isn’t a lot of documentation to find on how to setup proftpd with quotas on CentOS or RHEL. The documentation that I found was mainly for LDAP or MySQL integration and while that’s a good idea, I was looking for something more simple, using a simple file containing the quotas for the users.

The steps in this post are fairly equal for el6 and el7 installations so I’ll only point out version-specific actions. If nothing’s written, it means that an action can be executed fine on both RHEL/CentOS 6 and RHEL/CentOS 7.

Install proftpd

The first thing to do is to install the proftpd package from the repositories. The package can be found in the EPEL repository. To be able to use the EPEL-repository, we need to make it available first:

[jensd@cen ~]$ sudo yum -y install epel-release
...
Complete !

Now that we have access to packages in the EPEL repository, we can go ahead and install proftpd itself. To be able to test the ftp-server on the same machine, let’s also install the cli ftp-client:

[jensd@cen ~]$ sudo yum -y install proftpd ftp
...
Complete !

At the time of writing, for CentOS 7, the latest version was: 1.3.5-2 and for CentOS 6, the latest version was: 1.3.3g-4. Because of the difference in versions, there are some differences regarding quota configuration but more about that later.

Firewall and SELinux

Before we will start with the actual configuration, we need to allow FTP to pass trough our firewall and trough SELinux.

By default, iptables will block incoming connections so we need to open up TCP port 21 to allow incoming FTP-connections. Because we would like to support passive FTP, since most FTP-clients use that by default, we’ll also need to load the ip_conntrack_ftp kernel module for iptables.

For RHEL 6 or CentOS 6:

On el6, we need to configure iptables directly:

Add the ip_conntrack_ftp module in /etc/sysconfig/iptables-config:

# Load additional iptables modules (nat helpers)
#   Default: -none-
# Space separated list of nat helpers (e.g. 'ip_nat_ftp ip_nat_irc'), which
# are loaded after the firewall rules are applied. Options for the helpers are
# stored in /etc/modprobe.conf.
IPTABLES_MODULES="ip_conntrack_ftp"
...

Open up TCP port 21 for incoming traffic, save the rules and restart iptables to load the kernel module:

[jensd@cen ~]# vi /etc/sysconfig/iptables-config
[jensd@cen ~]# sudo iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 21 -j ACCEPT
[jensd@cen ~]# sudo service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[  OK  ]
[jensd@cen ~]# sudo /etc/init.d/iptables restart
iptables: Setting chains to policy ACCEPT: filter          [  OK  ]
iptables: Flushing firewall rules:                         [  OK  ]
iptables: Unloading modules:                               [  OK  ]
iptables: Applying firewall rules:                         [  OK  ]
iptables: Loading additional modules: ip_conntrack_ftp     [  OK  ]

FOR RHEL 7 OR CENTOS 7:

On el7 we can use firewalld to make things a little simpler:

[jensd@cen ~]$ sudo firewall-cmd --permanent --add-port=21/tcp
success
[jensd@cen ~]$ sudo firewall-cmd --permanent --add-service=ftp
success
[jensd@cen ~]$ sudo systemctl restart firewalld
[jensd@cen ~]$

To make sure that the conntrack_ftp module is loaded, let’s check which kernel modules are loaded (on both el6 and el7):

[jensd@cen ~]$ lsmod|grep nf_conntrack_ftp
nf_conntrack_ftp       18638  0
nf_conntrack          101024  9 nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,ip6table_nat,nf_conntrack_ftp,iptable_nat,nf_conntrack_ipv4,nf_conntrack_ipv6

Be default, SELinux won’t allow the FTP-users to write to their home directory which would immediately take away the need to set up a quota :) Let’s allow read and write access for the ftp-users to their home directory by setting SELinux boolean ftp_home_dir to 1:

[jensd@cen ~]$ sudo setsebool -P ftp_home_dir=1

Basic proftpd configuration

To get familiar with proftpd and it’s configuration file, let’s start with a basic FTP-server configuration so we can be sure that our FTP-server works fine before we start with quotas.

The configuration of proftpd is done in /etc/proftpd.conf. Change the configuration file to have something like this. I removed commented lines to prevent the page to be too long but it’s a good idea to keep them in your file.

ServerName                      "ProFTPD server"
ServerIdent                     on "FTP Server ready."
ServerAdmin                     root@localhost
DefaultServer                   on

# Cause every FTP user except adm to be chrooted into their home directory
DefaultRoot                     ~ !adm

# Use pam to authenticate (default) and be authoritative
AuthPAMConfig                   proftpd
AuthOrder                       mod_auth_pam.c* mod_auth_unix.c

# Don't do reverse DNS lookups (hangs on DNS problems)
UseReverseDNS                   off

# Set the user and group that the server runs as
User                            nobody
Group                           nobody

# To prevent DoS attacks, set the maximum number of child processes
# to 20.
MaxInstances                    20

# Disable sendfile by default since it breaks displaying the download speeds in
UseSendfile                     off

# Define the log formats
LogFormat                       default "%h %l %u %t \"%r\" %s %b"
LogFormat                       auth    "%v [%P] %h %t \"%r\" %s"

# Allow only user root to load and unload modules, but allow everyone
# to see which modules have been loaded
# (http://www.proftpd.org/docs/modules/mod_dso.html#ModuleControlsACLs)
ModuleControlsACLs              insmod,rmmod allow user root
ModuleControlsACLs              lsmod allow user *

# Global Config - config common to Server Config and all virtual hosts
# See: http://www.proftpd.org/docs/howto/Vhost.html
<Global>
  Umask                         022
  AllowOverwrite                yes
  <Limit ALL SITE_CHMOD>
        DenyAll
        AllowGroup ftp
  </Limit>
</Global>

Most of the configuration in the file is standard. The only real change is the denial of all users and to allow only users that are in the group ftp. Be default, all users are allowed to connect to the FTP-server, except the ones listed in /etc/ftpusers.

After the change in the configuration, to allow my own user (or any other existing user) to have access to FTP, I need to add it to the group ftp:

[jensd@cen ~]$ sudo usermod -a -G ftp jensd

To create a new user that will only be used for FTP:

[jensd@cen ~]$ sudo useradd ftpuser1 -G ftp -s /sbin/nologin
[jensd@cen ~]$ sudo passwd ftpuser1
Changing password for user ftpuser1.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.

All preparation for the FTP-server should be done so it’s time to start the service and test it.

For RHEL 6 or CentOS 6:

[root@localhost ~]# sudo /etc/init.d/proftpd start
Starting proftpd:                                          [  OK  ]

For RHEL 7 or CentOS 7:

[jensd@cen ~]$ sudo systemctl start proftpd

In case you would get an error that contains something like this:

Feb 25 14:04:24 localhost systemd: Starting ProFTPD FTP Server...
Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,531 cen proftpd[2335]: warning: unable to determine IP address of 'cen'
Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,531 cen proftpd[2335]: error: no valid servers configured
Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,532 cen proftpd[2335]: fatal: error processing configuration file '/etc/proftpd.conf'
Feb 25 14:04:24 localhost systemd: proftpd.service: control process exited, code=exited status=1
Feb 25 14:04:24 localhost systemd: Failed to start ProFTPD FTP Server.
Feb 25 14:04:24 localhost systemd: Unit proftpd.service entered failed state.

Then, make sure that your system can resolve it’s own hostname by adding it to the /etc/hosts file:

[jensd@cen ~]$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.202.101 cen
[jensd@cen ~]$ ping $(hostname) -c 1
PING cen (192.168.202.101) 56(84) bytes of data.
64 bytes from cen (192.168.202.101): icmp_seq=1 ttl=64 time=0.046 ms

--- cen ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.046/0.046/0.046/0.000 ms

Once the server is started, it’s time to test the connection and see if we can upload a file. To keep things visible and easy to follow, I used the cli ftp-client but you can use any client (like Filezilla) to test the connection.

[jensd@cen ~]$ ftp 192.168.202.101
Connected to 192.168.202.101 (192.168.202.101).
220 FTP Server ready.
Name (192.168.202.101:jensd): ftpuser1
331 Password required for ftpuser1
Password:
230 User ftpuser1 logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
227 Entering Passive Mode (192,168,202,101,181,136).
150 Opening ASCII mode data connection for file list
226 Transfer complete
ftp> put /etc/issue testfile
local: /etc/issue remote: testfile
227 Entering Passive Mode (192,168,202,101,140,175).
150 Opening BINARY mode data connection for testfile
226 Transfer complete
23 bytes sent in 9.9e-05 secs (232.32 Kbytes/sec)
ftp> ls
227 Entering Passive Mode (192,168,202,101,180,56).
150 Opening ASCII mode data connection for file list
-rw-r--r--   1 ftpuser1 ftpuser1       23 Feb 25 13:37 testfile
226 Transfer complete
ftp>

Configuration with quotas

Now that we’ve got a working FTP-server, let’s add a quota for the FTP-users. Change the configuration file in /etc/proftpd.conf to include some quota configuration:

ServerName                      "ProFTPD server"
ServerIdent                     on "FTP Server ready."
ServerAdmin                     root@localhost
DefaultServer                   on

# Cause every FTP user except adm to be chrooted into their home directory
DefaultRoot                     ~ !adm

# Use pam to authenticate (default) and be authoritative
AuthPAMConfig                   proftpd
AuthOrder                       mod_auth_pam.c* mod_auth_unix.c

# Don't do reverse DNS lookups (hangs on DNS problems)
UseReverseDNS                   off

# Set the user and group that the server runs as
User                            nobody
Group                           nobody

# To prevent DoS attacks, set the maximum number of child processes
# to 20.
MaxInstances                    20

# Disable sendfile by default since it breaks displaying the download speeds in
UseSendfile                     off

# Define the log formats
LogFormat                       default "%h %l %u %t \"%r\" %s %b"
LogFormat                       auth    "%v [%P] %h %t \"%r\" %s"

# Allow only user root to load and unload modules, but allow everyone
# to see which modules have been loaded
# (http://www.proftpd.org/docs/modules/mod_dso.html#ModuleControlsACLs)
ModuleControlsACLs              insmod,rmmod allow user root
ModuleControlsACLs              lsmod allow user *

# Global Config - config common to Server Config and all virtual hosts
# See: http://www.proftpd.org/docs/howto/Vhost.html
<Global>
  Umask                         022
  AllowOverwrite                yes
  <Limit ALL SITE_CHMOD>
        DenyAll
        AllowGroup ftp
  </Limit>
</Global>

# Quota support (http://www.proftpd.org/docs/contrib/mod_quotatab.html)
LoadModule mod_quotatab.c
LoadModule mod_quotatab_file.c
<IfModule mod_quotatab.c>
        QuotaEngine on
        QuotaLog /var/log/proftpd/quota.log
        #QuotaDefault is not supported on the proftp-version on CentOS 6
        QuotaDefault user false hard 5242880 0 0 0 0 0
        QuotaDisplayUnits Mb
        QuotaOptions ScanOnLogin
        QuotaDirectoryTally off
        <IfModule mod_quotatab_file.c>
                QuotaLimitTable file:/etc/proftpd/ftpquota.limittab
                QuotaTallyTable file:/etc/proftpd/ftpquota.tallytab
        </IfModule>
</IfModule>

As you can see, only the last lines were added. These lines load the modules that will be responsible for watching and enforcing the quotas and on line 55, we configure that every user will have a default quota of 5MB (5*1024*1024).

FOR RHEL 6 OR CENTOS 6:

Comment out line 55 in the above file and add quotas for every user with FTP-access (see further).

Before we can reload the configuration, we need to initialize the limit table and tally table that we mentioned in our configuration file. This needs to be done with the ftpquota script. Unfortunately, for el6, this script isn’t in any standard package so you’ll have to install it manually.

FOR RHEL 6 OR CENTOS 6:

Download the script here: http://jensd.be/download/ftpquota and install it as follows:

[jensd@cen ~]$ curl -O http://jensd.be/download/ftpquota
[jensd@cen ~]$ sudo cp ~/ftpquota /usr/bin/
[jensd@cen ~]$ sudo chmod +x /usr/bin/ftpquota
[jensd@cen ~]$ sudo yum -y install perl
...
Complete !

FOR RHEL 7 OR CENTOS 7:

Install the proftpd-utils package, it contains the script:

[jensd@cen ~]$ sudo yum -y install proftpd-utils
...
Complete !

After installing the ftpquota-script, use it to create the location for the tables and to initialize them:

[jensd@cen ~]$ sudo mkdir /etc/proftpd
[jensd@cen ~]$ cd /etc/proftpd
[jensd@cen proftpd]$ sudo ftpquota --create-table --type limit
[jensd@cen proftpd]$ sudo ftpquota --create-table --type tally
[jensd@cen proftpd]$ ll
total 8
-rw-r--r--. 1 root root 4 Feb 25 14:28 ftpquota.limittab
-rw-r--r--. 1 root root 4 Feb 25 14:28 ftpquota.tallytab

In case you don’t want tot use the default quota (defined on line 55 in the configuration file), you can manually add a different quota for every user. This is mandatory on RHEL 6 or CentOS 6. For example to set a quota of 10MB on user jensd:

[jensd@cen ~]$ cd /etc/proftpd
[jensd@cen ~]$ sudo ftpquota --add-record --type=limit --name=jensd --quota-type=user --bytes-upload=10485760

To let our changes take effect, we need to restart the FTP-server:

FOR RHEL 6 OR CENTOS 6:

[jensd@cen ~] sudo /etc/init.d/proftpd restart
Shutting down proftpd:                                     [  OK  ]
Starting proftpd:                                          [  OK  ]

FOR RHEL 7 OR CENTOS 7:

[jensd@cen proftpd]$ sudo systemctl restart proftpd

To test the quota, let’s create a file of 2MB to be uploaded:

[jensd@cen ~]$ dd if=/dev/zero of=~/2MB bs=1024 count=2048
2048+0 records in
2048+0 records out
2097152 bytes (2.1 MB) copied, 0.00903008 s, 232 MB/s

Now let’s test the FTP again and see if we’re limited in quota:

[jensd@cen ~]$ ftp 192.168.202.101
Connected to 192.168.202.101 (192.168.202.101).
220 FTP Server ready.
Name (192.168.202.101:jensd): ftpuser1
331 Password required for ftpuser1
Password:
230 User ftpuser1 logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
227 Entering Passive Mode (192,168,202,101,159,215).
150 Opening ASCII mode data connection for file list
-rw-r--r--   1 ftpuser1 ftpuser1       23 Feb 25 13:37 testfile
226 Transfer complete
ftp> put 2MB test2
local: 2MB remote: test2
227 Entering Passive Mode (192,168,202,101,148,192).
150 Opening BINARY mode data connection for test2
226 Transfer complete
2097152 bytes sent in 0.00537 secs (390385.70 Kbytes/sec)
ftp> put 2MB test3
local: 2MB remote: test3
227 Entering Passive Mode (192,168,202,101,237,213).
150 Opening BINARY mode data connection for test3
226 Transfer complete
2097152 bytes sent in 0.00323 secs (649273.09 Kbytes/sec)
ftp> put 2MB test4
local: 2MB remote: test4
227 Entering Passive Mode (192,168,202,101,196,180).
150 Opening BINARY mode data connection for test4
552 Transfer aborted. Disk quota exceeded
2097152 bytes sent in 0.00504 secs (416266.75 Kbytes/sec)
ftp> ls
227 Entering Passive Mode (192,168,202,101,231,37).
150 Opening ASCII mode data connection for file list
-rw-r--r--   1 ftpuser1 ftpuser1  2097152 Feb 25 13:40 test2
-rw-r--r--   1 ftpuser1 ftpuser1  2097152 Feb 25 13:40 test3
-rw-r--r--   1 ftpuser1 ftpuser1       23 Feb 25 13:37 testfile
226 Transfer complete
ftp>

As you can see, the last file, test4, didn’t get uploaded since that would get the total used space higher that our quota. The transfer log shows the following message to make this clear to the user: 552 Transfer aborted. Disk quota exceeded.

On the server itself we can monitor the quotas from the logfile in /var/log/proftpd/quota.log (as we definded in our configuration file):

[jensd@cen ~]$ sudo tail /var/log/proftpd/quota.log
2015-02-25 14:37:23,447 mod_quotatab/1.3.1[2663]: updating tally (442.00 bytes, 3 files difference)
2015-02-25 14:37:23,447 mod_quotatab/1.3.1[2663]: quotatab fs registered
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: using default limit from QuotaDefault directive
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found limit entry for user 'ftpuser1'
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found tally entry for user 'ftpuser1'
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: ScanOnLogin enabled, scanning current directory '/' for files owned by user 'ftpuser1'
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found 465.00 bytes in 4 files for user 'ftpuser1' in 0 secs
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: updating tally (0.00 bytes, 4 files difference)
2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: quotatab fs registered
2015-02-25 14:40:49,299 mod_quotatab/1.3.1[2675]: quotatab write(): limit exceeded, returning Disk quota exceeded

As you can see, it’s rather simple and easy to set up quotas for your FTP-server.

3 thoughts on “Setup an FTP-server with quotas on RHEL or CentOS 6 or 7 with proftpd

  1. i have a problem with quota, in quota log i get this: mai 01 15:39:37 mod_quotatab/1.3.0[3739]: error: unable to open QuotaTallyTable: Opération non permise
    mai 01 15:39:46 mod_quotatab/1.3.0[3739]: turning QuotaEngine off

    do you know how to repair this problem?
    thanks

    • Did you create the tables with ftpquota?
      You could check the permsisions on /etc/proftpd/ftpquota.*

      Also, check if SELinux isn’t causing you troubles. The easies way to test it, is to turn it of temporarily (setenforce 0) and check for messages with AVC in /var/log/audit/audit.log

Leave a Reply

Your email address will not be published. Required fields are marked *