Chapitre 10.3 — WordPress en prod
⏱️ TL;DR — WordPress, c’est du PHP + MySQL. Sur un VPS, on le sert avec la pile LEMP : Linux, Enginx (Nginx), MySQL/MariaDB, PHP-FPM. Tu télécharges WordPress, tu lui crées une base et un utilisateur MySQL dédiés (jamais
root), tu configureswp-config.php, tu règles les permissions (seulwp-content/uploadsinscriptible parwww-data, le reste en lecture seule), tu écris unserverblock avec les bons permaliens (try_files $uri $uri/ /index.php?$args), tu protègeswp-config.phpetxmlrpc.php, tu montesclient_max_body_sizepour les médias, et tu durcis (login limité, MàJ, pas d’édition de fichiers depuis l’admin). WP-CLI automatise tout ça au terminal. WordPress est une cible d’attaque permanente : le déploiement est un exercice de sécurité.
🎯 Objectifs
- Monter une pile LEMP et y installer WordPress proprement.
- Créer une base et un utilisateur MySQL dédiés et remplir
wp-config.php. - Régler les permissions WordPress (uploads inscriptibles, code en lecture seule).
- Écrire le server block WordPress (permaliens, cache statique, fichiers protégés).
- Durcir WordPress et gérer le site en ligne de commande avec WP-CLI.
LEMP : la pile de WordPress sur VPS
Tu connais déjà les ingrédients. WordPress a besoin de trois choses par-dessus Linux :
- Nginx en façade (Partie 7) — le « E » de LEMP (Engine-X).
- PHP-FPM pour exécuter WordPress (chapitre 10.1).
- MySQL ou MariaDB pour stocker articles, options, utilisateurs.
MariaDB est le fork libre de MySQL, compatible et souvent préféré sur Debian/Ubuntu. Les deux conviennent. La base sera traitée en profondeur en Partie 11 ; ici on fait le strict nécessaire pour WordPress.
1. Installer les paquets
sudo apt update
# Nginx, MariaDB, PHP-FPM et les extensions PHP requises par WordPress
sudo apt install nginx mariadb-server \
php-fpm php-mysql php-xml php-mbstring php-curl php-gd php-zip php-imagick
# Sécurise l'install MariaDB (mot de passe root, retire les comptes anonymes)
sudo mysql_secure_installationmysql_secure_installation est un assistant : il pose un mot de passe root à la base, supprime l’utilisateur anonyme, la base de test, et l’accès root distant. Lance-le toujours après avoir installé MariaDB.
2. La base et l’utilisateur MySQL dédiés
Règle d’or : WordPress ne se connecte jamais avec le compte root de la base. On lui crée sa base et son utilisateur, avec des droits limités à cette base.
sudo mariadb-- Une base dédiée à ce site
CREATE DATABASE formacampus_blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Un utilisateur dédié, qui n'écoute qu'en local (127.0.0.1)
CREATE USER 'wp_formacampus'@'localhost' IDENTIFIED BY 'un_mot_de_passe_long_et_unique';
-- Droits limités À CETTE base uniquement (pas *.*)
GRANT ALL PRIVILEGES ON formacampus_blog.* TO 'wp_formacampus'@'localhost';
FLUSH PRIVILEGES;
EXIT;🔒 Sécurité — Deux points non négociables. (1) L’utilisateur n’a de droits que sur sa base (
formacampus_blog.*, jamais*.*) : si WordPress est compromis, l’attaquant ne touche pas les autres bases du VPS. (2) L’utilisateur est@'localhost'et la base n’écoute qu’en local — le port 3306 ne doit jamais être ouvert sur Internet (UFW le bloque, cf. Partie 5). Une base MySQL exposée est scannée et attaquée en quelques minutes.
3. Télécharger WordPress et configurer wp-config.php
cd /tmp
# Récupère la dernière version stable, décompresse
curl -O https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
# Copie vers la racine web du site
sudo mkdir -p /var/www/blog
sudo cp -a /tmp/wordpress/. /var/www/blog/Puis on crée wp-config.php à partir du modèle et on y renseigne la base :
cd /var/www/blog
sudo cp wp-config-sample.php wp-config.php// wp-config.php (extrait) — les identifiants de LA base dédiée
define( 'DB_NAME', 'formacampus_blog' );
define( 'DB_USER', 'wp_formacampus' );
define( 'DB_PASSWORD', 'un_mot_de_passe_long_et_unique' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
// Colle ici les clés de salage générées (voir ci-dessous) — jamais celles par défaut
// define('AUTH_KEY', '...'); etc.
// Durcissement : interdit l'édition de thèmes/plugins depuis l'admin
define( 'DISALLOW_FILE_EDIT', true );🐚 Au terminal — WordPress fournit un générateur de clés de salage (les
AUTH_KEY,SECURE_AUTH_KEY… qui chiffrent les cookies de session). Récupère des clés fraîches et colle-les danswp-config.php:
curl -s https://api.wordpress.org/secret-key/1.1/salt/
# Copie la sortie (8 lignes define(...)) dans wp-config.php, en remplaçant les valeurs par défaut4. Les permissions : uploads inscriptible, le reste non
Le principe est le même que pour Symfony (chapitre 10.2) : le code en lecture seule pour www-data, seul le dossier qui doit être écrit l’est. Pour WordPress, ce dossier est wp-content/uploads (les médias téléversés).
# Propriété : ton user de déploiement possède, www-data est le groupe
sudo chown -R deploy:www-data /var/www/blog
# Dossiers en 750, fichiers en 640 (www-data lit, n'écrit pas)
sudo find /var/www/blog -type d -exec chmod 750 {} \;
sudo find /var/www/blog -type f -exec chmod 640 {} \;
# SEULE exception : les uploads, inscriptibles par www-data
sudo chown -R www-data:www-data /var/www/blog/wp-content/uploads
sudo chmod -R 770 /var/www/blog/wp-content/uploads⚠️ Piège — La tentation du
chmod -R 777sur toutwp-content(ou tout WordPress) « pour que les mises à jour et les plugins marchent ». C’est la porte ouverte : un plugin vérolé ou une faille peut alors réécrire ton code et déposer un webshell. On garde le code en lecture seule et on n’ouvre en écriture queuploads(et, temporairement et au cas par cas, ce dont une MàJ a besoin — souvent gérée plutôt via WP-CLI ouDISALLOW_FILE_MODS).
5. Le server block WordPress
Les permaliens WordPress (URLs jolies type /mon-article/) reposent sur une règle Nginx : si l’URL ne correspond à aucun fichier réel, on la passe à index.php qui laisse WordPress router.
server {
listen 80;
server_name blog.formacampus.fr;
root /var/www/blog;
index index.php;
# Autorise l'upload de médias volumineux (défaut Nginx : ~1 Mo)
client_max_body_size 64m;
# LA règle des permaliens : fichier réel, sinon WordPress via index.php
location / {
try_files $uri $uri/ /index.php?$args;
}
# Exécution du PHP via FPM
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Protège wp-config.php : aucun accès web
location = /wp-config.php {
deny all;
}
# Bloque xmlrpc.php (vecteur d'attaques brute-force / DDoS), rarement utile
location = /xmlrpc.php {
deny all;
}
# Interdit l'exécution de PHP dans les uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
# Cache long des assets statiques
location ~* \.(css|js|jpg|jpeg|png|gif|webp|svg|woff2|ico)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
}Les directives à retenir :
try_files $uri $uri/ /index.php?$args: le cœur des permaliens. On tente le fichier, puis le dossier, puis on retombe surindex.phpen lui repassant la query string via$args.client_max_body_size 64m: le défaut Nginx (~1 Mo) refuse l’upload d’une image ou vidéo un peu lourde avec une erreur 413. On le monte pour la médiathèque. (À accorder avecupload_max_filesizeetpost_max_sizecôté PHP.)location = /wp-config.php { deny all; }:wp-config.phpcontient tes identifiants de base — aucun accès web ne doit y mener.location = /xmlrpc.php { deny all; }: cette ancienne API est un vecteur classique de brute-force et d’amplification. Si tu n’en as pas besoin (pas d’app mobile WP, pas de pingback), bloque-la.
⚠️ Piège — Oublier de protéger
wp-config.phpou laisserxmlrpc.phpgrand ouvert. Ce sont deux des premières choses que les bots testent. Unwp-config.phplisible = fuite de tes identifiants de base ; unxmlrpc.phpouvert = brute-force massif de comptes. Les deuxdeny allci-dessus coûtent trois lignes et ferment deux failles récurrentes.
6. Durcir WordPress
WordPress est la cible la plus attaquée du web (parts de marché obligent). Le durcissement minimal :
- Limiter les tentatives de login (plugin type Limit Login Attempts, ou
fail2bansur/wp-login.php) contre le brute-force. - Mises à jour : cœur, thèmes et plugins à jour, toujours. La majorité des piratages exploitent des versions périmées.
- Pas d’édition de fichiers depuis l’admin :
define('DISALLOW_FILE_EDIT', true);empêche qu’un compte admin compromis n’édite du PHP directement dans le navigateur. - Supprimer thèmes et plugins inutilisés (chaque plugin est une surface d’attaque).
- Comptes : pas d’utilisateur nommé
admin, des mots de passe forts, la double authentification si possible. - HTTPS partout (Partie 8) et forcer l’admin en HTTPS (
FORCE_SSL_ADMIN).
🔒 Sécurité — WordPress illustre un principe général : la surface d’attaque, c’est surtout l’écosystème (thèmes, plugins), pas le cœur. Le cœur WP est plutôt sain et corrigé vite ; ce sont les plugins tiers mal maintenus qui font entrer les attaquants. Moins de plugins, tous à jour, tous issus de sources fiables : c’est 80 % de la sécurité WordPress.
7. WP-CLI : gérer WordPress au terminal
WP-CLI est l’outil en ligne de commande de WordPress. Il évite l’interface web pour tout ce qui est administration : installer, mettre à jour, gérer plugins et utilisateurs, importer/exporter — parfait pour scripter et déployer.
# Installer et déployer WordPress sans jamais toucher l'assistant web
wp core download
wp config create --dbname=formacampus_blog --dbuser=wp_formacampus --dbpass='...'
wp core install --url=blog.formacampus.fr --title="Blog FormaCampus" \
--admin_user=redac --admin_email=blog@formacampus.fr
# Maintenance courante
wp core update && wp plugin update --all && wp theme update --all💡 Réflexe — Lance WP-CLI en tant que
www-data(ou avec les bons droits) pour que les fichiers créés aient le bon propriétaire :sudo -u www-data wp plugin update --all. Sinon tu crées des fichiers appartenant àrootquewww-datane pourra pas gérer ensuite.
🧭 Sur FormaCampus — À côté de l’API Symfony et du front Next.js, FormaCampus héberge son blog éditorial sous WordPress, sur
blog.formacampus.fr, sur le même VPS. Il partage le PHP-FPM du serveur (dans un pool distinct pour l’isolation, cf. chapitre 10.1) et a sa base MariaDB dédiée (formacampus_blog, utilisateurwp_formacampus@localhost). L’équipe a bloquéxmlrpc.php, protégéwp-config.php, misDISALLOW_FILE_EDIT, montéclient_max_body_sizeà 64 Mo pour les visuels d’articles, et gère les mises à jour via WP-CLI dans un cron. Le blog n’est pas un « petit à-côté » côté sécurité : c’est souvent par lui qu’on essaie d’entrer.
📚 La doc — La référence d’install est le « Hardening WordPress » du Codex/Developer Handbook et la doc WP-CLI (
wp help). Le cours WordPress du hub traite WordPress côté création de sites (thèmes, plugins, contenu) ; ce chapitre-ci se concentre sur sa mise en production et son durcissement sur le VPS.
✏️ Exercices
Exercice 1 — La base dédiée. Écris les commandes SQL pour créer une base boutique et un utilisateur wp_boutique qui n’a de droits que sur cette base, accessible uniquement en local.
✅ Solution
CREATE DATABASE boutique CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_boutique'@'localhost' IDENTIFIED BY 'mot_de_passe_fort_unique';
GRANT ALL PRIVILEGES ON boutique.* TO 'wp_boutique'@'localhost';
FLUSH PRIVILEGES;Points clés : utf8mb4 (support complet des emojis/caractères), droits limités à boutique.* (jamais *.*), utilisateur @'localhost' (pas d’accès distant). Le port 3306 reste fermé au monde côté pare-feu.
Exercice 2 — Erreur 413 sur un upload. Une rédactrice ne peut pas téléverser une image de 8 Mo : le navigateur affiche « 413 Request Entity Too Large ». Où est le blocage et comment le régler proprement ?
✅ Solution
Le 413 vient de Nginx : la directive client_max_body_size (défaut ~1 Mo) rejette la requête trop grosse. On la monte dans le server block, par ex. client_max_body_size 64m;, puis nginx -t && systemctl reload nginx. Il faut aussi accorder PHP : upload_max_filesize et post_max_size dans le php.ini de FPM doivent être au moins aussi élevés, puis reload php-fpm. Les deux limites (Nginx et PHP) doivent être cohérentes.
Exercice 3 — Durcis ce WordPress. Un WordPress fraîchement installé est en ligne avec les réglages par défaut. Cite quatre mesures de durcissement immédiates (config + Nginx).
✅ Solution
Par exemple : (1) deny all sur wp-config.php et sur xmlrpc.php dans Nginx ; (2) interdire l’exécution de PHP dans wp-content/uploads (deny all sur .php là-dedans) ; (3) define('DISALLOW_FILE_EDIT', true); dans wp-config.php ; (4) permissions saines (code en 640/750, seul uploads inscriptible, jamais 777). On ajoute volontiers : limiter les tentatives de login (fail2ban/plugin), forcer HTTPS et l’admin en SSL, supprimer les plugins/thèmes inutilisés, tout tenir à jour.
🧠 Quiz de révision
1. Que désigne la pile LEMP et quel composant exécute réellement le code WordPress ?
Linux, Engine-X (Nginx), MySQL/MariaDB, PHP-FPM. C’est PHP-FPM qui exécute le code WordPress ; Nginx sert la façade et lui passe les requêtes .php en FastCGI ; la base stocke les données.
2. Pourquoi crée-t-on un utilisateur MySQL dédié plutôt que d’utiliser root ?
root ?Pour limiter les dégâts en cas de compromission : l’utilisateur dédié n’a de droits que sur la base du site (pas *.*) et n’écoute qu’en local. root donnerait accès à toutes les bases du serveur. Principe de moindre privilège.
3. Quelle règle try_files fait fonctionner les permaliens WordPress, et que fait-elle ?
try_files fait fonctionner les permaliens WordPress, et que fait-elle ?try_files $uri $uri/ /index.php?$args;. Elle tente de servir le fichier demandé, puis le dossier, et si rien n’existe, passe la main à index.php (en repassant la query string via $args) pour que WordPress route l’URL. Sans elle, les jolis permaliens renvoient des 404.
4. Quel dossier WordPress doit être inscriptible par www-data, et pourquoi éviter chmod 777 partout ?
www-data, et pourquoi éviter chmod 777 partout ?wp-content/uploads (les médias). Le reste du code doit rester en lecture seule pour www-data. chmod 777 sur tout permet à un plugin vérolé ou une faille de réécrire le code et d’installer un webshell : c’est une faille majeure.
5. Cite deux fichiers/endpoints qu’on protège systématiquement dans le server block WordPress.
wp-config.php (contient les identifiants de base — deny all) et xmlrpc.php (vecteur de brute-force et d’amplification, à bloquer si inutilisé). On empêche aussi l’exécution de .php dans wp-content/uploads.
Chapitre suivant : Statique & SPA — le cas le plus simple et le plus robuste : servir un site statique ou une SPA avec Nginx seul.