Image du titre

Migrer un WordPress de Dev → Prod avec WP-CLI

Migrer un WordPress de Dev → Prod avec WP-CLI

Migration WordPress dev → prod en 7 étapes, sans plugin, sans sed bricolé qui casse la base sérialisée. WP-CLI fait le job proprement, et le script bash sera réutilisable en bonus afin de t’évite de réécrire la procédure à chaque projet

Pourquoi WP-CLI plutôt qu’un plugin de migration ? WP-CLI fait le même boulot, en mieux, pour trois raisons concrètes.

D’abord, c’est reproductible. Vous écrivez votre procédure une fois, vous la rejouez à l’identique sur le prochain projet. Vous pouvez même la scripter (on en parle à la fin).

Ensuite, c’est scriptable et CI-friendly. Pas d’interface graphique qui timeout au bout de 60 secondes parce que votre hébergeur mutualisé fait la sieste.

Enfin, et c’est le point qui change tout, WP-CLI gère correctement la sérialisation PHP dans le search-replace. Si ce mot ne vous dit rien, retenez juste ça : WordPress stocke plein de données sous forme de tableaux PHP sérialisés en base. Un sed brutal ou un mauvais plugin va casser ces structures, et vous vous retrouvez avec des widgets vides, des options foireuses, des thèmes qui plantent. WP-CLI désérialise, remplace, re-sérialise. Proprement.

Prérequis

Avant de commencer, vérifiez que vous avez :

  • Accès SSH sur les deux serveurs (dev et prod)
  • WP-CLI installé des deux côtés. Test rapide : wp --info doit sortir la version, le chemin PHP, etc.
  • Des versions PHP et MySQL/MariaDB proches (idéalement identiques) entre dev et prod
  • Un café

Pour le scénario, on part sur :

  • Source : dev.exemple.fr (serveur de dev/test)
  • Cible : www.exemple.fr (production)

Étape 1 : Préparer le site source

On commence par nettoyer la source. Un site de dev a souvent accumulé du cache, des transients, des trucs inutiles qui vont juste alourdir le dump et potentiellement créer des bugs en prod.

cd /var/www/dev.exemple.fr
wp cache flush
wp transient delete --all
wp rewrite flush

Si vous avez un plugin de cache type WP Rocket, W3 Total Cache ou LiteSpeed Cache, désactivez-le avant l’export. Ces plugins stockent des chemins absolus serveur qui n’auront aucun sens en prod.

wp plugin deactivate wp-rocket w3-total-cache litespeed-cache

Vous pourrez les réactiver côté prod après vérif.

Étape 2 : Exporter la base

WP-CLI a une commande dédiée, qui gère mieux l’encodage que mysqldump brut :

wp db export dump-$(date +%Y%m%d-%H%M).sql \
  --add-drop-table \
  --default-character-set=utf8mb4

L’option --add-drop-table est importante : elle ajoute des DROP TABLE IF EXISTS au dump. Si vous réimportez par-dessus une base existante côté prod, vous partez sur du propre.

Le utf8mb4 évite les surprises avec les emojis et caractères spéciaux (utile pour du contenu francophone avec apostrophes typographiques, et indispensable si votre site a des emojis dans les titres).

Pour wp-config.php : ne le copiez pas. Vous allez le recréer côté prod avec les bons identifiants de base. Si vous le copiez, vous allez écraser la config prod et perdre 20 minutes à comprendre pourquoi le site se connecte à la base de dev.

Étape 3 : Transférer les fichiers

rsync est votre ami. Il transfère uniquement les différences, gère les permissions, et vous pouvez exclure ce qui ne sert à rien.

Depuis le serveur de dev :

rsync -avz --progress \
  --exclude 'wp-content/cache' \
  --exclude 'wp-content/uploads/cache' \
  --exclude 'wp-content/debug.log' \
  --exclude '.git' \
  --exclude 'wp-config.php' \
  /var/www/dev.exemple.fr/ \
  user@prod-server:/var/www/www.exemple.fr/

Notez bien l’exclusion de wp-config.php. On ne le veut pas, pour la raison vue plus haut.

Côté prod, vérifiez les permissions :

sudo chown -R www-data:www-data /var/www/www.exemple.fr
sudo find /var/www/www.exemple.fr -type d -exec chmod 755 {} \;
sudo find /var/www/www.exemple.fr -type f -exec chmod 644 {} \;

Adaptez www-data à votre utilisateur web (sur certains setups c’est apache, nginx ou un user dédié type Virtualmin).

Étape 4 : Importer la base en prod

Connectez-vous en SSH sur la prod et placez-vous dans le répertoire du site. À ce stade vous avez déjà recréé wp-config.php avec les identifiants de la base de prod (sinon, faites-le maintenant en partant de wp-config-sample.php).

Créez la base si elle n’existe pas :

wp db create

Puis importez le dump (que vous avez transféré via scp ou rsync) :

wp db import dump-20260511-1430.sql

Petit check de cohérence :

wp db check

À cet instant, votre site de prod contient toutes les données de dev. Si vous visitez www.exemple.fr, vous allez probablement être redirigé vers dev.exemple.fr ou voir des choses cassées. C’est normal. On passe à l’étape suivante.

Étape 5 : Search-replace, le cœur du sujet

C’est ici que les gens se plantent, et c’est ici que WP-CLI fait toute la différence.

La commande de base

wp search-replace 'dev.exemple.fr' 'www.exemple.fr' \
  --all-tables \
  --report-changed-only

Trois flags importants :

  • --all-tables : balaye toutes les tables, pas seulement les tables WP standard. Utile si vous avez des plugins qui créent leurs propres tables (WooCommerce, formulaires, etc.)
  • --report-changed-only : n’affiche que les tables où il y a eu un changement, sortie plus lisible
  • (À ajouter) --dry-run la première fois, toujours

Le réflexe dry-run

Toujours, toujours, toujours lancer un dry-run avant le vrai search-replace :

wp search-replace 'dev.exemple.fr' 'www.exemple.fr' \
  --all-tables \
  --dry-run

Vous voyez ce qui va être modifié, dans quelles tables, combien de lignes. Si le nombre vous paraît délirant (genre 50 000 changements sur une table où vous n’attendez rien), vous savez que quelque chose cloche avant de toucher quoi que ce soit.

Les variantes d’URLs à traiter

Une URL peut apparaître sous plusieurs formes dans la base. Vous allez devoir faire plusieurs passes :

# 1. URL avec https
wp search-replace 'https://dev.exemple.fr' 'https://www.exemple.fr' --all-tables

# 2. URL avec http (au cas où)
wp search-replace 'http://dev.exemple.fr' 'https://www.exemple.fr' --all-tables

# 3. URL sans protocole (référence relative)
wp search-replace '//dev.exemple.fr' '//www.exemple.fr' --all-tables

# 4. Domaine seul (rare mais arrive dans certaines configs)
wp search-replace 'dev.exemple.fr' 'www.exemple.fr' --all-tables

L’ordre compte. Commencez par les variantes les plus spécifiques (avec protocole) avant les plus génériques. Sinon vous allez remplacer deux fois certaines occurrences.

Le piège des chemins de fichiers absolus

Si votre path serveur change aussi (par exemple /var/www/dev/ vers /var/www/prod/), il faut aussi traiter ça :

wp search-replace '/var/www/dev.exemple.fr' '/var/www/www.exemple.fr' --all-tables

Ça concerne typiquement les options de plugins qui stockent des chemins absolus (BackWPup, certains plugins de cache, etc.).

L’option –precise pour les cas tordus

Par défaut, WP-CLI utilise PHP pour gérer la sérialisation. Sur certaines structures complexes (Elementor, ACF avec champs imbriqués, blocs Gutenberg avec JSON encodé), vous pouvez avoir besoin de l’option --precise :

wp search-replace 'dev.exemple.fr' 'www.exemple.fr' \
  --all-tables \
  --precise

C’est plus lent mais plus fiable sur les structures imbriquées. Si vous voyez des bugs sur des templates Elementor après migration, refaites une passe avec --precise.

Le débat des GUIDs

Vous allez peut-être tomber sur des tutos qui disent --skip-columns=guid. Le GUID (Globally Unique Identifier) est l’URL « permanente » de chaque post, stockée dans wp_posts.guid. La doc WordPress recommande de ne jamais le changer une fois qu’un post est publié, parce que certains lecteurs RSS s’en servent comme identifiant.

Sauf que sur un site de dev qui n’a jamais été public, personne ne s’est abonné au flux RSS. Donc le changer est sans conséquence. Notre recommandation : sur une migration dev → prod initiale, laissez WP-CLI changer les GUIDs (donc pas de --skip-columns). Sur une migration prod → prod (changement de domaine d’un site live), là oui, --skip-columns=guid.

Étape 6 : Reconfigurer la prod

wp-config.php

Vérifiez que vous avez bien :

  • Les bons identifiants de base
  • De nouvelles AUTH_KEY, SECURE_AUTH_KEY, etc. (générez-les sur https://api.wordpress.org/secret-key/1.1/salt/)
  • WP_DEBUG à false
  • WP_DEBUG_LOG à false ou un chemin custom

Si vous voulez forcer les URLs (pratique en prod, ça évite qu’un admin distrait change siteurl depuis l’admin) :

define('WP_HOME', 'https://www.exemple.fr');
define('WP_SITEURL', 'https://www.exemple.fr');

Vérif et flush

wp option get siteurl
wp option get home

Si quelque chose pointe encore vers dev, faites une passe de search-replace supplémentaire.

Puis on flush les rewrite rules :

wp rewrite flush --hard

Le --hard régénère aussi le .htaccess (utile sur Apache, ignoré sur Nginx).

Étape 7 : Checks post-migration

Voici la checklist de vérification rapide, à passer dans l’ordre :

# 1. URLs principales
wp option get siteurl
wp option get home

# 2. Aucune trace résiduelle du domaine de dev
wp search-replace 'dev.exemple.fr' 'www.exemple.fr' --all-tables --dry-run
# Doit retourner "Success: 0 replacements to be made."

# 3. Comptes utilisateurs intacts
wp user list

# 4. Plugins actifs cohérents
wp plugin list --status=active

# 5. Aucune erreur PHP au chargement
wp eval 'echo "OK\n";'

Côté navigateur, testez :

  • La home
  • Une page intérieure (vérifiez les permaliens)
  • Un article (vérifiez les médias, les liens internes)
  • Le back-office (/wp-admin)
  • Les formulaires si vous en avez
  • Le mixed content (ouvrez la console développeur, traquez les warnings sur HTTPS)

Bonus : Le script tout-en-un

Pour les migrations récurrentes, voici un script bash réutilisable. Vous mettez vos variables en haut, vous lancez, ça enchaîne tout.

#!/bin/bash
# migrate-wp.sh - Migration WordPress dev vers prod via WP-CLI

set -e  # Stop sur erreur

# === Variables à adapter ===
SOURCE_PATH="/var/www/dev.exemple.fr"
SOURCE_URL="dev.exemple.fr"
TARGET_PATH="/var/www/www.exemple.fr"
TARGET_URL="www.exemple.fr"
TARGET_HOST="prod-server"
TARGET_USER="deploy"
DUMP_NAME="dump-$(date +%Y%m%d-%H%M).sql"

# === 1. Préparation source ===
echo "→ Préparation du site source"
cd "$SOURCE_PATH"
wp cache flush
wp transient delete --all
wp rewrite flush

# === 2. Export base ===
echo "→ Export de la base"
wp db export "/tmp/$DUMP_NAME" --add-drop-table --default-character-set=utf8mb4

# === 3. Transfert ===
echo "→ Transfert fichiers via rsync"
rsync -avz --progress \
  --exclude 'wp-content/cache' \
  --exclude 'wp-content/uploads/cache' \
  --exclude 'wp-config.php' \
  --exclude '.git' \
  "$SOURCE_PATH/" \
  "$TARGET_USER@$TARGET_HOST:$TARGET_PATH/"

echo "→ Transfert du dump SQL"
scp "/tmp/$DUMP_NAME" "$TARGET_USER@$TARGET_HOST:/tmp/"

# === 4. Import et search-replace sur la prod ===
echo "→ Import et search-replace côté prod"
ssh "$TARGET_USER@$TARGET_HOST" << EOF
  cd $TARGET_PATH
  wp db import /tmp/$DUMP_NAME
  wp search-replace 'https://$SOURCE_URL' 'https://$TARGET_URL' --all-tables
  wp search-replace 'http://$SOURCE_URL' 'https://$TARGET_URL' --all-tables
  wp search-replace '//$SOURCE_URL' '//$TARGET_URL' --all-tables
  wp search-replace '$SOURCE_URL' '$TARGET_URL' --all-tables
  wp cache flush
  wp rewrite flush --hard
  echo "✓ Migration terminée"
EOF

À adapter selon votre infrastructure (clés SSH, chemins, etc.). Sauvegardez-le dans vos scripts d’admin, ça vous fera gagner un temps fou sur les prochains projets.

Les pièges classiques (FAQ)

« Le site affiche encore l’ancien domaine sur certaines pages »

Refaites une passe de search-replace avec --precise. Si ça persiste, le problème est souvent dans postmeta (champs ACF, Elementor) ou dans les options de plugins SEO type Yoast/RankMath qui stockent des URLs canoniques.

« Error establishing a database connection » après import

Identifiants de base incorrects dans wp-config.php, ou utilisateur MySQL sans les droits sur la base de prod. Vérifiez avec wp db check.

« Mes médias ne s’affichent pas »

Vérifiez les permissions de wp-content/uploads/. Doit être en www-data:www-data (ou équivalent) et 755 sur les dossiers, 644 sur les fichiers.

« Le cron WP tourne encore sur l’ancien domaine »

wp cron event list pour voir ce qui est planifié. Si nécessaire, wp cron event delete <hook> puis laissez WP recréer les tâches.

Pour aller plus loin

WP-CLI a une doc excellente : https://wp-cli.org/. Les commandes wp db, wp search-replace, wp option et wp cron couvrent 90% des besoins en migration.

Si vous faites beaucoup de migrations, regardez aussi du côté de WP Migrate DB Pro (payant, intègre du push/pull entre environnements) ou de WP-CLI Packages pour étendre les commandes natives.

Et vous, vous avez une astuce qui vous aurait évité de réécrire vos scripts 12 fois ? Mettez-la en commentaire.

Pas de commentaire

Publier un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.