Amigapallo A web development blog by Anssi Kinnunen

Configuring fail2ban and iptables to get along with docker

If you run your applications in docker containers, use iptables as a firewall and want to block IPs with malicious signs with fail2ban, you’re in the right place.

The problem

The setup I struggled with is quite simple:

  • postfix running in a docker container
    • logging with the syslog driver into /var/log/mail.log
  • iptables and fail2ban installed to the host machine
    • [postfix] jail enabled in fail2ban

With the default settings fail2ban is creating the block rules into iptables’ INPUT chain in the filter table when irregular activity is detected in /var/log/mail.log:

-A fail2ban-postfix -s -j REJECT --reject-with icmp-port-unreachable

Despite that the connections from were still hitting my postfix server and I was seeing messages like this in /var/log/fail2ban.log:

Jun 19 12:09:32 localhost fail2ban.actions: INFO   [postfix] already banned

The reason for this are the PREROUTING chain rules in the iptables’ nat table that docker creates automatically:

# Jump into DOCKER chain
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

# NAT traffic going to port 25
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 25
    -j DNAT --to-destination

After NATing the packets hit the FORWARD chain in the iptables’ filter table, not INPUT as fail2ban is expecting.

The solution

After putting all the pieces together the solution was also quite simple. I created two new files:


# /etc/fail2ban/action.d/fail2ban-postfix-action.conf


actionstart = iptables -N fail2ban-postfix
              iptables -A fail2ban-postfix -j RETURN
              iptables -I FORWARD -p tcp -m multiport --dports 25 -j fail2ban-postfix

actionstop = iptables -D FORWARD -p tcp -m multiport --dports 25 -j fail2ban-postfix
             iptables -F fail2ban-postfix
             iptables -X fail2ban-postfix

actioncheck = iptables -n -L FORWARD | grep -q 'fail2ban-postfix[ \t]'

actionban = iptables -I fail2ban-postfix 1 -s <ip> -j DROP

actionunban = iptables -D fail2ban-postfix -s <ip> -j DROP

From fail2ban manual: The directory action.d contains different scripts defining actions. The actions are executed at well-defined moments during the execution of Fail2ban: when starting/stopping a jail, banning/unbanning a host, etc.


# /etc/fail2ban/filter.d/fail2ban-postfix-filter.conf

before = common.conf


_daemon = postfix/smtpd

# Note that the last 3 regex's are not present by default. The 4th one
# is the most important because the default ones do not match any of the
# lines the docker and the syslog driver are generating.
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
            ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
            ^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$
            ^.* postfix.* NOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
            ^.* postfix.* too many errors after AUTH from \S+\[<HOST>\]$
            ^.* postfix.* warning: \S+\[<HOST>\]: SASL LOGIN authentication failed: authentication failure$

ignoreregex =

From fail2ban manual: The directory filter.d contains mainly regular expressions which are used to detect break-in attempts, password failures, etc.

The last thing needed was to take the new action and filter into use in fail2ban config file:


enabled  = true
port     = smtp
filter   = fail2ban-postfix-filter
logpath  = /var/log/mail.log
banaction = fail2ban-postfix-action

That’s it, bans are now appearing correctly into /var/log/fail2ban.log:

2016-04-13 22:18:01,772 fail2ban.actions: WARNING [postfix] Ban
2016-04-14 06:22:42,922 fail2ban.actions: WARNING [postfix] Ban

The iptables rules are also in order:

-A FORWARD -p tcp -m multiport --dports 25 -j fail2ban-postfix
-A fail2ban-postfix -s -j DROP

I still had to solve the problem of loading the iptables rules on boot (with my own custom rules while trying not to break docker), but that’s going to be a separate blog post in the near future.