Sécuriser Nginx sur HestiaCP : atténuer les attaques DDoS de Couche 7 et optimiser la mise en cache du contenu dynamique

Comment personnaliser les templates Nginx de HestiaCP pour limiter les requêtes, bloquer les connexions lentes et activer le FastCGI Cache, afin de réduire l'impact des attaques DDoS applicatives et d'accélérer les pages dynamiques.

Sécuriser Nginx sur HestiaCP : atténuer les attaques DDoS de Couche 7 et optimiser la mise en cache du contenu dynamique

Quiconque administre un serveur avec HestiaCP finit tôt ou tard par rencontrer un problème classique : le site répond lentement, le CPU grimpe en flèche et les journaux de Nginx se remplissent de milliers de requêtes qui, prises isolément, semblent anodines. Il ne s'agit souvent pas d'un pic de trafic « légitime », mais d'une attaque DDoS applicative : ce type d'attaque ne vise pas à saturer la bande passante du réseau, mais à épuiser les ressources du serveur (CPU, mémoire, connexions à la base de données) en envoyant des requêtes HTTP qui paraissent parfaitement légitimes. Dans ce guide, nous allons intervenir directement sur les templates Nginx de HestiaCP pour ajouter des protections contre ce type d'attaque, et en profiter pour utiliser la mise en cache de Nginx afin d'alléger la charge sur les pages dynamiques (par exemple générées par PHP), rendant le site plus résistant même lorsque le trafic est légitime mais très élevé.

Qu'est-ce qu'une attaque DDoS de « Couche 7 » et pourquoi concerne-t-elle Nginx

Les attaques DDoS (Distributed Denial of Service, déni de service distribué) se répartissent en plusieurs catégories selon la couche du modèle OSI qu'elles visent. Les attaques sur les couches 3 et 4 (réseau et transport) cherchent à saturer la bande passante ou les tables de connexion, et sont généralement filtrées en amont du serveur, par exemple par l'hébergeur ou un service de protection comme un CDN. Les attaques de Couche 7 (la couche « application », celle où vit le protocole HTTP) sont plus insidieuses car chaque requête est presque indiscernable de celle d'un utilisateur réel : une requête vers une page de connexion, une recherche sur le site ou un point d'accès qui interroge la base de données, répétée des milliers de fois par seconde depuis de nombreuses adresses IP différentes, peut mettre un serveur à genoux même avec une connexion réseau loin d'être saturée.

Nginx, étant le serveur web qui reçoit en premier chaque requête HTTP dans une installation HestiaCP (qu'il fonctionne seul ou en reverse proxy devant Apache), est l'endroit idéal pour appliquer les premières contre-mesures : il peut limiter le nombre de requêtes acceptées depuis une même adresse IP, le nombre de connexions simultanées maintenues ouvertes, et peut « absorber » une partie du trafic en servant des réponses depuis le cache plutôt que de les régénérer à chaque fois.

Pourquoi ne pas modifier les templates d'origine de HestiaCP

Dans HestiaCP, la configuration Nginx de chaque domaine n'est pas écrite à la main : elle est générée automatiquement à partir de fichiers modèles appelés templates, qui contiennent des variables comme %domain%, %user%, %docroot% ou %backend_lsnr%, remplacées par les valeurs réelles du site lors de la création ou de la reconstruction de la configuration. Les templates se trouvent à deux emplacements sur le serveur, selon le scénario :

  • Nginx comme unique serveur web (avec PHP-FPM) : /usr/local/hestia/data/templates/web/nginx/php-fpm/
  • Nginx en reverse proxy devant Apache : /usr/local/hestia/data/templates/web/nginx/

La documentation officielle de HestiaCP recommande explicitement de ne pas modifier les fichiers de templates par défaut (par exemple default.tpl) : à chaque mise à jour du panneau, ces fichiers sont réécrits et les modifications seraient perdues. La bonne pratique consiste à copier le template que l'on souhaite personnaliser sous un nouveau nom, puis à ne modifier que les copies. Chaque template nécessite généralement plusieurs fichiers portant le même nom mais avec des extensions différentes, par exemple hardened.tpl, hardened.stpl (la variante avec certificat SSL) et, le cas échéant, le script associé hardened.sh.

Créer le template personnalisé étape par étape

Après s'être connecté au serveur en SSH en tant que root (ou avec un utilisateur disposant des droits adéquats), créer un template basé sur celui par défaut se fait ainsi :

cd /usr/local/hestia/data/templates/web/nginx/php-fpm/
cp default.tpl hardened.tpl
cp default.stpl hardened.stpl

À partir de là, hardened.tpl et hardened.stpl sont deux copies indépendantes que l'on peut modifier sans risquer de perdre les changements à la prochaine mise à jour de HestiaCP. Les sections suivantes montrent ce qu'il faut ajouter dans ces fichiers pour obtenir les deux protections principales : la limitation des requêtes et la mise en cache.

Limiter les requêtes par adresse IP : limit_req

La première contre-mesure, et la plus efficace, contre une attaque applicative consiste à empêcher une seule adresse IP d'envoyer un nombre excessif de requêtes en peu de temps. Nginx propose à cet effet le module limit_req, qui fonctionne en deux étapes : on définit d'abord une « zone de mémoire partagée » qui suit les requêtes par IP, puis on applique cette zone à un ou plusieurs blocs location de la configuration.

La définition de la zone se place dans le bloc http global de Nginx (fichier /etc/nginx/nginx.conf, ou un fichier dans /etc/nginx/conf.d/ inclus automatiquement), pas dans le template propre à chaque domaine :

# /etc/nginx/conf.d/ratelimit.conf
limit_req_zone $binary_remote_addr zone=generic:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;

Ici, generic est une zone qui autorise en moyenne 30 requêtes par minute par IP sur l'ensemble du site, tandis que login est une zone plus stricte (10 requêtes par minute) spécifiquement destinée aux pages d'authentification, cible privilégiée de ce type d'attaque. La valeur 10m indique la quantité de mémoire réservée pour suivre les adresses IP : 10 Mo suffisent pour environ 160 000 adresses distinctes, largement assez pour la plupart des sites.

Cela fait, dans le template hardened.tpl, on applique la zone générique à tout le site et la zone plus stricte à la page de connexion :

server {
    # ... directives générées automatiquement par HestiaCP ...

    limit_req zone=generic burst=15 nodelay;

    location = /wp-login.php {
        limit_req zone=login burst=5 nodelay;
        include /etc/nginx/fastcgi_params;
        fastcgi_pass %backend_lsnr%;
    }
}

Le paramètre burst permet d'absorber de courtes rafales de requêtes légitimes (par exemple quand un navigateur charge plusieurs ressources en parallèle) sans bloquer le visiteur, tandis que nodelay évite que les requêtes excédentaires soient mises en file d'attente, en renvoyant immédiatement une erreur 503 Service Temporarily Unavailable à qui dépasse la limite. Ainsi, un bot qui martèle la page de connexion reçoit rapidement des erreurs et cesse de consommer les ressources du serveur, tandis qu'un visiteur normal ne remarque rien.

Limiter les connexions simultanées : limit_conn

Une technique complémentaire consiste à limiter le nombre de connexions ouvertes en même temps par une même IP, utile contre les attaques qui ouvrent de nombreuses connexions et les gardent occupées le plus longtemps possible. Là aussi, la logique se fait en deux étapes :

# /etc/nginx/conf.d/ratelimit.conf
limit_conn_zone $binary_remote_addr zone=perip:10m;
server {
    limit_conn perip 20;
}

Avec cette configuration, une même adresse IP ne pourra pas avoir plus de 20 connexions ouvertes simultanément vers ce site : au-delà de ce seuil, Nginx répond par une erreur sans même transmettre la requête au backend PHP ou à Apache, ce qui économise des ressources précieuses.

Se protéger des connexions lentes (Slowloris)

Une famille d'attaques applicatives, connue sous le nom de Slowloris, n'envoie pas beaucoup de requêtes, mais ouvre des connexions et les maintient occupées en envoyant les données au compte-gouttes, extrêmement lentement, afin d'épuiser le nombre maximal de connexions que le serveur peut gérer. La contre-mesure consiste à fixer des délais d'attente stricts pour la réception des en-têtes et du corps de la requête, afin que Nginx referme les connexions trop lentes :

server {
    client_header_timeout 10s;
    client_body_timeout 10s;
    send_timeout 10s;
}

Les valeurs par défaut de Nginx pour ces délais sont de 60 secondes, un temps largement suffisant pour qu'une attaque Slowloris sature les connexions disponibles ; les réduire à 10 secondes (une valeur à ajuster selon la qualité moyenne des connexions des visiteurs réels du site) réduit nettement la fenêtre d'attaque sans impact perceptible pour les utilisateurs disposant d'une connexion normale.

Bloquer les IP et User-Agents suspects

Lorsque l'analyse des journaux pendant une attaque révèle des adresses IP récurrentes ou un User-Agent inhabituel utilisé par le trafic malveillant (de nombreux bots simples utilisent un User-Agent vide ou manifestement faux), on peut ajouter un blocage direct dans le template :

server {
    # Bloquer une plage d'IP
    deny 203.0.113.0/24;

    # Bloquer les requêtes avec un User-Agent vide ou suspect
    if ($http_user_agent ~* "^$|curl|wget|python-requests") {
        return 403;
    }
}

Cette technique doit être utilisée avec prudence : bloquer par User-Agent n'est utile que contre les bots les plus rudimentaires, car les plus sophistiqués imitent parfaitement le User-Agent d'un vrai navigateur ; il s'agit néanmoins d'un filtre quasi gratuit qui peut éliminer une partie du trafic indésirable avant qu'il n'atteigne le reste de la configuration.

Optimiser la mise en cache du contenu dynamique avec FastCGI Cache

Les protections vues jusqu'ici réduisent le trafic malveillant, mais le second pilier de la résilience consiste à faire en sorte que le trafic légitime (même très important, par exemple lors d'un pic de visites organique) pèse le moins possible sur le serveur. Pour les sites basés sur PHP (WordPress y compris) gérés par HestiaCP avec Nginx et PHP-FPM, la solution est la FastCGI Cache intégrée à Nginx : la première fois qu'une page est demandée, Nginx la génère normalement en transmettant la requête à PHP-FPM, mais en conserve une copie ; les requêtes suivantes vers la même page sont servies directement depuis le cache, sans plus solliciter PHP ni la base de données, avec un gain de vitesse énorme et une charge quasi nulle sur le serveur.

Ici encore, la zone de cache doit être définie au niveau global :

# /etc/nginx/conf.d/fastcgi-cache.conf
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=WPCACHE:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

Ensuite, dans le template hardened.tpl, HestiaCP prévoit un mécanisme spécifique : il suffit d'ajouter, juste après l'include fastcgi_params dans le bloc qui gère les fichiers PHP, une référence au fichier de configuration propre au domaine que le panneau génère automatiquement lorsque le cache est activé depuis l'interface :

location ~ [^/]\.php(/|$) {
    fastcgi_pass %backend_lsnr%;
    fastcgi_index index.php;
    include /etc/nginx/fastcgi_params;
    include %home%/%user%/conf/web/%domain%/nginx.fastcgi_cache.conf*;

    fastcgi_cache WPCACHE;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_use_stale updating error timeout invalid_header http_500;
    add_header X-FastCGI-Cache $upstream_cache_status;
}

L'en-tête X-FastCGI-Cache est très utile pour vérifier le fonctionnement : il apparaît dans la réponse HTTP avec la valeur HIT quand la page est servie depuis le cache, MISS quand elle est générée par PHP et mise en cache pour la première fois, et on peut le consulter facilement depuis le navigateur (outils de développement, onglet Réseau) ou en ligne de commande avec curl -I https://monsite.fr/. L'option fastcgi_cache_use_stale avec le paramètre updating est particulièrement importante lors d'un pic de trafic : si le cache d'une page expire au moment précis où arrivent des centaines de requêtes simultanées, Nginx n'en envoie qu'une seule à PHP pour la régénérer et continue de servir à toutes les autres la version précédente (légèrement périmée) jusqu'à ce que la nouvelle soit prête, évitant que toutes les requêtes ne frappent le backend en même temps.

Toutes les pages ne doivent pas être mises en cache : la zone d'administration d'un CMS, ou les pages affichant du contenu personnalisé pour l'utilisateur connecté, doivent être exclues, sinon un utilisateur pourrait voir les données d'un autre. HestiaCP gère déjà en partie ces cas via le fichier nginx.fastcgi_cache.conf généré par le panneau, mais il est préférable de vérifier, pour des CMS spécifiques comme WordPress, que des cookies tels que ceux de session ou de panier d'achat désactivent correctement le cache pour cette requête via une directive fastcgi_cache_bypass basée sur la valeur du cookie.

Appliquer le nouveau template et vérifier le résultat

Une fois les fichiers hardened.tpl et hardened.stpl prêts, l'activation sur un domaine se fait entièrement depuis l'interface de HestiaCP :

  1. Se connecter au panneau, ouvrir Web > [nom du domaine] > Edit.
  2. Dans le menu déroulant du template Nginx, sélectionner le nouveau template hardened (il apparaît automatiquement dans la liste une fois les fichiers copiés).
  3. Enregistrer : HestiaCP régénère la configuration du domaine à partir du nouveau template.

Depuis la ligne de commande, le même résultat s'obtient avec :

v-change-web-domain-tpl utilisateur domaine.fr hardened
v-rebuild-web-domain utilisateur domaine.fr yes

où le dernier paramètre yes force également le redémarrage des services web concernés. Avant d'appliquer la modification en production, il est toujours recommandé de vérifier la syntaxe de la configuration avec nginx -t, qui signale les éventuelles erreurs sans interrompre le service en cours, et de tester le comportement du cache et des limites de requêtes sur un domaine de test ou pendant une période de faible trafic.

Pour vider le cache FastCGI après une modification du site (par exemple une mise à jour de thème ou de contenu), HestiaCP propose le bouton « Purge Nginx Cache » sur la page de modification du domaine, ou la commande v-purge-nginx-cache utilisateur domaine.fr en ligne de commande.

Conclusion

Aucune des mesures décrites dans ce guide ne constitue, à elle seule, une protection complète contre une attaque DDoS applicative sérieuse et prolongée : cela nécessite des solutions dédiées en amont du serveur, comme un CDN avec protection anti-DDoS ou un pare-feu applicatif (WAF). Néanmoins, combiner la limitation des requêtes, la limitation des connexions, des délais d'attente stricts pour les clients lents et un cache FastCGI bien configuré sur les templates Nginx de HestiaCP relève significativement le niveau de trafic malveillant que le serveur peut absorber seul, tout en améliorant les performances du site lors des périodes de trafic légitime élevé. C'est le type d'intervention « peu coûteuse, très bénéfique » qu'il vaut la peine d'appliquer sur tout serveur HestiaCP exposé publiquement sur Internet.

Sources officielles

Partager

Quelle est votre réaction ?

Aimer Aimer 0
Je n'aime pas Je n'aime pas 0
Amour Amour 0
Drôle Drôle 0
En colère En colère 0
Triste Triste 0
Waouh Waouh 0