Hardening WordPress minimal : le socle sécurité que j’applique sur tous mes petits sites

La sécurité WordPress, sur un petit site, c’est rarement un projet. C’est un socle qu’on pose une fois, proprement, et qu’on oublie. Depuis que j’auto-héberge une trentaine de sites sur du Bedrock derrière CloudPanel et Cloudflare, j’ai fini par figer une routine minimale : trois briques, une demi-journée de mise en place, et l’immense majorité des attaques automatisées ne voient plus rien passer. Pas de sur-ingénierie, pas de plugin usine à gaz. Juste ce qui tient en production.

Voici le socle exact, dans l’ordre où je le déploie, avec les réglages que j’utilise vraiment.

Pourquoi un socle minimal, pas une forteresse

99 % de ce qui frappe un petit WordPress, c’est du bruit automatisé : bots qui tapent wp-login.php, scans de vulnérabilités connues, tentatives d’énumération d’utilisateurs via l’API REST. Aucune de ces attaques ne vous vise, vous : elles ratissent le web entier. Le socle ci-dessous coupe cette masse. Le reste — attaque ciblée, 0-day sur un plugin précis — ne se règle pas avec trois lignes de config, et sur un site vitrine ça n’arrive quasiment jamais.

Le meilleur hardening n’est pas celui qui bloque tout, c’est celui que vous maintenez réellement six mois plus tard.

Brique 1 — Wordfence, la couche applicative

Wordfence protège plus de 5 millions de sites, et sa version gratuite couvre l’essentiel : pare-feu applicatif (WAF), scanner de malware qui compare vos fichiers core à ceux du dépôt officiel et cherche les signatures de dizaines de milliers de variantes connues, limitation de débit, protection contre le brute force et 2FA. C’est largement suffisant pour un petit site.

Le piège à connaître : en version gratuite, les nouvelles règles de pare-feu arrivent avec 30 jours de retard sur la version premium. Sur un site vitrine c’est un compromis acceptable ; sur une boutique WooCommerce active, je bascule en payant sans hésiter.

Mon réglage minimal après installation :

  • Passer le pare-feu en mode « Extended Protection » (il s’exécute avant PHP, pas comme un simple plugin).
  • Activer la 2FA sur tous les comptes administrateur — non négociable.
  • Activer le blocage sur énumération des utilisateurs et masquer la version de WordPress.
  • Désactiver les alertes e-mail bavardes : garder seulement les alertes critiques, sinon vous ne les lirez plus.

Brique 2 — Fail2ban, la couche réseau

Wordfence agit au niveau PHP. Fail2ban agit avant, au niveau du serveur : il lit les logs Nginx et bannit l’IP au niveau du pare-feu système dès qu’un motif d’attaque se répète. L’attaquant ne consomme même plus de PHP. C’est la ceinture par-dessus les bretelles.

Je crée un filtre dédié aux POST sur wp-login.php, puis une jail qui bannit trois échecs en une heure. Voici la config que je déploie :

# /etc/fail2ban/filter.d/wp-login.conf
[Definition]
failregex = ^<HOST> -.*"POST /wp-login.php
            ^<HOST> -.*"POST /xmlrpc.php
ignoreregex =

# /etc/fail2ban/jail.d/wordpress.conf
[wp-login]
enabled  = true
filter   = wp-login
logpath  = /home/*/logs/nginx/access.log
port     = http,https
maxretry = 3
findtime = 3600
bantime  = 3600
Derrière Cloudflare, attention : si votre Nginx logge l’IP du proxy et non celle du visiteur, Fail2ban bannira Cloudflare — donc tout le monde. Assurez-vous que le vrai CF-Connecting-IP est restauré (module RealIP) avant d’activer la jail. Et testez toujours le filtre avant de l’armer :
fail2ban-regex /home/monsite/logs/nginx/access.log 
  /etc/fail2ban/filter.d/wp-login.conf

Brique 3 — Application Passwords, en finir avec les mots de passe partout

Introduites dans WordPress 5.6, les Application Passwords sont sans doute la fonctionnalité de sécurité la plus sous-utilisée du core. Ce sont des identifiants de 24 caractères, dédiés à l’authentification API (REST, XML-RPC), qui n’exposent jamais le mot de passe principal du compte.

Leur intérêt tient en trois points que j’exploite au quotidien :

  • Elles sont révocables individuellement : je grille une clé de script de déploiement sans toucher au login de l’utilisateur.
  • Elles sont stockées hachées et affichées une seule fois, à la création.
  • Elles sont liées à un couple utilisateur + application nommée : je sais toujours quelle intégration détient quelle clé.

Concrètement, dès qu’un outil en CLI ou un script doit parler à l’API, il utilise une App Password dédiée, jamais le mot de passe admin. Un exemple d’appel authentifié en Basic Auth sur HTTPS :

curl -u "simon:xxxx yyyy zzzz aaaa bbbb cccc" 
  https://monsite.com/wp-json/wp/v2/posts?status=draft

Et si un site n’expose aucune intégration API légitime, je désactive purement et simplement la fonctionnalité via le filtre wp_is_application_passwords_available. Ce qui n’existe pas ne s’attaque pas.

Le bonus qui coûte cinq minutes — les headers de sécurité

Tant que je suis dans la config Nginx, j’ajoute les en-têtes de sécurité. Ça ne bloque pas une attaque brute force, mais ça ferme le clickjacking, le MIME-sniffing et améliore la posture générale (et votre note sur les scanners type Mozilla Observatory).

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Le drapeau always est important : sans lui, les en-têtes ne sont pas envoyés sur les pages d’erreur. Le CSP, plus délicat à calibrer sans casser des plugins, je le pose dans un second temps, en mode Report-Only d’abord.

La checklist, dans l’ordre

  1. Installer Wordfence, activer le pare-feu en mode étendu et la 2FA sur les admins.
  2. Créer le filtre et la jail Fail2ban sur wp-login.php / xmlrpc.php, tester au fail2ban-regex avant d’activer.
  3. Vérifier la restauration de l’IP réelle si vous êtes derrière Cloudflare.
  4. Remplacer chaque mot de passe stocké dans un script/outil par une Application Password dédiée et nommée.
  5. Désactiver les Application Passwords si aucune intégration ne les utilise.
  6. Ajouter les quatre en-têtes de sécurité dans le vhost Nginx.
  7. Forcer HTTPS partout et vérifier que les mises à jour auto du core sont actives.
BriqueNiveauCe qu’elle coupe
WordfenceApplicatif (PHP)Malware, exploits plugins, brute force
Fail2banRéseau (pare-feu)Bots de login, avant PHP
App PasswordsAuthentification APIFuite du mot de passe principal
HeadersNavigateurClickjacking, MIME-sniffing

Ce que j’en retiens

Ce socle n’a rien de spectaculaire, et c’est précisément pour ça qu’il tient : trois briques complémentaires qui agissent à trois niveaux différents — réseau, applicatif, authentification — plus une couche de headers en bonus. Je le déploie en une demi-journée sur un nouveau site et je n’y retouche plus, sinon pour lire les rares alertes critiques. La sécurité d’un petit WordPress n’est pas une question de plugins empilés, c’est une question de couches bien choisies. Posez celles-ci, activez les mises à jour automatiques, et vous aurez éliminé la quasi-totalité de ce qui frappe réellement vos sites au quotidien.