Introduction
Fail2Ban monitors log files for authentication failures and automatically bans offending IP addresses using firewall rules. Ansible automates the full setup — install Fail2Ban, configure jails for SSH, web servers, mail, and custom applications, manage whitelists, set up email alerts, and deploy custom filters across your fleet.
Install and Configure
---
- name: Deploy Fail2Ban
hosts: all
become: true
vars:
fail2ban_bantime: 3600
fail2ban_findtime: 600
fail2ban_maxretry: 5
fail2ban_ignoreip:
- 127.0.0.1/8
- 10.0.0.0/8
fail2ban_destemail: security@example.com
fail2ban_action: action_mwl
tasks:
- name: Install Fail2Ban
ansible.builtin.package:
name: fail2ban
state: present
- name: Deploy jail.local
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
mode: '0644'
notify: restart fail2ban
- name: Start Fail2Ban
ansible.builtin.service:
name: fail2ban
state: started
enabled: true
handlers:
- name: restart fail2ban
ansible.builtin.service:
name: fail2ban
state: restarted
jail.local Template
# templates/jail.local.j2
# Managed by Ansible
[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = {{ fail2ban_findtime }}
maxretry = {{ fail2ban_maxretry }}
ignoreip = {{ fail2ban_ignoreip | join(' ') }}
banaction = iptables-multiport
destemail = {{ fail2ban_destemail }}
sender = fail2ban@{{ ansible_fqdn }}
mta = sendmail
action = %({{ fail2ban_action }})s
# SSH — always enabled
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 7200
{% if 'webservers' in group_names %}
# Nginx
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 10
{% endif %}
{% if 'apache' in group_names %}
# Apache
[apache-auth]
enabled = true
port = http,https
logpath = %(apache_error_log)s
maxretry = 5
[apache-badbots]
enabled = true
port = http,https
logpath = %(apache_access_log)s
maxretry = 2
bantime = 86400
{% endif %}
{% if 'mail_servers' in group_names %}
# Postfix
[postfix]
enabled = true
port = smtp,465,submission
logpath = /var/log/mail.log
maxretry = 5
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps
logpath = /var/log/mail.log
maxretry = 5
{% endif %}
Custom Filters
- name: Deploy custom filter for web app
ansible.builtin.copy:
dest: /etc/fail2ban/filter.d/myapp-auth.conf
content: |
[Definition]
failregex = ^.*Authentication failed for user .* from <HOST>.*$
^.*Invalid login attempt from <HOST>.*$
^.*Brute force detected from <HOST>.*$
ignoreregex =
mode: '0644'
notify: restart fail2ban
- name: Add custom jail
ansible.builtin.blockinfile:
path: /etc/fail2ban/jail.local
block: |
[myapp-auth]
enabled = true
port = 8080
filter = myapp-auth
logpath = /var/log/myapp/auth.log
maxretry = 5
bantime = 3600
notify: restart fail2ban
Progressive Banning
# Ban longer for repeat offenders
- name: Deploy recidive jail
ansible.builtin.blockinfile:
path: /etc/fail2ban/jail.local
marker: "# {mark} RECIDIVE JAIL"
block: |
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = 604800
findtime = 86400
maxretry = 3
notify: restart fail2ban
Monitoring
- name: Check Fail2Ban status
ansible.builtin.command: fail2ban-client status
register: f2b_status
changed_when: false
- name: Check specific jail
ansible.builtin.command: fail2ban-client status sshd
register: sshd_jail
changed_when: false
- name: Count banned IPs
ansible.builtin.shell: fail2ban-client status sshd | grep 'Currently banned' | awk '{print $NF}'
register: banned_count
changed_when: false
- name: Alert on high ban count
ansible.builtin.debug:
msg: "WARNING: {{ banned_count.stdout }} IPs banned on {{ inventory_hostname }}"
when: banned_count.stdout | int > 50
Unban IPs
- name: Unban specific IP
ansible.builtin.command: "fail2ban-client set {{ jail_name }} unbanip {{ ip_address }}"
when: ip_address is defined
- name: Unban all IPs in jail
ansible.builtin.command: "fail2ban-client unban --all"
when: unban_all | default(false)
Troubleshooting
Check Filter Regex
- name: Test filter against log
ansible.builtin.command: >
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
register: regex_test
changed_when: false
Fail2Ban Not Starting
- name: Validate config
ansible.builtin.command: fail2ban-client -t
register: config_test
changed_when: false
Related Articles
Conclusion
Ansible deploys Fail2Ban across your fleet with group-specific jails — SSH everywhere, Nginx jails on web servers, Postfix jails on mail servers. Use custom filters for application-specific log patterns, the recidive jail for repeat offenders, and whitelists for trusted networks. Template everything from variables so security policy is code.