Les goélands lents

Idées, textes et images entre ciel, terre et mer

social: [ mastodon logo mastodon | github logo github | sourcehut logo sourcehut | radicle logo radicle ]

lang: [ fr | en ]

Une configuration Fail2Ban pour bloquer les bots arrivant sur nginx

2026-05-19 (mis à jour: 2026-05-20)

Tous les jours, les bots cherchent des serveurs à attaquer. Nous allons configurer un pare-feu évolutif pour les empêcher de nous scanner.

Si vous avez un serveur exposé à internet, vous avez probablement un serveur web qui tourne dessus. Si vous en regardez les journaux d’accès, vous verrez tout aussi probablement des tentatives d’accès à /PHPMyAdmin/, /.env et autres tentatives d’exploiter de potentielles failles.

Si vous avez un serveur exposé à internet, vous avez probablement un serveur SSH qui tourne dessus. Si, comme moi, vous ne disposez pas d’IP fixe, vous n’avez probablement pas non plus de règle de pare-feu interdisant les connexions depuis toute IP différente d’un ensemble restreint d’IPs connues.

Dans ce cas, vous utilisez probablement déjà Fail2Ban. Si vous ne l’utilisez pas, arrêtez de lire mon site et allez trouver un tuto pour l’installer et le configurer.

Exemple de journaux qu’on peut voir sur un serveur HTTP:

20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /wp-content/plugins/hellopress/wp_filemanager.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /r.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /ight.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /wpns.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /er.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:26 +0000] "GET /sql.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:27 +0000] "GET /lang/es.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:27 +0000] "GET /getir.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:27 +0000] "GET /core/init.php HTTP/1.1" 404 162 "-" "-"
20.104.227.76 - - [19/May/2026:17:36:27 +0000] "GET /aa.php HTTP/1.1" 404 162 "-" "-"

Je ne veux pas que mon serveur continue à accepter ce genre de tentatives, ça consomme des sockets, ça consomme des stat() sur le filesystem, bref, j’en veux pas pour plein de raisons.

J’ai donc créé un filtre Fail2Ban pour bannir ce genre de comportement.

# /etc/fail2ban/filter.d/nginx-404.conf
[Definition]
failregex = ^<HOST> - - \[\.*\] \"[A-Z]+ \S+ HTTP/\d\.?\d?\" 4[0-9]{2} \d+ \"-\" \".*\"$

Il nous faut ensuite activer ce filtre dans notre jail.local:

# /etc/fail2ban/jail.local

# … autres définitions de jails …

[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath  = %(nginx_access_log)s
           /var/log/nginx/*.vit.am.log
maxretry = 3
bantime = 3600
findtime = 600

# … autres définitions de jails …

NOTE : %(nginx_access_log)s est définit dans un des fichiers inclus par jail.local et correspond à une expression glob qui correspond à mon organisation des journaux d’accès nginx.

Et voilà! Il ne nous reste plus qu’à systemctl reload fail2ban.service et nous sommes bons.

Après un certain moment on peut vérifier que notre filtre fonctionne bien comme il faut:

root@myserver:~# fail2ban-client status nginx-404
Status for the jail: nginx-404
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     19
|  `- File list:        /var/log/nginx/goelands.vit.am.access.log /var/log/nginx/goelands.vit.am.log (liste tronquée)
`- Actions
   |- Currently banned: 2
   |- Total banned:     2
   `- Banned IP list:   20.104.227.76 128.140.125.52

Il est à noter qu’il y a plusieurs choses qu’on peut améliorer:

  1. la regex utilisée pour matcher les lignes de logs est coûteuse en cycles de part l’usage de .*, qui demande bien plus de cycles qu’un matching plus rigide, il faudrait que je me replonge dans les différents formats de lignes de log possibles et voir si je peux réduire le nombre de cycles requis pour l’évaluation de la regex; EDIT1: a été corrigé dans le listing de configuration approprié, on passe de 8980 steps à 516 avec la nouvelle (^<HOST> - \S+ \[\S+ [+-]\d{4}\] "\S+ \S+ \S+" 4[0-9]{2} \d+ "[^"]*" "[^"]*"$). EDIT2: Curieusement, la regex modifiée ne fonctionne pas sur fail2ban, malgré le fait qu’elle fonctionne (et match!) sur tous les utilitaires de debugging de regex que j’ai pu tenter. Je me suis rabattu sur une regex à peine plus performante que l’originale: ^<HOST> - - \[\.*\] \"[A-Z]+ \S+ HTTP/\d\.?\d?\" 4[0-9]{2} \d+ \"-\" \".*\"$
  2. un POST ne retournera probablement pas une 404 (Not Found), mais peut-être une 405 (Method Not Allowed), voir une 400 (Bad Request), il peut être intéressant de matcher sur les codes HTTP 40[0-9]; -> pareil, changé pour bannir toutes les HTTP 4xx. Attention à ne pas activer ça sur un service avec login ! Ou au moins exclure les 401.
  3. il faut s’assurer que les sites concernés aient peu de liens internes cassés car un utilisateur légitime risquerait de se faire bannir pour avoir simplement suivi un lien.

Si vous avez des commentaires ou retours à faire, c’est comme toujours sur @ololduck@fosstodon.org.