2026年6月24日
2026年6月24日
WordPressの大量404エラーを一括で直す方法|パーマリンク修復と301リダイレクト一括設定
WordPressで404が大量に出たときは、「全ページが404になっている」のか「特定の古いURLだけが404になっている」のかで直し方が変わります。前者はURLの書き換えルールの問題で、数分で直ることが多く、後者は旧URLと新URLの対応表を作って301リダイレクトを一括設定します。この記事では、両方のケースの見分け方と、数百件規模の404をまとめて処理し、結果を検証するところまでを解説します。
まず404の種類を見分ける
確認するのは次の2点です。
- トップページは表示されるのに、個別の記事・固定ページ・カテゴリーがすべて404になる
- 多くのページは正常で、検索エンジン経由や外部リンクからの古いURLだけが404になる
前者は、WordPressへリクエストを渡す仕組み(.htaccess やNginxの設定)が壊れている状態です。サイト移転、サーバーの設定変更、パーマリンク設定の変更の直後によく起きます。後者は、スラッグの変更、パーマリンク構造の変更、記事の削除や統合、ドメインやディレクトリの移転が原因です。
全ページが404のとき:書き換えルールを修復する
管理画面からパーマリンクを再保存する
「設定」→「パーマリンク」を開き、何も変えずに「変更を保存」を押します。これでWordPress内部の書き換えルールが作り直され、Apacheで書き込み権限があれば .htaccess も更新されます。WP-CLIでも同じことができます。
# 書き換えルールを作り直し、.htaccess も書き出す
wp rewrite flush --hard
# 現在のパーマリンク構造
wp option get permalink_structure
Apacheの場合:.htaccessとAllowOverrideを確認する
再保存しても直らないときは、ドキュメントルートの .htaccess に次の標準ブロックがあるかを確認します。
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
ブロックが正しいのに404になる場合は、Apacheが .htaccess を読んでいないことを疑います。VPSで自分でApacheを入れた場合、ディレクトリ設定が AllowOverride None のままだと .htaccess は無視されます。また、mod_rewrite が無効だと <IfModule> の中身ごと読み飛ばされ、エラーも出ません。
# Debian/Ubuntu: mod_rewrite を有効化
sudo a2enmod rewrite
# VirtualHost の <Directory /var/www/html> 内を AllowOverride All に変更後
sudo apachectl configtest && sudo systemctl reload apache2
Nginxの場合:try_filesを確認する
Nginxは .htaccess を読まないため、パーマリンクの再保存では直りません。server ブロックに次の設定があるかを確認します。
location / {
try_files $uri $uri/ /index.php?$args;
}
この行がないと、実在しないパス(=WordPressのパーマリンク)はすべてNginxが404を返します。変更後は sudo nginx -t で構文を確認してから再読み込みします。
カスタム投稿タイプだけが404のとき
特定のカスタム投稿タイプやタクソノミーだけが404なら、登録後に書き換えルールが更新されていない可能性があります。プラグインやテーマでカスタム投稿タイプを追加・変更したら、パーマリンクの再保存を1回行ってください。自作プラグインなら、有効化時に register_post_type() を呼んだあとで flush_rewrite_rules() を実行するのが定石です。init フックで毎回 flush_rewrite_rules() を呼ぶと処理が重くなるため避けてください。
特定URLの404:まず対象を一覧にする
旧URLの404を直すには、どのURLが404になっているかを正確に把握する必要があります。情報源は2つあります。
- Google Search Console:「インデックス作成」→「ページ」で、未登録の理由が「見つかりませんでした(404)」のURLを確認し、一覧をエクスポートします(2026年10月時点の画面構成)。検索エンジンが知っているURLが分かりますが、反映に時間差があります。
- サーバーのアクセスログ:実際にアクセスがあって404になったURLが分かります。件数の多いものから対処すると効率的です。
# 404になったURLを件数順に(combined形式のログ)
awk '$9 == 404 {print $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -100 > /tmp/404-top.txt
ログには、.env や他のCMSの管理画面のパスなど、攻撃目的のスキャンも大量に含まれます。これらは本来存在しないURLなので、リダイレクトの対象にしません。対処すべきなのは、過去に実在したページのURLです。
対応表を作り、行き先を決める
一覧から、旧URLと新URLの対応表をCSVで作ります。この表がリダイレクト設定と検証の両方の元になります。パスはサイトのルートからの形(/ で始まる形)でそろえます。
source,target
/2023/05/12/old-article/,/old-article/
/category/news/old-post/,/news-renewal/
/old-service/,/services/
行き先を決めるときの基準は次のとおりです。
- 内容が同じか後継のページがあれば、そのページへ301リダイレクトする
- 削除して代わりもないページは、404のままにするか410を返す。関係のないトップページへまとめて転送すると、検索エンジンからはソフト404とみなされやすく、利用者にも不親切です
- 転送先がさらに転送される「多段リダイレクト」にしない。表の target が別の行の source になっていないかを確認する
なお、WordPressには、投稿のスラッグを変えたときに古いスラッグを記録して新しいURLへ転送する仕組みが標準で入っています。同じ投稿のスラッグ変更だけなら、リダイレクトを追加しなくても転送されることがあります。対応表を作る前に、実際に旧URLへアクセスして転送されるかを確かめると、作業量を減らせます。
301リダイレクトを一括で設定する
パターンで処理できる場合
パーマリンクを日付入りから投稿名のみに変えた、といった規則的な変更なら、1行の正規表現でまとめて転送できます。.htaccess の # BEGIN WordPress より前に書きます。WordPressのブロック内に書くと、パーマリンクの再保存で消えます。
# /2023/05/12/slug/ → /slug/ へ301転送
RewriteEngine On
RewriteRule ^[0-9]{4}/[0-9]{2}/[0-9]{2}/([^/]+)/?$ /$1/ [R=301,L]
Nginxなら server ブロックに書きます。
# /2023/05/12/slug/ → /slug/ へ301転送
rewrite ^/[0-9]{4}/[0-9]{2}/[0-9]{2}/([^/]+)/?$ /$1/ permanent;
個別の対応表を使う場合
規則性のない数百件の転送は、1件ずつ書くより、対応表から設定を生成します。Apacheなら、CSVから RewriteRule の行を作れます。Excelで保存したCSVは行末が CRLF のことがあるため、tr -d '\r' で取り除いてから読みます。
# CSV(1行目は見出し)から .htaccess 用の行を生成
tail -n +2 redirects.csv | tr -d '\r' | while IFS=, read -r from to; do
from_re=$(printf '%s' "${from#/}" | sed 's/[.[\*^$()+?{|]/\\&/g')
printf 'RewriteRule ^%s/?$ %s [R=301,L]\n' "${from_re%/}" "$to"
done > redirects.htaccess
上の例の対応表からは、次のような行ができます。末尾のスラッシュはあってもなくても一致するようにしています。
RewriteRule ^2023/05/12/old-article/?$ /old-article/ [R=301,L]
RewriteRule ^category/news/old-post/?$ /news-renewal/ [R=301,L]
RewriteRule ^old-service/?$ /services/ [R=301,L]
生成した redirects.htaccess の中身を目視で確認してから、.htaccess の # BEGIN WordPress より前に、RewriteEngine On の行に続けて貼り付けます。作業前に .htaccess のコピーを取っておけば、表示がおかしくなってもすぐ戻せます。
Nginxでは map を使うと、件数が多くても見通しよく管理できます。map は http コンテキスト(server ブロックの外)に書きます。
# /etc/nginx/conf.d/redirects.conf(http コンテキスト)
map $uri $redirect_to {
default "";
include /etc/nginx/redirects.map;
}
# /etc/nginx/redirects.map(1行に「旧パス 新パス;」)
/old-service/ /services/;
/category/news/old-post/ /news-renewal/;
# server ブロック内
if ($redirect_to) {
return 301 $redirect_to;
}
map のキーは完全一致なので、末尾のスラッシュの有無が違うURLは別のキーとして書きます。変更後は sudo nginx -t で構文を確認してから再読み込みします。
サーバーの設定を触れない環境では、リダイレクト管理用のプラグインを使います。たとえば Redirection はCSVの取り込みに対応しており、作った対応表を登録でき、転送の発生回数も記録できます。
設定後に一括で検証する
設定したら、対応表の全行について、実際に301が返り、行き先が正しく、転送先が200を返すかを確認します。
注意点として、curl の %{redirect_url} は https://example.com/old-article/ のような完全なURLを返します。対応表の target(/old-article/)とそのまま比べると、正しく転送されていても常に不一致になります。次のスクリプトでは、target にサイトのURLを付けた完全なURLを期待値にして比べます。
base="https://example.com" # 自分のサイトのURL(末尾の / なし)
tail -n +2 redirects.csv | tr -d '\r' | while IFS=, read -r from to; do
# target が完全なURLならそのまま、パスならサイトのURLを付けて期待値にする
case "$to" in
http://*|https://*) expected="$to" ;;
*) expected="$base$to" ;;
esac
# 旧URLのステータスと転送先(完全なURL)
read -r code location <<< "$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "$base$from")"
# 転送先そのもののステータス
final=$(curl -s -o /dev/null -w '%{http_code}' "$expected")
if [ "$code" = "301" ] && [ "$location" = "$expected" ] && [ "$final" = "200" ]; then
result=OK
else
result=NG
fi
printf '%s\t%s\t%s\t%s\texpected=%s\tfinal=%s\n' "$result" "$code" "$from" "$location" "$expected" "$final"
done | tee redirect-check.log
# NG の件数
grep -c '^NG' redirect-check.log
結果の各行は「OK/NG、旧URLのステータス、旧パス、実際の転送先、期待した転送先、転送先のステータス」の順です。NGの行は、次のどれかに当たります。
- ステータスが
404:その行のリダイレクトが効いていない(.htaccessの位置、mapのキーの書き方を確認) - ステータスが
302:恒久的な転送になっていない(R=301・return 301を確認) - 転送先が期待値と違う:別のルールが先に一致している、または対応表の誤り
finalが200以外:転送先のページ自体が存在しない、または転送先がさらに転送されている
キャッシュ系のプラグインやCDNを使っている場合は、古い404の応答がキャッシュに残っていることがあるため、検証前にキャッシュを削除してください。
最後に、Search Consoleの「ページ」レポートで該当する理由を開き、「修正を検証」を押すと、再クロールが進みます。
まとめ
- 全ページが404なら書き換えルールの問題。パーマリンクの再保存、
.htaccessとAllowOverride、mod_rewrite、Nginxのtry_filesを順に確認する - 特定URLの404は、Search Consoleとアクセスログから対象を一覧化し、旧URLと新URLの対応表を作る
- 行き先のないページをトップページへまとめて転送しない。404や410のままにする判断も必要
- 規則的な変更は正規表現1行で、個別の転送は対応表から設定を生成する。
.htaccessの追記は# BEGIN WordPressより前に - 検証では、
curlが返す完全なURLと「サイトのURL+target」を比べ、301・行き先・転送先の200がそろっているかを全行で確認する