RETOUR D’EXPÉRIENCE /

Pourquoi WP-CLI ignorait mod_rewrite alors qu’Apache l’avait bien activé

Les problèmes les plus intéressants en environnement conteneurisé apparaissent souvent lorsque chaque composant fonctionne correctement pris isolément, mais que l’ensemble ne fonctionne pas.

C’est exactement ce qui m’est arrivé avec une instance WordPress déployée avec Docker Compose.

Apache chargeait correctement mod_rewrite. La configuration autorisait les overrides. WordPress connaissait ses règles de réécriture. Pourtant, les permaliens continuaient à retourner des erreurs 404 et la commande :

wp rewrite flush --hard

ne régénérait pas le fichier .htaccess comme attendu.

Le problème ne venait finalement ni des règles WordPress, ni directement d’Apache.

Il se trouvait à la frontière entre Apache, WordPress et un conteneur WP-CLI séparé.

Le symptôme : tout semble correct, mais les URLs retournent 404

L’objectif était classique : utiliser des URLs WordPress lisibles plutôt que des URLs basées sur des paramètres.

Par exemple :

/blog/mon-article/

Mais après activation des permaliens, les URLs propres retournaient des erreurs 404.

La première hypothèse naturelle est un problème avec Apache et son module de réécriture.

Première vérification :

apache2ctl -M

rewrite_module était bien chargé.

Il fallait ensuite vérifier que la configuration Apache autorisait WordPress à utiliser son .htaccess.

Là encore, la configuration était correcte :

AllowOverride All

À ce stade, Apache semblait donc correctement configuré.

WordPress connaissait également ses règles

Deuxième piste : WordPress avait-il réellement généré ses règles de réécriture ?

WP-CLI permet de les inspecter :

wp rewrite list

Les règles étaient présentes.

On avait donc une situation assez déroutante :

Apache
 └── mod_rewrite actif ✓

Configuration Apache
 └── AllowOverride All ✓

WordPress
 └── rewrite rules présentes ✓

.htaccess
 └── règles attendues ✗

URLs propres
 └── HTTP 404 ✗

C’est précisément ce genre de situation où vérifier encore et encore la configuration Apache ne fait plus beaucoup avancer le diagnostic.

Une architecture Docker légèrement différente

L’élément important était l’architecture utilisée.

WordPress et WP-CLI ne s’exécutaient pas dans le même conteneur.

Schématiquement :

                   Docker Compose

                         │

            ┌────────────┴────────────┐

            │                         │

            ▼                         ▼

      WordPress/Apache              WP-CLI

            │                         │

            │                         │

            └────── volume partagé ───┘

                         │

                         ▼

                    /var/www/html

Le conteneur WordPress exécutait Apache.

Le conteneur WP-CLI permettait d’effectuer les opérations d’administration :

wp option ...
wp post ...
wp rewrite ...

Les deux services partageaient les fichiers WordPress, mais ils ne partageaient évidemment pas le même processus Apache.

Cette distinction allait devenir essentielle.

Le message de WP-CLI donnait finalement la piste

L’exécution de :

wp rewrite flush --hard

indiquait qu’une configuration supplémentaire était nécessaire pour effectuer le flush complet.

C’est là qu’une subtilité de WP-CLI entre en jeu.

Le fait que :

Apache → mod_rewrite actif

ne signifie pas automatiquement :

WP-CLI → sait que mod_rewrite est actif

Dans notre architecture, WP-CLI s’exécutait depuis un autre conteneur.

Il ne pouvait donc pas simplement déduire toutes les capacités du serveur Apache exécuté ailleurs.

C’était le point qui manquait au diagnostic.

Déclarer explicitement mod_rewrite à WP-CLI

La solution consiste à fournir une configuration dédiée à WP-CLI.

Une configuration minimale peut ressembler à ceci :

apache_modules:
  - mod_rewrite

Cette configuration est ensuite montée dans le conteneur WP-CLI.

Exemple générique avec Docker Compose :

services:
  wordpress:
    image: wordpress:php8.2-apache
    volumes:
      - wordpress_data:/var/www/html

  wordpress-cli:
    image: wordpress:cli
    volumes:
      - wordpress_data:/var/www/html
      - ./wp-cli.yml:/wp-cli.yml:ro
    environment:
      WP_CLI_CONFIG_PATH: /wp-cli.yml

Le :ro est volontaire.

Le conteneur CLI a besoin de lire sa configuration, pas de la modifier.

Pourquoi utiliser WP_CLI_CONFIG_PATH ?

Il serait possible de dépendre d’un emplacement implicite de configuration.

Mais dans une infrastructure automatisée, je préfère rendre ce contrat explicite :

WP_CLI_CONFIG_PATH: /wp-cli.yml

On sait alors exactement :

configuration versionnée
 ↓
 montage Docker read-only
 ↓
 WP_CLI_CONFIG_PATH
 ↓
 WP-CLI

Cette approche présente également un avantage pour l’Infrastructure as Code : le comportement du CLI ne dépend plus d’une configuration présente manuellement dans le conteneur.

Elle devient déclarative et reproductible.

Validation

Après avoir fourni cette configuration au service WP-CLI, il restait à effectuer le flush :

wp rewrite flush --hard

Puis à contrôler le fichier généré :

cat .htaccess

et enfin tester une URL utilisant les permaliens.

La chaîne devenait alors :

WordPress
 │
 │ rewrite rules
 ▼
 WP-CLI
 │
 │ connaît mod_rewrite
 ▼
 .htaccess
 │
 │ RewriteRule
 ▼
 Apache
 │
 ▼
 Pretty URL
 │
 ▼
 HTTP 200

Le problème était résolu.

Ce que ce problème m’a appris

Le point intéressant n’est finalement pas la ligne :

apache_modules:
  - mod_rewrite

Elle est triviale.

Ce qui est intéressant est le raisonnement qui permet d’arriver jusque-là.

Dans une architecture traditionnelle, WordPress, PHP, Apache et les outils d’administration peuvent vivre dans un environnement relativement proche.

Avec Docker, nous créons volontairement des frontières.

Dans notre cas :

Apache sait quelque chose
 ≠
 WP-CLI sait quelque chose

Même lorsqu’ils manipulent les mêmes fichiers.

C’est un principe que l’on retrouve dans beaucoup d’architectures conteneurisées : un volume partagé partage des données, pas le contexte d’exécution des processus.

Une deuxième frontière à surveiller : les permissions

Il existe d’ailleurs une autre difficulté dans ce type d’architecture.

Même après avoir correctement informé WP-CLI de la présence de mod_rewrite, le conteneur CLI doit encore être capable d’écrire dans le stockage partagé.

Il faut donc également vérifier les identités utilisées par les différents conteneurs :

id
ls -ln .htaccess

Un conteneur peut parfaitement connaître les bonnes règles et malgré tout être incapable de les écrire si son UID/GID ne correspond pas aux permissions du volume.

Je traiterai ce problème séparément, car il mérite son propre retour d’expérience.

C’est une bonne illustration du diagnostic par couches :

Application
 ↓
 Configuration WP-CLI
 ↓
 Filesystem / permissions
 ↓
 Configuration Apache
 ↓
 Serveur HTTP

Valider une couche ne valide pas automatiquement les autres.

Le pattern que j’en retiens

Pour un service CLI séparé du runtime applicatif, je privilégie désormais trois principes :

  1. Configuration explicite
    Les capacités dont dépend l’outil doivent être déclarées lorsque leur découverte automatique n’est pas fiable.
  2. Configuration versionnée
    Un fichier comme wp-cli.yml fait partie de l’infrastructure et doit pouvoir être reproduit.
  3. Configuration montée en lecture seule
    ./wp-cli.yml:/wp-cli.yml:ro
    Le runtime consomme la configuration ; il ne doit pas devenir sa source de vérité.

Cela rend le comportement plus prévisible et facilite également les déploiements automatisés avec Ansible.

Conclusion

Ce 404 WordPress ressemblait initialement à un problème classique de permaliens.

Pourtant :

  • mod_rewrite était actif ;
  • AllowOverride était correct ;
  • WordPress connaissait ses règles ;
  • le problème persistait.

La cause se situait ailleurs : WP-CLI s’exécutait dans un environnement isolé et ne connaissait pas automatiquement les capacités du serveur Apache.

Déclarer explicitement mod_rewrite dans sa configuration a permis de rétablir cette information et de rendre le flush complet reproductible.

C’est aussi un bon rappel d’un principe plus général de Platform Engineering :

Lorsque l’on sépare les composants, il faut également rendre explicites les contrats qui existaient auparavant implicitement entre eux.