Fail2ban with Nginx and Authelia

I have been playing around with getting Fail2ban working with Nginx and Autherlia on Nixos and in doing so I had some learnings I thought I would share. To start with, here is my finale working config:

 services.fail2ban = {
   enable = true;
   ignoreIP = [
   jails = {
     nginx-http-auth = ''
       enabled  = true
       port     = http,https
       logpath  = /var/log/nginx/*.log
       backend  = polling
       journalmatch =
     nginx-botsearch = ''
       enabled  = true
       port     = http,https
       logpath  = /var/log/nginx/*.log
       backend  = polling
       journalmatch =
     nginx-bad-request = ''
       enabled  = true
       port     = http,https
       logpath  = /var/log/nginx/*.log
       backend  = polling
       journalmatch =
     authelia = ''
       enabled  = true
       port     = http,https
 environment.etc = {
   "fail2ban/filter.d/authelia.conf".text = ''
      # Fail2Ban filter for Authelia

      # Make sure that the HTTP header "X-Forwarded-For" received by Authelia's backend
      # only contains a single IP address (the one from the end-user), and not the proxy chain
      # (it is misleading: usually, this is the purpose of this header).

      # the failregex rule counts every failed 1FA attempt (first line, wrong username or password) and failed 2FA attempt
      # second line) as a failure.
      # the ignoreregex rule ignores debug, info and warning messages as all authentication failures are flagged as errors

      failregex = ^.*Unsuccessful 1FA authentication attempt by user .*remote_ip="?<HOST>"? stack.*
                  ^.*Unsuccessful (TOTP|Duo|U2F) authentication attempt by user .*remote_ip="?<HOST>"? stack.*

      ignoreregex = ^.*level=debug.*

     journalmatch = _SYSTEMD_UNIT=authelia-main.service + _COMM=authelia

Authelia was very easy to get working and I mostly followed the official documents with some minor change due to the fact that on Nixos Fail2Ban uses systemd (journalctl) as the default backend for interfacing with logs.

Getting Fail2ban to work with Nginx on Nixos took a little more effort. This is largely the result of Nginx not using journalctl/syslog for logging, but instead write logs to files in /var/log/nginx/. This is a mismatch in default between Fail2ban and Nginx.

My first attempt was to get Nginx to write logs to Systemd though Syslogs. This would have been my preferred option as I like using journalctl, not sure if that make me odd. To do this, I added the following to the Nginx part of my Nixos config:

  services.nginx = {
   logError = "syslog:server=unix:/dev/log";
   commonHttpConfig = ''
     access_log syslog:server=unix:/dev/log;

This looked to be working as running journalctl -fu nginx.service and reviewing the output well poking the website produce what I was expecting. However, Fail2Ban did not seem to be parsing the logs, as it never identify a failure and never blocked me when testing.

For testing, I was using the nginx-http-auth filter and set up a test page with basic authentication and just did some failed logins. nginx-http-auth was a filter that came with Fail2ban by default, below is a copy of it:

$ cat /etc/fail2ban/filter.d/nginx-http-auth.conf 
# fail2ban filter configuration for nginx


mode = normal

mdre-auth = ^\s*\[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
mdre-fallback = ^\s*\[crit\] \d+#\d+: \*\d+ SSL_do_handshake\(\) failed \(SSL: error:\S+(?: \S+){1,3} too (?:long|short)\)[^,]*, client: <HOST>

mdre-normal = %(mdre-auth)s
mdre-aggressive = %(mdre-auth)s

failregex = <mdre-<mode>>

ignoreregex =

datepattern = {^LN-BEG}

journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx

# mdre-auth:
# Based on samples in
# Extensive search of all nginx auth failures not done yet.
# Author: Daniel Black

# mdre-fallback:
# Ban people checking for TLS_FALLBACK_SCSV repeatedly
# Author: Stephan Orlowsky

I would check the status of Fail2ban with the following and if it blocked my IP.

$ sudo fail2ban-client status nginx-http-auth
Status for the jail: nginx-http-auth 
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- Journal matches:  _SYSTEMD_UNIT=nginx.service + _COMM=nginx
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

$ sudo iptables -S | grep f2b

I never got this to work and if anyone has some suggestions, please let me know

My next attempt was to get Fail2ban to read the logs in /var/log/nginx without breaking the already working sshd and authelia jail. Just adding a “logpath” to the jail’s config as is suggested in many places on the web did not work. You will need to specify some other backend for that jail (e.g. polling, setting it to auto will give an error) and provide an empty value for journalmatch. Here is an example I would like to say I figured that out right away, but it took some time. Hopefully me putting this here will help someone else in the future.

A few final thoughts:

  1. I am by no means an expert on this, so any correction or suggesting are more than welcome.
  2. This was some documentation I found helpfully How To Protect an Nginx Server with Fail2Ban on Ubuntu 20.04 | DigitalOcean
  3. I included the nginx-http-auth jail just for testing, and plan on getting rid of it and basic authentication in favor of Authelia.
  4. Crowdsec looks to be the new Fail2ban. I know this is already packaged in Nixos, but I would really appreciate it if someone smarter than me would build some Nixos options for it.