WordPressをルート直下からサブディレクトリ(例:/blog/)へ移動したくなること、ありませんか。
トップページは静的なポートフォリオサイトにして、ブログだけWordPressで運用したい。そんな構成にしたくて、今回自分のブログを丸ごとディレクトリ移動してみました。
正直、WordPressのディレクトリ移動は「ちょっとmvするだけでしょ?」と軽く見ていたんですが、実際にやってみるとwp-config.phpの中に潜んでいた罠に足を取られかけました。この記事では、実際につまずいたポイントも含めて、WordPressのディレクトリ移動手順を解説します。
この記事で解決できること
- WordPress ディレクトリ 移動の具体的な手順
wp-config.phpに潜む「見落としがちな罠」- 画像やリンクURLを一括で書き換える
search-replaceの使い方 - 移動後によくある404・画像切れの原因と直し方
そもそもなぜディレクトリを移動するのか
自分の場合、もともとWordPressがドメイン直下(https://example.com/)にインストールされていて、静的なポートフォリオページと共存させていました。.htaccessのDirectoryIndexで静的HTMLを優先表示させる、いわゆる力技の共存パターンです。
これはこれで動いてはいたのですが、
- WordPressの
home/siteurlが実質使われない中途半端な状態 - 将来的に静的サイト側を拡張しづらい
- URL設計として「ブログは
/blog/配下」の方が管理しやすい
という理由から、WordPress本体を/blog/ディレクトリへ物理的に移動することにしました。WordPress公式でも「静的トップページ+サブディレクトリにWordPress」は正式にドキュメント化されているパターンなので、変則的なAliasやプロキシで無理やり実現するより、素直にファイルを移動する方が長期的にメンテナンスしやすいと判断しました。
移行前に必ずやること:バックアップ
本番環境をいじる前に、DBとファイルのバックアップは絶対に取ってください。WP-CLIが使えるならこれだけで完了します。
# DBのバックアップ
wp db export ~/backup_db_$(date +%Y%m%d).sql
# ファイル一式のバックアップ
tar czf ~/backup_wpfiles_$(date +%Y%m%d).tar.gz wp-admin wp-includes wp-content wp-*.php index.php xmlrpc.php
サーバーによってはWP-CLIが標準で入っていないこともあります。その場合はwp-cli.pharを直接落としてくれば動きます。
curl -sSL -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
さくらやヘテムルのような共用サーバーだと、PHPのバイナリがそもそもPATHに入っていないこともあるので、which phpで見つからなければ/usr/local/php/8.2/bin/phpのようなバージョン別ディレクトリを探してみてください。
事前にリハーサルしておくと安心
本番でいきなり作業するのが不安な人は、ローカルのDocker環境などに本番のDB・ファイルを一度インポートして、同じ手順を試しておくのがおすすめです。
自分は事前にDocker上のWordPress環境に本番DBとプラグイン一式をコピーして、同じsearch-replaceコマンドをリハーサルしました。おかげで置換件数の見込み(自分の場合は約2,910件)が事前に把握できて、本番作業中に「あれ、想定と違う件数が出た」と焦らずに済みました。
ただし、後述するwp-config.phpの罠だけは、DBダンプに含まれないためリハーサルでは再現できませんでした。ここは実際に手を動かして初めて気づいたポイントです。
手順1:メンテナンスモードを有効化
作業中にユーザーに壊れた画面を見せないよう、メンテナンスモードをオンにします。
wp maintenance-mode activate
手順2:ディレクトリを作成してファイルを移動
/blog/ディレクトリを作成し、WordPress関連ファイルだけをそこへ移動します。静的サイトのファイル(index.html、css/、js/、images/など)はルートに残すのがポイントです。
mkdir blog
mv wp-admin wp-includes wp-content \
wp-activate.php wp-blog-header.php wp-comments-post.php \
wp-config-sample.php wp-config.php wp-cron.php \
wp-links-opml.php wp-load.php wp-login.php wp-mail.php \
wp-settings.php wp-signup.php wp-trackback.php \
xmlrpc.php index.php \
blog/
.htaccessは移動しません。ルートに残したまま、中身だけ後で整理します。
手順3:wp-content内のURLを一括置換する
投稿本文や画像URLには/wp-content/uploads/...という形でパスが埋め込まれています。ディレクトリを移動すると、このパスの前に/blog/を足してあげないと画像が表示されなくなります。ここでwp search-replaceの出番です。
# まずはドライランで影響範囲を確認
wp search-replace 'example.com/wp-content' 'example.com/blog/wp-content' \
--all-tables-with-prefix --dry-run --report-changed-only
# 問題なければ本実行
wp search-replace 'example.com/wp-content' 'example.com/blog/wp-content' \
--all-tables-with-prefix --report-changed-only
自分の環境だと、wp_postsの本文はもちろん、Yoast SEOのOGP画像・Twitterカード画像を保存しているテーブル(wp_yoast_indexable)まで対象になりました。プラグインを入れていると、想像以上にいろんなテーブルにURLが散らばっているので、--all-tables-with-prefixをつけて漏れなく対象にするのがコツです。
ここで安易にexample.comをまるごとexample.com/blogに置換すると、本文中の/blog/と無関係なリンク(お問い合わせページや他ツールへのリンクなど)まで巻き込んでしまうので、wp-contentという文字列まで含めて狙い撃ちするのがポイントです。
手順4:本当の罠は wp-config.php に潜んでいた
ここまでやって、さあ確認だ、とサイトを開いてみたところ、<link rel="https://api.w.org/">のURLに/blog/が付いていませんでした。
DBのsiteurl・homeオプションはたしかに正しく更新されているのに、なぜか出力されるURLだけ古いまま。原因を探ってみるとwp-config.phpにこんな記述が残っていました。
define('WP_HOME', 'https://example.com');
define('WP_SITEURL', 'https://example.com');
WP_HOMEとWP_SITEURLの定数は、DBに保存されているhome・siteurlオプションよりも優先して使われます。過去に別の事情でこの定数を直書きしていたことをすっかり忘れていて、DBだけ更新して満足していました。これに気づかないままだと、REST APIやサイトマップのURLがいつまでも古いパスのままになるところでした。
define('WP_HOME', 'https://example.com/blog');
define('WP_SITEURL', 'https://example.com/blog');
このように書き換えて、ようやく正しいURLが出力されるようになりました。もし同じようにキャッシュ系プラグイン(自分の場合はWP Super Cache)を使っている場合、WPCACHEHOMEのような定数にも旧パスがハードコードされていることがあるので、あわせてチェックしておくと安心です。
手順5:.htaccessを整理する
ルートの.htaccessから、WordPress標準のリライトブロック(# BEGIN WordPress〜# END WordPress)を削除します。このブロックはもう/blog/側に必要なので、ルートには残しません。
sed -i '/# BEGIN WordPress/,/# END WordPress/d' .htaccess
そして/blog/.htaccessを新規作成します。RewriteBaseと書き換え先のパスを/blog/基準にするのを忘れないでください。
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
</IfModule>
# END WordPress
wp rewrite flush --hardで自動生成させようとしたのですが、共用サーバーの権限の都合で「特殊な設定が必要」という警告が出て自動生成されなかったので、手動で作成しました。サーバー環境によってはこの手順が必要になることを覚えておいてください。
手順6:メンテナンスモード解除と最終確認
wp maintenance-mode deactivate
解除したら、最低限以下は必ずチェックしましょう。
- トップページ・個別記事がエラーなく表示されるか
- 記事内の画像がちゃんと表示されるか
- 管理画面にログインできるか(
/blog/wp-admin/) - サイトマップが正しいURLで生成されているか
<link rel="canonical">が新しいURLになっているか
自分はこれに加えてcurlでHTTPステータスとレスポンスヘッダーを1件ずつ確認しながら進めました。ブラウザで見た目だけ確認するより、レスポンスヘッダーのURLまでチェックしておくと、今回のような「見た目は正常だけどメタ情報だけ古い」というトラブルに気づきやすくなります。
まとめ
WordPressのディレクトリ移動は、ファイルをmvしてDBのURLをsearch-replaceすれば終わり、と思いがちですが、実際にはwp-config.phpにハードコードされた定数や、キャッシュ系プラグインが生成する補助ファイルなど、DBの外側に潜む「隠れたURL」がいくつも存在します。
今回自分がハマったのは以下の3か所でした。
wp-config.phpのWP_HOME・WP_SITEURL定数- キャッシュプラグインの
WPCACHEHOME定数 - キャッシュプラグインが生成する404ハンドラファイル内の絶対パス
DBのsearch-replaceだけで満足せず、wp-config.phpとキャッシュ系プラグインの生成ファイルは必ずgrepで旧パスが残っていないか確認することをおすすめします。
grep -rn "example.com/wp-content\|/old-path/" wp-config.php wp-content/*.php
このひと手間があるかないかで、移行後のトラブルシューティングにかかる時間がまったく変わってきます。同じようにWordPressのディレクトリ移動を検討している方の参考になれば幸いです。