2026年5月24日

2026年5月24日

CORS エラーが発生した場合の対処法(コンソールの読み方と WordPress での直し方)

CORS エラーは、ブラウザが「別のオリジン(スキーム・ホスト名・ポートの組み合わせ)からのレスポンスを、許可の表明がないので JavaScript に渡さない」と判断したときに出ます。直すのはリクエストを送る側ではなく、レスポンスを返すサーバー側です。この記事では、コンソールに出るメッセージから原因を特定する方法と、WordPress の REST API・admin-ajax・フォントなどでよくある原因を、可能性の高い順に確認・修正する手順を説明します。

前提:CORS はブラウザだけが行うチェック

同じ URL を curl やサーバー側の PHP から呼ぶと問題なく取得できるのに、ブラウザの fetch だけ失敗する。これが CORS エラーの典型です。CORS はブラウザが利用者を守るための仕組みなので、サーバー同士の通信には関係しません。

よく見かける誤った対処として、fetch に mode: 'no-cors' を付ける方法があります。これはエラーを消すのではなく、中身を読めない「不透明なレスポンス」を受け取るだけなので、JSON を扱いたい場合の解決にはなりません。また、ブラウザの拡張機能やセキュリティ設定を無効にして回避するのも、自分の環境で動くだけで利用者の環境では直りません。

コンソールのメッセージで原因を見分ける

開発者ツールのコンソールと Network タブを開いて、失敗したリクエストを確認します。Chrome では次のようなメッセージが出ます(表記はブラウザやバージョンで多少異なります)。

  • No 'Access-Control-Allow-Origin' header is present on the requested resource:レスポンスに許可のヘッダーがない。サーバーが設定していないか、エラー応答でヘッダーが落ちている
  • The 'Access-Control-Allow-Origin' header contains multiple values:ヘッダーが二重に付いている。Web サーバーと WordPress(またはプラグイン)の両方で付けている
  • Response to preflight request doesn't pass access control check: It does not have HTTP ok status.:事前確認の OPTIONS リクエストが 401・403・404・405 などで失敗している
  • Request header field x-wp-nonce is not allowed by Access-Control-Allow-Headers:送ろうとしたヘッダーが許可されていない
  • must not be the wildcard '*' when the request's credentials mode is 'include':Cookie を送る設定なのに、許可が * になっている

Network タブでは、本来のリクエストの前に OPTIONS メソッドのリクエスト(プリフライト)が出ているかも確認します。Content-Type: application/json で送る、X-WP-Nonce や Authorization ヘッダーを付ける、PUT や DELETE を使う、といった場合はプリフライトが発生し、そこで許可が得られないと本来のリクエストは送られません。

原因1:エラー応答やリダイレクトに許可ヘッダーが付いていない

設定はしてあるのに「ヘッダーがない」と言われる場合、実際には 500 エラーやリダイレクトが返っていることがよくあります。Network タブでステータスコードを見てください。

  • 500 や 403 になっている:PHP のエラーやセキュリティプラグイン・WAF による遮断で、ヘッダーを付ける処理まで届いていない。エラーログを確認し、本来のエラーを先に直す
  • 301・302 になっている:http から https、www の有無、末尾のスラッシュなどでリダイレクトされている。プリフライトはリダイレクトを許されないため失敗する。リクエスト先の URL を最終的な URL に合わせる

nginx の add_header は、既定では 200 や 301 などの一部のステータスにしかヘッダーを付けません。エラー応答にも付けたいときは always を指定します。

原因2:ヘッダーが二重に付いている

WordPress の REST API は、標準でリクエストの Origin をそのまま Access-Control-Allow-Origin に返す処理(rest_send_cors_headers())を持っています。そこに nginx や .htaccess で同じヘッダーを追加すると、値が2つになってブラウザに拒否されます。

curl -sI -H "Origin: https://app.example.com" https://example.com/wp-json/ でヘッダーを確認し、Access-Control-Allow-Origin が2行出ていれば二重です。どちらか一方(多くの場合は WordPress 側)に寄せ、Web サーバー側の追加を外します。

原因3:許可するオリジンやヘッダーが合っていない

REST API の標準動作は「どのオリジンでも返す」なので、公開 API として動かすだけなら追加の設定がいらないことも多いです。逆に、許可するオリジンを絞りたい場合は、標準の処理を外して自前の判定に置き換えます。子テーマの functions.php か mu-plugin に書き、検証環境で試してから本番に入れてください。

<?php
// mu-plugins/rest-cors-allowlist.php
add_action( 'rest_api_init', function () {
	remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );

	add_filter( 'rest_pre_serve_request', function ( $served ) {
		$allowed = array(
			'https://app.example.com',
			'https://staging-app.example.com',
		);
		$origin = get_http_origin();

		if ( $origin && in_array( $origin, $allowed, true ) ) {
			header( 'Access-Control-Allow-Origin: ' . $origin );
			header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
			header( 'Access-Control-Allow-Credentials: true' );
		}
		header( 'Vary: Origin', false );

		return $served;
	} );
}, 15 );

Vary: Origin は、CDN やキャッシュプラグインが「あるオリジン向けのレスポンス」を別のオリジンに使い回さないための指定です。オリジンによってヘッダーを変える場合は外さないでください。

許可するリクエストヘッダーを増やしたい場合は、rest_allowed_cors_headers フィルター(WordPress 5.5 以降)で配列に追加できます。JavaScript から読ませたいレスポンスヘッダー(X-WP-Total など以外)は rest_exposed_cors_headers フィルターで追加します。

なお、許可するのは CORS だけで、認証は別の話です。Cookie によるログイン状態で REST API を呼ぶには nonce(X-WP-Nonce)が必要で、別ドメインのアプリから Cookie を送る構成は、ブラウザのサードパーティ Cookie 制限の影響も受けます。ヘッドレス構成などで管理者権限の操作をさせたい場合は、同じオリジンのサーバー側から API を呼ぶ構成も検討してください。

原因4:admin-ajax.php や独自の PHP を呼んでいる

admin-ajax.php は、WordPress の allowed_http_origins フィルターで許可されたオリジン(標準ではサイト自身のホームURLや管理画面のURL)にだけ許可ヘッダーを返します。別ドメインから呼ぶ必要がある場合は、このフィルターに追加するか、新しく作るなら REST API のエンドポイントで実装するほうが扱いやすいです。いずれの場合も、nonce と current_user_can() による権限チェックは省かないでください。

原因5:CDN や別ホスト名のフォント・画像

CSS から読み込むフォントや、canvas で扱う画像は、別オリジンからの読み込みに CORS の許可が必要です。CDN のホスト名を分けたあとにフォントだけ表示されなくなった、という場合はこれが原因です。nginx なら静的ファイルの location に追加します。

location ~* \.(?:woff2?|ttf|otf)$ {
    add_header Access-Control-Allow-Origin "https://www.example.com" always;
    add_header Vary Origin always;
}

CDN 側でキャッシュされた古いレスポンスにはヘッダーがないので、設定後は CDN のキャッシュを削除してから確認します。設定ファイルの変更前にはバックアップを取り、nginx -t で文法を確かめてください。

直ったかを確かめる

curl で Origin ヘッダーを付けて、本来のリクエストとプリフライトの両方を確認します。

# 本来のリクエスト
curl -sI -H "Origin: https://app.example.com" https://example.com/wp-json/wp/v2/posts

# プリフライト
curl -si -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type, x-wp-nonce" \
  https://example.com/wp-json/wp/v2/posts | grep -iE "^HTTP|access-control"

プリフライトが 200 か 204 で返り、Access-Control-Allow-Origin に送ったオリジンが1つだけ、Access-Control-Allow-Headers に送るヘッダーが含まれていれば、ブラウザでも通るはずです。ブラウザのキャッシュが残っていることがあるので、最後はシークレットウィンドウでも確認します。ブラウザの挙動や表示されるメッセージは更新で変わることがあるため(2026年10月現在)、迷ったときは MDN の CORS の解説も参照してください。

まとめ

  • CORS はブラウザのチェックで、直すのはレスポンスを返すサーバー側。no-cors は解決策にならない
  • コンソールのメッセージで「ヘッダーがない」「二重」「プリフライト失敗」「ヘッダー不許可」「credentials と *」を見分ける
  • 「ヘッダーがない」の多くは、実際には 500・403・リダイレクト。ステータスコードを先に見る
  • WordPress の REST API は標準で CORS ヘッダーを返すので、Web サーバー側との二重付与に注意する
  • 絞り込みは rest_pre_serve_request で置き換え、Vary: Origin を付ける。認証と権限チェックは別に行う

お気軽にご相談ください

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