Cybersécurité

Caddy rate limiting et fail2ban : tutoriel 2026

13 دقائق للقراءة

Une fois votre site en ligne avec Caddy, deux menaces récurrentes apparaissent : les bots qui hammer vos endpoints (login, API publique) et les scrapers qui dévorent votre bande passante. La protection se fait à deux niveaux complémentaires : rate limiting au niveau Caddy (refus immédiat des excès) et fail2ban en background (bannissement IP des récidivistes). Voici le tutoriel complet 2026.

Voir notre guide Caddy complet.

Rate limiting via plugin Caddy

Le plugin caddy-ratelimit de Matt Holt est le standard de fait :

# Builder Caddy avec le plugin
xcaddy build --with github.com/mholt/caddy-ratelimit

# Ou télécharger une build pré-compilée incluant le plugin

Configuration de base

{
    order rate_limit before basic_auth
}

api.exemple.sn {
    rate_limit {
        zone dynamic_per_ip {
            key {remote_host}
            events 100
            window 1m
        }
    }

    reverse_proxy 127.0.0.1:3000
}

Cette config limite chaque IP à 100 requêtes par minute. Au-delà, Caddy retourne 429 Too Many Requests.

Rate limit ciblé sur endpoints sensibles

api.exemple.sn {
    # Login : très restrictif
    @login path /api/auth/login
    rate_limit @login {
        zone login_per_ip {
            key {remote_host}
            events 5
            window 5m
        }
    }

    # Reset password : ultra-restrictif
    @reset path /api/auth/reset-password
    rate_limit @reset {
        zone reset_per_ip {
            key {remote_host}
            events 3
            window 30m
        }
    }

    # Reste de l'API : raisonnable
    @api path /api/*
    rate_limit @api {
        zone api_per_ip {
            key {remote_host}
            events 200
            window 1m
        }
    }

    reverse_proxy 127.0.0.1:3000
}

Rate limit par utilisateur authentifié

api.exemple.sn {
    # Si Authorization header présent, key par token (par user)
    rate_limit {
        zone per_user {
            key {http.request.header.authorization}
            events 1000
            window 1h
        }
    }

    reverse_proxy 127.0.0.1:3000
}

fail2ban pour bannir les IP malveillantes

fail2ban analyse les logs Caddy et bloque les IP qui dépassent un seuil :

apt install -y fail2ban

# Configurer Caddy pour logger en JSON
# Caddyfile :
{
    log {
        output file /var/log/caddy/access.log
        format json
    }
}
# /etc/fail2ban/filter.d/caddy.conf
[Definition]
failregex = ^.*"remote_ip":"<HOST>".*"status":(?:401|403|404|429).*$
ignoreregex =
# /etc/fail2ban/jail.local
[caddy]
enabled  = true
port     = http,https
filter   = caddy
logpath  = /var/log/caddy/access.log
maxretry = 20
findtime = 600
bantime  = 3600
backend  = polling

systemctl restart fail2ban
fail2ban-client status caddy

Crowdsec : alternative moderne

Crowdsec est une alternative moderne à fail2ban avec partage de blacklist communautaire. Plugin Caddy disponible. Pratique si vous voulez une protection collaborative.

IP whitelist / blacklist statique

api.exemple.sn {
    # Bloquer des IPs/CIDRs connues comme malveillantes
    @blocked client_ip 192.0.2.0/24 198.51.100.0/24
    abort @blocked

    reverse_proxy 127.0.0.1:3000
}

Stratégie complète recommandée

  1. Caddy rate_limit sur login (5/5min), reset (3/30min), API (200/1min) — refuse immédiat
  2. fail2ban bannit 1h les IP ayant 20+ erreurs en 10 min — défense persistante
  3. Hetzner Cloud Firewall bloque les IPs whitelistées comme abuseurs récurrents
  4. Cloudflare devant filtre les bots évidents et les attaques DDoS

Adaptation Afrique de l’Ouest

Pour des sites avec audience mixte africaine + européenne, soyez prudents avec les rate limits trop agressifs : un FAI mobile sénégalais peut sortir derrière une seule IP CGNAT pour des centaines d’utilisateurs. Préférez des limites par session/auth plutôt que par IP pour les cas légitimes, et gardez fail2ban pour les comportements clairement abusifs.

Erreurs fréquentes

ErreurCauseSolution
« unknown directive rate_limit »Plugin pas inclusxcaddy build –with caddy-ratelimit
fail2ban ban tout le mondemaxretry trop bas + filter trop largeAjuster failregex pour ne matcher que les vrais abus
CGNAT FAI mobile bannit utilisateurs légitimesRate par IPRate par session ou par auth header
Logs Caddy n’apparaissent pasoutput file manquantAjouter le block log dans Caddyfile

Pour approfondir

Pour appliquer ce tutoriel sur un vrai serveur

Hostinger reste l’option la plus simple pour démarrer. Lien partenaire — votre achat soutient le blog sans surcoût.

Réserver un VPS Hostinger →

Lien d affiliation. Si vous achetez via ce lien, le blog reçoit une petite commission sans surcoût pour vous.

Etape 1 : Preparer le VPS et installer Caddy 2.8 sur Debian 12

Avant toute regle de limitation, il faut un Caddy a jour avec le module caddy-ratelimit compile dedans. Sur la plupart des hebergeurs cibles en Afrique de l’Ouest (Contabo Allemagne, Hetzner Helsinki, Scaleway Paris) le binaire officiel des depots Cloudsmith ne contient pas ce module. On le reconstruit avec xcaddy, l’outil de build officiel de Caddy.

sudo apt update && sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
xcaddy build --with github.com/mholt/caddy-ratelimit
sudo mv caddy /usr/local/bin/caddy
caddy version

La commande caddy version doit retourner v2.8.4 ou superieur, suivi de la liste des modules incluant http.handlers.rate_limit. Si le module manque, recommencez le build avec --with explicite. C’est le signal de reussite avant toute configuration.

Etape 2 : Ecrire un Caddyfile avec rate_limit par zone et par IP

Le module rate_limit raisonne en zones nommees, chacune avec une cle (souvent l’IP cliente) et un quota glissant. Pour un site WordPress vitrine accessible depuis Dakar, Abidjan ou Lome, on protege en priorite /wp-login.php, /xmlrpc.php et l’API REST. La fenetre est volontairement courte pour bloquer les bots de credential stuffing sans gener un visiteur reel qui rafraichit une page. La syntaxe ci-dessous declare deux zones distinctes pour separer login et API. Cela evite qu’un crawler legitime saturant l’API ne deverrouille indirectement le brute-force login.

{
  order rate_limit before basicauth
}
example.sn {
  rate_limit {
    zone login_zone { key {http.request.remote_host} events 5 window 1m }
    zone api_zone { key {http.request.remote_host} events 30 window 1m }
  }
  @login path /wp-login.php /xmlrpc.php
  rate_limit @login login_zone
  @api path /wp-json/*
  rate_limit @api api_zone
  reverse_proxy localhost:8080
  log { output file /var/log/caddy/access.log format json }
}

Apres caddy reload --config /etc/caddy/Caddyfile, un attaquant qui depasse 5 tentatives de login en 60 secondes recoit un HTTP 429. Le log JSON contient le champ rate_limit_exceeded que fail2ban va parser a l’etape suivante. La zone est globale au processus Caddy, donc partagee entre tous les vhosts si vous heberger plusieurs sites.

Etape 3 : Installer fail2ban et creer un filtre custom Caddy

Le paquet officiel Debian fournit fail2ban 1.0 qui suffit ici. Le filtre par defaut cible sshd, il faut en creer un pour Caddy qui lit le log JSON et detecte les 429 ou les 401 repetes sur wp-login. On utilise une regex JSON-aware compatible avec le format de log natif de Caddy. La double regex permet de capturer aussi les attaques distribuees ou Caddy n’a pas encore declenche son rate_limit mais ou WordPress retourne 401.

sudo apt install -y fail2ban
sudo tee /etc/fail2ban/filter.d/caddy-ratelimit.conf <<'EOF'
[Definition]
failregex = ^.*"remote_ip":"<HOST>".*"status":429.*$
            ^.*"remote_ip":"<HOST>".*"status":401.*"uri":"/wp-login.php".*$
ignoreregex =
EOF

Le placeholder <HOST> est remplace dynamiquement par fail2ban avec l’IP extraite. Validez la regex avec fail2ban-regex /var/log/caddy/access.log /etc/fail2ban/filter.d/caddy-ratelimit.conf : la sortie doit afficher au moins quelques lignes matchees si du trafic existe deja. Sans match, c’est probablement un probleme de format JSON dans le Caddyfile.

Etape 4 : Activer la jail caddy-ratelimit avec bantime progressif

Une jail decrit la sanction. On part sur 10 minutes au premier delit, multiplie par 4 a chaque recidive, plafonne a 7 jours. Le bantime progressif est une fonctionnalite native de fail2ban 1.0 qui evite de bannir 24h un visiteur qui s’est trompe de mot de passe une fois. Pour un blog technique senegalais, ce profil est plus juste qu’un ban fixe : il epargne les utilisateurs maladroits sans relacher la pression sur les bots.

sudo tee /etc/fail2ban/jail.d/caddy.conf <<'EOF'
[caddy-ratelimit]
enabled = true
filter = caddy-ratelimit
logpath = /var/log/caddy/access.log
maxretry = 3
findtime = 5m
bantime = 10m
bantime.increment = true
bantime.factor = 4
bantime.maxtime = 7d
action = iptables-multiport[name=caddy, port="80,443", protocol=tcp]
EOF
sudo systemctl restart fail2ban
sudo fail2ban-client status caddy-ratelimit

Le statut doit afficher Currently banned: 0 au demarrage. Apres quelques heures, on verra apparaitre les IPs bannies. Sur un VPS exposant un WordPress senegalais, comptez 5 a 20 bans par jour selon la notoriete du site et l’anciennete du domaine.

Etape 5 : Tester l’attaque depuis une seconde machine avec hey

Pour valider la chaine complete, on simule une attaque depuis une autre IP avec l’outil hey. On cible /wp-login.php avec 50 requetes en 30 secondes, ce qui depasse largement le quota de 5/min. Si vous testez depuis votre poste a Dakar avec une IP Sonatel partagee, prevenez les autres utilisateurs : ils risquent un ban collateral car de nombreux abonnes sortent derriere la meme IP NAT.

go install github.com/rakyll/hey@latest
hey -n 50 -c 5 https://example.sn/wp-login.php

Apres l’execution, le rapport hey doit montrer une majorite de status 429. Sur le VPS, sudo fail2ban-client status caddy-ratelimit doit lister l’IP de test dans Banned IP list. Si l’IP n’apparait pas, verifiez les permissions de lecture du fichier log par fail2ban (sudo chmod 644 /var/log/caddy/access.log).

Etape 6 : Brancher une alerte WhatsApp via Cloud API Meta

Pour etre notifie quand un ban survient, on configure un action.d custom qui appelle un webhook. Sur smartphone Sahel, WhatsApp Cloud API est plus fiable que SMS. On utilise l’API Graph v25.0 avec un template approuve security_alert_v1. Cout marginal : zero sur les 1000 conversations gratuites mensuelles WhatsApp Business utility.

sudo tee /etc/fail2ban/action.d/whatsapp-alert.conf <<'EOF'
[Definition]
actionban = curl -X POST "https://graph.facebook.com/v25.0/PHONE_ID/messages" -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" -d '{"messaging_product":"whatsapp","to":"221770000000","type":"template","template":{"name":"security_alert_v1","language":{"code":"fr"},"components":[{"type":"body","parameters":[{"type":"text","text":"<ip>"}]}]}}'
EOF

Ajoutez action = iptables-multiport[...] puis whatsapp-alert dans la jail. Vous recevrez un message au format IP bannie quelques secondes apres chaque ban. Pratique pour superviser un VPS depuis le terrain sans portable connecte au WiFi.

Etape 7 : Whitelister les bots legitimes Google et Bing

Le rate_limit ne doit jamais bannir Googlebot sous peine de chute SEO. Caddy permet une matcher exclusion native, plus rapide qu’un check fail2ban a posteriori. On combine plages IP officielles publiees par Google et Microsoft. Verifiez chaque trimestre la liste IP officielle Google sur developers.google.com/search/apis/ipranges/googlebot.json car elle evolue regulierement.

@trusted_bots remote_ip 66.249.64.0/19 207.46.0.0/16 40.77.0.0/16
handle @trusted_bots { reverse_proxy localhost:8080 }
handle { rate_limit @login login_zone
  reverse_proxy localhost:8080 }

Le bloc @trusted_bots bypass totalement le rate_limit pour Googlebot, Bingbot et Yandex. Les autres crawlers (Ahrefs, Semrush) restent soumis au quota normal pour eviter qu’ils saturent la bande passante du VPS.

Etape 8 : Monitoring Grafana avec Loki pour visualiser les bans

Pour explorer plus loin, on agrege les logs Caddy et fail2ban dans Loki, puis on construit un dashboard Grafana qui affiche bans/heure, top 10 IPs sources, repartition pays via GeoIP. Cout VPS : 10 EUR/mois (6 558 FCFA) pour Loki+Grafana sur le meme serveur. Le dashboard ID 13865 (Caddy v2 monitoring) est importable directement.

docker run -d --name=loki -p 3100:3100 grafana/loki:3.2.0
docker run -d --name=grafana -p 3000:3000 grafana/grafana:11.3.0

Le dashboard ID 13865 affiche en temps reel le nombre de 429 par minute. Une chute brutale signale soit un bot qui a abandonne, soit Caddy qui ne traite plus les requetes (panne a investiguer). On peut aussi brancher une alerte Grafana qui envoie un message WhatsApp si zero requete pendant 5 minutes.

Etape 9 : Sauvegarder la configuration et automatiser via Ansible

La derniere etape garantit la reproductibilite. Un playbook Ansible de 60 lignes deploie Caddyfile, jail.conf et filtre sur n’importe quel nouveau VPS en 90 secondes. Cle pour les agences web qui gerent plusieurs sites WordPress dakarois : on standardise la config securite, on versionne dans Git, on rejoue en cas de migration. Pour explorer plus loin, voir notre guide Coolify cle en main qui integre Caddy par defaut, et la checklist securite WordPress pour completer la defense applicative.

Etape 10 : Auditer et adapter les seuils selon le trafic reel

Apres deux a quatre semaines de production, ouvrez Grafana et observez la distribution des bans par heure. Si vous bannissez moins de 5 IPs par jour sur un site qui recoit 1000 visiteurs uniques quotidiens, votre seuil est probablement trop laxiste : passez la fenetre login_zone de 5 evenements/min a 3 evenements/min. A l’inverse, si vous bannissez 200 IPs par jour mais recevez des plaintes utilisateurs Dakar Sonatel, votre seuil est trop strict : la NAT carrier-grade fait sortir 50 a 200 abonnes derriere une seule IP publique.

Regle empirique pour l’Afrique de l’Ouest francophone : doublez les quotas par defaut sur tout site grand public. Conservez les valeurs strictes uniquement sur les endpoints d’administration ou financiers. Documentez chaque ajustement dans un fichier CHANGELOG-secu.md versionne dans Git pour pouvoir rollback si une mise a jour casse le comportement attendu.

Etape 11 : Bonus checklist trimestrielle de maintenance

Tous les trois mois, executez ce mini-audit en moins de 30 minutes : verifier la version Caddy avec caddy version (mise a jour mineure tous les deux mois), recompiler avec xcaddy si nouvelle release, mettre a jour fail2ban via apt upgrade, exporter la liste des IPs bannies persistantes vers une blacklist Cloudflare gratuite, tester la chaine complete avec hey depuis une IP externe, verifier que les certificats Let’s Encrypt se renouvellent (logs Caddy doivent montrer un renew tous les 60 jours), et confirmer que le webhook WhatsApp repond toujours sous 200 ms.

Cette routine evite la derive lente : un Caddy non maintenu six mois passe d’invisible a vulnerable. Le cout en temps est negligeable face au cout d’un piratage de WordPress ouest-africain qui finit invariablement par injection de spam SEO ou cryptomining sur le VPS.

Etape 10 : Auditer et adapter les seuils selon le trafic reel

Apres deux a quatre semaines de production, ouvrez Grafana et observez la distribution des bans par heure. Si vous bannissez moins de 5 IPs par jour sur un site qui recoit 1000 visiteurs uniques quotidiens, votre seuil est probablement trop laxiste : passez la fenetre login_zone de 5 evenements/min a 3 evenements/min. A l’inverse, si vous bannissez 200 IPs par jour mais recevez des plaintes utilisateurs Dakar Sonatel, votre seuil est trop strict : la NAT carrier-grade fait sortir 50 a 200 abonnes derriere une seule IP publique.

Regle empirique pour l’Afrique de l’Ouest francophone : doublez les quotas par defaut sur tout site grand public. Conservez les valeurs strictes uniquement sur les endpoints d’administration ou financiers. Documentez chaque ajustement dans un fichier CHANGELOG-secu.md versionne dans Git pour pouvoir rollback si une mise a jour casse le comportement attendu.

Etape 11 : Bonus checklist trimestrielle de maintenance

Tous les trois mois, executez ce mini-audit en moins de 30 minutes : verifier la version Caddy avec caddy version (mise a jour mineure tous les deux mois), recompiler avec xcaddy si nouvelle release, mettre a jour fail2ban via apt upgrade, exporter la liste des IPs bannies persistantes vers une blacklist Cloudflare gratuite, tester la chaine complete avec hey depuis une IP externe, verifier que les certificats Let’s Encrypt se renouvellent automatiquement (logs Caddy doivent montrer un renew tous les 60 jours), et confirmer que le webhook WhatsApp repond toujours sous 200 ms.

Cette routine evite la derive lente : un Caddy non maintenu six mois passe d’invisible a vulnerable. Le cout en temps est negligeable face au cout d’un piratage WordPress qui finit invariablement par injection de spam SEO ou cryptomining sur le VPS.

مشاركة