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がそろっているかを全行で確認する

関連記事

お気軽にご相談ください

お見積りへ お問い合わせへ
▲