Pourquoi cette stack ?
Héberger WordPress sur un serveur Linux avec Docker, c’est choisir la liberté, la performance et l’évolutivité. Fini les dépendances système qui se marchent dessus, fini les mises à jour PHP qui cassent votre site, fini les hébergeurs mutualisés qui surchargent vos ressources. Avec Docker, chaque composant de votre infrastructure vit dans son propre conteneur, isolé et configurable à l’infini.
Dans ce guide, nous allons monter une stack complète et production-ready, composée de six conteneur complémentaires :
- WordPress : Le CMS le plus utilisé au monde (43 % du web)
- MariaDB : La base de données, fork performant de MySQL
- Redis : Le cache objet en mémoire pour accélérer WordPress
- Cloudflare Tunnel : L’exposition sécurisée sur internet sans ouvrir de port
- CrowdSec : La protection collaborative contre les attaques
- Duplicati : Les sauvegardes automatiques chiffrées et externalisées
Cette architecture fonctionne sur n’importe quel serveur Linux un VPS à 3000 XOF/mois, un vieux PC recyclé en serveur, ou un NAS Synology. Et dans tous les cas, votre site sera accessible via HTTPS avec un certificat SSL valide, sans avoir besoin d’une IP publique fixe ni d’ouvrir le moindre port dans votre box ou votre pare-feu.
LIRE AUSSI : Mail-Archiver : Archivez Automatiquement Tous Vos Emails en 5 étapes
2. Architecture de la solution
Avant de taper la première commande, prenons un moment pour comprendre comment s’articulent les différents composants. Une bonne architecture se comprend, se maintient et s’améliore. Voici le flux d’une requête HTTP vers votre WordPress :
Cloudflare : joue le rôle de proxy inverse. Il absorbe les pics de trafic, protège contre les attaques DDoS et génère le certificat SSL automatiquement. Votre serveur n’est jamais directement exposé sur internet.
Cloudflare Tunnel : crée un tunnel chiffré sortant (outbound) depuis votre serveur vers les datacenters Cloudflare. Pas besoin d’IP fixe, pas de port ouvert, l’équivalent d’un VPN à sens unique géré par Cloudflare.
CrowdSec : analyse les logs en temps réel et bloque automatiquement les IPs qui adoptent un comportement suspect (brute-force, scan de ports, requêtes WordPress malveillantes). Sa base de données de menaces est partagée entre des millions d’instances dans le monde.
Redis : stocke en mémoire les résultats des requêtes PHP les plus fréquentes. Au lieu d’interroger MariaDB à chaque visite, WordPress récupère la réponse depuis Redis en quelques microsecondes.
3. Prérequis
Avant de commencer, vérifiez que vous disposez des éléments suivants :
- ✅ Un serveur sous Ubuntu 22.04 LTS ou Debian 12 (avec accès root ou sudo)
- ✅ Un nom de domaine actif (ex :
monsite.com), connecté à Cloudflare - ✅ Un compte Cloudflare gratuit avec votre domaine ajouté
- ✅ Accès SSH au serveur
- ✅ Au minimum 1 Go de RAM et 10 Go d’espace disque SSD
Commencez par mettre à jour le système :
sudo apt update && sudo apt upgrade -y
sudo apt install curl wget git nano -yBashÉtape 1: Installer Docker et Docker Compose
Nous allons installer Docker depuis les dépôts officiels de Docker, et non depuis les dépôts Ubuntu qui contiennent souvent une version plus ancienne.
1.1 : Ajouter le dépôt officiel Docker
# Ajouter la clé GPG officielle Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Ajouter le dépôt Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullBash1.2 : Installer Docker Engine et le plugin Compose
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -yBash1.3 : Ajouter votre utilisateur au groupe Docker
Cette étape vous permet d’utiliser Docker sans taper sudo à chaque commande. Reconnectez-vous à
votre session SSH après cette commande pour que le changement soit pris en compte.
sudo usermod -aG docker $USER
# Puis déconnectez-vous et reconnectez-vous en SSHBash1.4 : Vérifier l’installation
docker --version
# Docker version 26.x.x, build ...
docker compose version
# Docker Compose version v2.x.xBashÉtape 2 : Préparer la structure du projet
Une bonne organisation des fichiers est fondamentale. Créez l’arborescence du projet avec ces commandes :
mkdir -p ~/wordpress-stack/{nginx,backups,duplicati_config,crowdsec}
cd ~/wordpress-stackBashVotre arborescence ressemblera à ceci :
~/wordpress-stack/
├── docker-compose.yml # Orchestrateur principal
├── .env # Variables d'environnement (secrets)
├── nginx/
│ └── wordpress.conf # Configuration Nginx
├── backups/ # Sauvegardes locales (Duplicati)
└── duplicati_config/ # Configuration DuplicatiBash2.1 : Créer le fichier des variables d’environnement
Le fichier .env regroupe tous vos secrets. Il ne doit jamais être commité dans Git. Générez des mots de passe robustes avec openssl rand -base64 32.
nano ~/wordpress-stack/.envBash# ── BASE DE DONNÉES ──────────────────────────────────────────
MYSQL_ROOT_PASSWORD=MotDePasseRootSuperSecret2025!
MYSQL_DATABASE=wordpress_db
MYSQL_USER=wp_user
MYSQL_PASSWORD=MotDePasseWPSuperSecret2025!
# ── WORDPRESS ─────────────────────────────────────────────────
WORDPRESS_DB_HOST=mariadb:3306
WORDPRESS_DB_NAME=wordpress_db
WORDPRESS_DB_USER=wp_user
WORDPRESS_DB_PASSWORD=MotDePasseWPSuperSecret2025!
WORDPRESS_TABLE_PREFIX=k9x_
# ── DOMAINE ───────────────────────────────────────────────────
DOMAIN=monsite.comBashÉtape 3 : Le fichier docker-compose.yml
C’est le cœur de votre infrastructure. Ce fichier unique décrit tous les conteneurs, leurs paramètres, leurs dépendances et leurs volumes persistants.
nano ~/wordpress-stack/docker-compose.ymlBashversion: "3.9"
networks:
wp_net:
driver: bridge
volumes:
wordpress_data:
db_data:
redis_data:
crowdsec_db:
crowdsec_config:
services:
# ── MARIADB ──────────────────────────────────────────────────
mariadb:
image: mariadb:11
container_name: wp_mariadb
restart: unless-stopped
env_file: .env
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- wp_net
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 20s
timeout: 5s
retries: 5
command: >
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--innodb-buffer-pool-size=256M
--max-connections=100
# ── REDIS ─────────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: wp_redis
restart: unless-stopped
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru --save ""
volumes:
- redis_data:/data
networks:
- wp_net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
# ── WORDPRESS (PHP-FPM) ────────────────────────────────────────
wordpress:
image: wordpress:6-php8.2-fpm-alpine
container_name: wp_app
restart: unless-stopped
env_file: .env
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: ${WORDPRESS_DB_HOST}
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
define('DISALLOW_FILE_EDIT', true);
define('WP_DEBUG', false);
define('FORCE_SSL_ADMIN', true);
volumes:
- wordpress_data:/var/www/html
networks:
- wp_net
# ── NGINX (reverse proxy) ─────────────────────────────────────
nginx:
image: nginx:alpine
container_name: wp_nginx
restart: unless-stopped
depends_on:
- wordpress
volumes:
- wordpress_data:/var/www/html:ro
- ./nginx/wordpress.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- "8080:80"
networks:
- wp_net
# ── CROWDSEC ──────────────────────────────────────────────────
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: wp_crowdsec
restart: unless-stopped
environment:
GID: "1000"
COLLECTIONS: "crowdsecurity/nginx crowdsecurity/wordpress"
volumes:
- /var/log/nginx:/var/log/nginx:ro
- crowdsec_db:/var/lib/crowdsec/data
- crowdsec_config:/etc/crowdsec
networks:
- wp_net
# ── CROWDSEC BOUNCER ──────────────────────────────────────────
cs-bouncer:
image: fbonalair/traefik-crowdsec-bouncer:latest
container_name: wp_cs_bouncer
restart: unless-stopped
environment:
CROWDSEC_BOUNCER_API_KEY: ${CROWDSEC_BOUNCER_API_KEY:-}
CROWDSEC_AGENT_HOST: crowdsec:8080
GIN_MODE: release
networks:
- wp_net
depends_on:
- crowdsec
# ── DUPLICATI (sauvegardes) ───────────────────────────────────
duplicati:
image: lscr.io/linuxserver/duplicati:latest
container_name: wp_duplicati
restart: unless-stopped
environment:
PUID: 1000
PGID: 1000
TZ: Africa/Lome
volumes:
- ./duplicati_config:/config
- ./backups:/backups
- wordpress_data:/source/wordpress:ro
- db_data:/source/db:ro
ports:
- "127.0.0.1:8200:8200"
networks:
- wp_netYAML3.1 : Configuration Nginx
nano ~/wordpress-stack/nginx/wordpress.confBashlimit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;
server {
listen 80;
server_name _;
root /var/www/html;
index index.php index.html;
client_max_body_size 64M;
# Gestion des permaliens WordPress
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP-FPM via le conteneur WordPress
location ~ \.php$ {
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Protection de wp-login.php (rate limiting)
location = /wp-login.php {
limit_req zone=wp_login burst=3 nodelay;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Bloquer xmlrpc.php (vecteur d'attaque courant)
location = /xmlrpc.php {
return 403;
}
# Bloquer l'accès aux fichiers sensibles
location ~ /\.(ht|git|env) {
deny all;
}
# Cache navigateur longue durée pour les assets statiques
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp|woff2|ttf)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}NginxÉtape 4 : Lancer la stack
Tout est en place. Démarrez l’ensemble des conteneurs :
cd ~/wordpress-stack
# Démarrer tous les services en arrière-plan
docker compose up -d
# Suivre les logs en temps réel (Ctrl+C pour quitter)
docker compose logs -fBashAprès quelques secondes, vérifiez que tous les conteneurs sont démarrés :
docker compose psBashTous les services doivent afficher le statut running ou healthy. Testez l’accès local à
WordPress :
curl -I http://localhost:8080
# HTTP/1.1 200 OK ou 302 Found → WordPress est fonctionnel ✅BashÉtape 5 : Cloudflare Tunnel : exposition sécurisée
Cloudflare Tunnel permet d’exposer votre serveur local sur internet sans ouvrir de port dans votre pare-feu et sans IP publique fixe. Le trafic transite via les serveurs Cloudflare, qui gèrent le SSL, la protection DDoS et le CDN de manière entièrement transparente.
5.1 : Installer cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb \
-o cloudflared.deb
sudo dpkg -i cloudflared.deb
cloudflared --versionBash5.2 : S’authentifier et créer le tunnel
# Authentification (ouvrez le lien dans votre navigateur)
cloudflared tunnel login
# Créer le tunnel (remplacez "wp-monsite" par le nom de votre choix)
cloudflared tunnel create wp-monsiteBashLa commande create génère un UUID et un fichier de credentials JSON dans ~/.cloudflared/.
Notez l’UUID, il est nécessaire à l’étape suivante.
5.3 : Configurer le tunnel
nano ~/.cloudflared/config.ymlBashtunnel: <UUID-DU-TUNNEL>
credentials-file: /root/.cloudflared/<UUID-DU-TUNNEL>.json
ingress:
- hostname: monsite.com
service: http://localhost:8080
- hostname: www.monsite.com
service: http://localhost:8080
- service: http_status:404Bash5.4 : Créer les entrées DNS et activer le service
# Créer les enregistrements CNAME automatiquement dans Cloudflare
cloudflared tunnel route dns wp-monsite monsite.com
cloudflared tunnel route dns wp-monsite www.monsite.com
# Installer le tunnel comme service système (démarrage automatique)
sudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflared
sudo systemctl status cloudflaredBashVotre WordPress est maintenant accessible sur https://monsite.com avec SSL valide — entièrement géré par
Cloudflare, sans Certbot ni renouvellement à planifier.
Étape 6 : CrowdSec : protection collaborative
CrowdSec analyse vos logs en temps réel et bloque automatiquement les IPs malveillantes. Sa force : l’intelligence collective. Lorsqu’une IP attaque une instance CrowdSec quelque part dans le monde, l’information est partagée avec toutes les instances y compris la vôtre.
6.1 : Vérifier et installer les collections
# Entrer dans le conteneur CrowdSec
docker exec -it wp_crowdsec /bin/sh
# Vérifier les collections installées
cscli collections list
# Si absent, les installer manuellement
cscli collections install crowdsecurity/nginx
cscli collections install crowdsecurity/wordpress
cscli hub update
exitBash6.2 : Générer la clé API pour le bouncer
docker exec wp_crowdsec cscli bouncers add wp-nginx-bouncer
# → Copiez la clé API affichée, vous en aurez besoin à l'étape suivanteBashAjoutez la clé dans votre fichier .env :
echo "CROWDSEC_BOUNCER_API_KEY=votre_cle_api" >> ~/wordpress-stack/.env
# Relancer le bouncer avec la nouvelle clé
docker compose up -d --force-recreate cs-bouncerBash6.3 : Surveiller les attaques en temps réel
# Lister les IPs actuellement bannies
docker exec wp_crowdsec cscli decisions list
# Voir les alertes déclenchées
docker exec wp_crowdsec cscli alerts list
# Bannir manuellement une IP suspecte pour 24h
docker exec wp_crowdsec cscli decisions add --ip 1.2.3.4 --reason "Suspect" --duration 24h
# Rejoindre la communauté CrowdSec (intelligence partagée)
docker exec wp_crowdsec cscli console enroll <votre-enrollment-key>
# Obtenez votre clé sur https://app.crowdsec.net (inscription gratuite)BashÉtape 7 : Configurer WordPress
7.1 : Premier accès et installation
Accédez à https://monsite.com dans votre navigateur. L’assistant d’installation WordPress s’affiche.
Renseignez le titre du site, le nom d’utilisateur administrateur, un mot de passe fort et une adresse email. Cliquez
sur Installer WordPress.
7.2 : Activer le cache Redis
Installez le plugin Redis Object Cache depuis Extensions → Ajouter → rechercher « Redis Object Cache ». Activez-le, puis allez dans Réglages → Redis et cliquez sur Activer le cache objet. Vérifiez la connexion :
docker exec wp_app wp redis status --allow-root
# → Status: Connected ✅Bash7.3 : Durcir wp-config.php
La variable WORDPRESS_CONFIG_EXTRA du docker-compose.yml injecte automatique ou manuellement ces lignes dans wp-config.php. Vous pouvez en ajouter d’autres en modifiant cette variable, sans entrer dans le conteneur :
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
define('DISALLOW_FILE_EDIT', true);
define('FORCE_SSL_ADMIN', true);
define('WP_POST_REVISIONS', 5);
define('EMPTY_TRASH_DAYS', 30);
define('WP_DEBUG', false);PHPAprès modification du docker-compose.yml, appliquez les changements :
docker compose up -d --force-recreate wordpressBashÉtape 8 : Sauvegardes avec Duplicati
Même la meilleure infrastructure peut subir une défaillance. Duplicati est une solution open source qui effectue des sauvegardes chiffrées AES-256, compressées et externalisées vers de nombreuses destinations : Google Drive, Backblaze B2, Amazon S3, Synology NAS, SFTP, OneDrive…
8.1 : Accéder à l’interface Duplicati
Le conteneur Duplicati expose son interface sur le port 8200, uniquement accessible en local (lié à 127.0.0.1). Créez un tunnel SSH depuis votre machine pour y accéder :
# Sur votre machine locale (pas le serveur)
ssh -L 8200:localhost:8200 user@IP-DU-SERVEURBashOuvrez ensuite http://localhost:8200 dans votre navigateur.
8.2 : Configurer une tâche de sauvegarde
Cliquez sur Ajouter une sauvegarde et configurez les paramètres suivants :
- Nom : Sauvegarde WordPress Production
- Chiffrement : AES-256 — définissez une phrase secrète et conservez-la précieusement
- Destination : Backblaze B2 (10 Go gratuits), Google Drive, SFTP ou votre NAS Synology
- Sources :
/source/wordpress(fichiers WP) et/source/db(données
MariaDB) - Planification : Quotidien à 2h00 du matin
- Rétention : 30 sauvegardes (environ 1 mois d’historique)
8.3 : Sauvegarde manuelle de la base de données
Pour une sauvegarde ponctuelle avant une mise à jour majeure :
docker exec wp_mariadb mysqldump \
-u wp_user -p'MotDePasseWPSuperSecret2025!' wordpress_db \
| gzip > ~/wordpress-stack/backups/db-$(date +%Y%m%d_%H%M).sql.gz
# Vérifier le fichier créé
ls -lh ~/wordpress-stack/backups/BashCommandes utiles au quotidien
Gestion des conteneurs
# Démarrer / arrêter tous les conteneurs
docker compose up -d
docker compose down
# Redémarrer un service spécifique
docker compose restart wordpress
# Voir l'état de tous les conteneurs
docker compose ps
# Logs en temps réel d'un service
docker compose logs -f wordpressBashMises à jour
# Récupérer les nouvelles versions des images
docker compose pull
# Relancer avec les nouvelles images (sans interruption prolongée)
docker compose up -d --remove-orphans
# Nettoyer les anciennes images
docker image prune -fBashSurveillance des ressources
# Consommation CPU et RAM en temps réel
docker stats
# Espace disque utilisé par les volumes
docker system df -v
# Test Redis
docker exec wp_redis redis-cli ping
# → PONG ✅BashPeut-on héberger plusieurs sites WordPress avec cette stack ?
Oui. Ajoutez simplement des règles supplémentaires dans la configuration Cloudflare Tunnel, et créez une base de données supplémentaire dans MariaDB pour chaque site. Pour une gestion multi-sites avancée, considérez Nginx Proxy Manager comme reverse proxy central à la place du conteneur Nginx seul.
Cloudflare Tunnel est-il gratuit ?
Oui, la fonctionnalité Tunnel est disponible dans le plan Free de Cloudflare. Ce plan inclut la protection DDoS, le CDN mondial et le SSL automatique tout ce dont vous avez besoin pour un usage personnel ou professionnel standard.
CrowdSec vs Fail2Ban : quelle différence ?
Fail2Ban analyse uniquement vos logs locaux et ne partage aucune information. CrowdSec fait la même analyse, mais partage et reçoit des informations de menaces avec une communauté mondiale. Résultat : votre serveur bénéficie d’une liste de blocage enrichie en permanence, sans effort supplémentaire de votre part.
Vous venez de mettre en place une infrastructure WordPress professionnelle sur Linux, entièrement conteneurisée avec Docker. Cette stack vous offre ce qu’aucun hébergeur mutualisé ne peut proposer à ce tarif : une maîtrise totale de votre environnement, des performances optimales grâce à Redis, une sécurité proactive avec CrowdSec, une exposition sécurisée via Cloudflare Tunnel, et des sauvegardes chiffrées automatiques avec Duplicati. Pour aller encore plus loin, explorez Nginx Proxy Manager pour héberger plusieurs sites sur le même serveur avec une interface visuelle. Et si vous souhaitez automatiser vos tâches WordPress, découvrez notre guide sur n8n + WordPress pour gagner 10h par semaine.
Des questions ou des blocages ? Laissez un commentaire ci-dessous. Bonne installation !
