Setup ProFTPD protect by fail2ban

Last week I needed to migrate a specific FTP host to a new VPC as part of some related infrastructure improvements that we are doing. Setting up the new server meant I could party with Ansible again ๐Ÿ™„ I’ll spare the details of all the hoops I needed to jump to get our outdated Ansible roles back working, but I will share how we configured our ProFTPD container and protect it with fail2ban running on the host.

ProFTPD Docker Log Driver

As I mentioned, we’ve set up ProFTPD to run in a Docker container using the following Ansible task, I’ve omitted some of the details for brevity.

- name: proftpd container
  become: yes
    name: proftpd
    log_driver: syslog
      tag: proftpd

There are two important parts, first the log_driver: syslog param. This will configure the output of the daemonized ProFTPD process to be logged to the rsyslogd process running on the host. Since we don’t configure the syslog-address param, Docker will automatically pipe it to unixgram:///dev/log, I assume ๐Ÿ˜….

The second configuration param which is important is log_options.tag: proftpd, this option will enable us to filter the logs from this specific container with rsyslog and write it to a specific file where we can point fail2ban to monitor and ban eventual unwanted visitors.

Configure rsyslog

This is an example log line of what you can find in /var/log/syslog before you configure the rsyslog filter

Oct 17 08:34:32 host proftpd[25415]: 2019-10-17 08:34:32,274 host proftpd[1] host.internal: ProFTPD 1.3.5 (stable) (built Wed May 6 2015 13:31:16 UTC) standalone mode STARTUP

The important log_options.tag: proftpd makes sure the loglines are annotated with the proftpd tag. In the example line, it’s this proftpd[25415]: string. The next step is to make sure these loglines are saved to a file for fail2ban to monitor. To make that happen, you need to create the following property based filter 1

You need to create a file 50-proftpd.conf in the rsyslog configuration directory /etc/rsyslog.d/ with the following filter:

:programname, isequal, "proftpd" -/var/log/proftpd.log
& stop

The first param programname is the property we want to filter on, rsyslog will make sure it’s only the tag we configured. It will strip everything after the first [, see the documentation for more information Second is the compare operations a simple isequal, we want it to match with our proftpd tag (3rd param), and finally, the fourth param we instruct it to write these logs to /var/log/proftpd.log. The second line & stop makes sure the logs aren’t processed any further down the line.

Once you configured rsyslog make sure to restart it sudo service rsyslog restart and afterward to check the status to see it didn’t encounter any issues during the restart (sudo service rsyslog service). Then tail the logs and try to login to the FTP server, use a wrong password, or a missing user to generate some failed login attempts. You should be able to see something like the following lines

Oct 17 13:27:26 ... proftpd[25415]: ...: SSH2 session opened.
Oct 17 13:27:29 ... proftpd[25415]: ...: USER felix (Login failed): Incorrect password
Oct 17 13:27:32 ... proftpd[25415]: ...: SSH2 session closed.
Oct 17 19:07:14 ... proftpd[25415]: ...: SSH2 session opened.
Oct 17 19:07:22 ... proftpd[25415]: ...: USER felix (Login failed): Incorrect password
Oct 17 19:07:25 ... proftpd[25415]: ...: SSH2 session closed.
Oct 17 19:07:30 ... proftpd[25415]: ...: SSH2 session opened.
Oct 17 19:07:30 ... proftpd[25415]: ...: USER unknown: no such user found from
Oct 17 19:07:32 ... proftpd[25415]: ...: USER unknown: no such user found from

Finally configure fail2ban

If you reached this, great success ๐ŸŽ‰ You’re almost finished. The final step in protecting our FTP service using fail2ban. You will need to configure a custom fail2ban filter for ProFTPD and enable the jail in the fail2ban configuration file. Fail2ban comes with default installed ProFTPD filter, but I’ve tried it first on the logs I produced, and it didn’t work. You can try it yourself. Fail2ban comes with fail2ban-regex installed, which helps you to test your filters. This blog helped me during my search:

fail2ban-regex /var/log/proftpd.log /etc/fail2ban/filter.d/proftpd.conf --print-all-matched

I didn’t get any matches => Lines: 11 lines, 0 ignored, 0 matched, 11 missed, so we constructed our “custom” filter. Create a file called /etc/fail2ban/filter.d/custom_proftpd.conf with the following instructions

# fail2ban filter for the ProFTPD FTP daemon

before = common.conf


_daemon = proftpd

failregex = \(\S+\[<HOST>\]\)[: -]+ USER \S+: no such user found from \S+ \[[0-9.]+\] to \S+:\S+\s*$
            \(\S+\[<HOST>\]\)[: -]+ USER \S+ \(Login failed\):.*\s+$
            \(\S+\[<HOST>\]\)[: -]+ Maximum login attempts \([0-9]+\) exceeded, connection refused.*\s+$
            \(\S+\[<HOST>\]\)[: -]+ SECURITY VIOLATION: \S+ login attempted\.\s+$
            \(\S+\[<HOST>\]\)[: -]+ Maximum login attempts \(\d+\) exceeded\s+$

ignoreregex =

These regexps work better with the logs we captured when we test it we got Lines: 11 lines, 0 ignored, 4 matched, 7 missed ๐Ÿฅณ

Now you’ve prepared the filter it’s time to instruct fail2ban to use it. Edit the fail2ban jail config file /etc/fail2ban/jail.conf or place a new config file in /etc/fail2ban/jail.d/ with the following configuration


enabled  = true
port     = 22
filter   = custom_proftpd
logpath  = /var/log/proftpd.log
maxretry = 6

Make sure you restart fail2ban (sudo service fail2ban restart) and check the status (sudo service fail2ban status) to make sure it’s running. Next, check if the correct jails are running using sudo fail2ban-client status. If you want to see more details of a specific jail pass it along as 3rd params sudo fail2ban-client status proftpd, you can see how many bans are applied for this jail.

So when you try out to log in a couple of times with a wrong password or missing users, you should be able to see the following logs from fail2ban.

fail2ban.jail           [24605]: INFO    Jail 'proftpd' started
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:31:29
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:31:42
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:31:49
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:32:03
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:32:07
fail2ban.filter         [24605]: INFO    [proftpd] Found - 2019-10-17 13:32:12
fail2ban.actions        [24605]: NOTICE  [proftpd] Ban
fail2ban.actions        [24605]: NOTICE  [proftpd] Unban

ps: Make sure you have an existing /var/log/proftpd.log file when you start fail2ban otherwise it crashes when is missing see fail2ban#1593

The setup is done on ubuntu 18.04 Bionic Beaver with Docker 19.03.3, rsyslog 8.32.0, fail2ban 0.10.2 and ProFTPD 1.3.5e


I know this isn’t the best solution, but we were in a hurry to get things working again. The reason we needed to fix it in the first place was we found out our original approach wasn’t logging the logs we expected. We’ve used the ExtendLog config option from ProFTPD and mounted a volume from the host to make the logs accessible for fail2ban.

ExtendedLog /var/log/proftpd/auth.log AUTH auth

The logs looked like

Debian [18/Oct/2019:17:49:19 +0000] "USER felix" [] 331
Debian [18/Oct/2019:17:49:19 +0000] "PASS (hidden)" [] 530

And were completely missing the information which we rely on in the fail2ban filter to work. The reason why we’ve switched to use syslog was we did see the stdout log of ProFTPD did contain all the right information we were looking for. We tried several different options to get ExtendLog work, but without any results, so we looked for an alternative. Using Syslog log driver was an alternative approach, in the future I hope we can run our FTP server on Kubernetes as we do for 95% of the rest of our infrastructure.

Hopefully, I can save somebody the trouble in the future