2026年8月5日
2026年8月5日
Apacheでクロスオリジン(CORS)を設定する方法|許可オリジンの限定とWordPress REST APIの注意点
Apache で CORS を許可するには、mod_headers の Header 命令で Access-Control-Allow-Origin などのレスポンスヘッダーを付けます。ただし、何でも * で許可すると不要な公開につながり、WordPress の REST API では WordPress 自身が CORS ヘッダーを出すため、Apache 側で重ねると逆にエラーになることがあります。この記事では、CORS の仕組みを押さえたうえで、静的ファイル向けの基本設定、複数のオリジンを限定して許可する書き方、プリフライトへの対応、REST API での注意点と確認方法を説明します。
CORS が必要になる場面
ブラウザには、あるサイト(オリジン)から読み込んだ JavaScript が、別のオリジンのデータを勝手に読み取れないようにする同一オリジンポリシーがあります。オリジンは「スキーム+ホスト名+ポート」の組み合わせで、https://example.com と https://www.example.com、http://example.com はそれぞれ別のオリジンです。
CORS は、データを持つ側のサーバーが「このオリジンからの読み取りは許可する」とレスポンスヘッダーで宣言する仕組みです。WordPress の運用では、次のような場面で必要になります。
- Web フォントを別ドメイン(画像用のサブドメインや CDN)から配信している。フォントの読み込みには CORS の許可が必要です
- 別ドメインのフロントエンド(ヘッドレス構成のアプリなど)から、fetch で WordPress の REST API を呼ぶ
- 別サイトの JavaScript から JSON ファイルや画像を canvas で加工する
ブラウザの開発者ツールのコンソールに「has been blocked by CORS policy」のようなメッセージが出ていれば、CORS の設定が足りていません。メッセージの後半に、足りないヘッダーの名前が書かれていることが多いので、まずはそこを読みます。
CORS はブラウザが守るルールであり、サーバーを守るアクセス制限ではない点も押さえておきます。curl やサーバー間の通信は CORS を無視するため、公開したくないデータは認証や権限チェックで守る必要があります。
作業前の準備
Header 命令を使うには mod_headers が必要です。Debian/Ubuntu 系では有効になっていないことがあります。
sudo apache2ctl -M | grep headers
# 表示されなければ有効にする(Debian/Ubuntu系)
sudo a2enmod headers
sudo systemctl restart apache2
設定は VirtualHost に書くのが基本です。.htaccess に書く場合は、そのディレクトリで AllowOverride に FileInfo が含まれている必要があります。WordPress の .htaccess に書くときは、# BEGIN WordPress と # END WordPress の間に書かないでください。この区間は WordPress が書き換えることがあり、追記した内容が消える可能性があります。編集前には設定ファイルのバックアップを取ります。
静的ファイル(フォント)に CORS を設定する
画像やフォントを static.example.com のような別ホストから配信し、本体の https://www.example.com で使う場合の例です。配信する側(static.example.com)の VirtualHost に書きます。
<VirtualHost *:443>
ServerName static.example.com
<FilesMatch "\.(woff2?|ttf|otf|eot)$">
Header set Access-Control-Allow-Origin "https://www.example.com"
Header merge Vary Origin
</FilesMatch>
</VirtualHost>
FilesMatch で対象をフォントに絞っているため、他のファイルには影響しません。誰でも使ってよい公開フォントなら * を指定する方法もありますが、自サイト用なら許可するオリジンを書くほうが、他サイトからの流用も防げます。
Vary: Origin は、オリジンによってレスポンスのヘッダーが変わることをキャッシュに伝えるためのものです。CDN やブラウザのキャッシュが、あるオリジン向けのレスポンスを別のオリジンに使い回して CORS エラーになるのを防ぎます。merge を使うと、既存の Vary の値を残したまま追加できます。
複数のオリジンを限定して許可する
Access-Control-Allow-Origin には、オリジンを1つだけか * しか書けません。カンマ区切りで複数並べるとブラウザに拒否されます。複数のオリジンを許可したい場合は、リクエストの Origin ヘッダーを許可リストと照合し、一致したときだけその値を返します。
<IfModule mod_headers.c>
SetEnvIf Origin "^(https://(www\.)?example\.com|https://app\.example\.net)$" CORS_ORIGIN=$1
Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header always merge Vary Origin
</IfModule>
SetEnvIf(mod_setenvif)で Origin が許可リストに一致したときだけ環境変数 CORS_ORIGIN を作り、env=CORS_ORIGIN で、その変数があるときだけヘッダーを付けています。正規表現の先頭の ^ と末尾の $ は省略しないでください。省略すると https://example.com.attacker.example のような別のオリジンにも一致してしまいます。
Origin ヘッダーを照合せずにそのまま返す設定は、* と同じかそれ以上に危険です。特に次の Cookie 付きリクエストを許可している場合は、どのサイトからでもログイン中の利用者の権限でデータを読めることになります。
Cookie 付きのリクエストとプリフライト
認証情報を伴う場合
fetch で credentials: "include" を指定するなど、Cookie や認証ヘッダーを伴うリクエストを許可するには、Access-Control-Allow-Credentials: true が必要です。このとき Access-Control-Allow-Origin に * は使えず、具体的なオリジンを返す必要があります。上の許可リストの方式と組み合わせ、許可するオリジンは自分が管理するものだけにします。
プリフライトリクエストに応える
Content-Type: application/json の POST や、独自のヘッダー(X-WP-Nonce など)を付けたリクエストでは、ブラウザが本番のリクエストの前に OPTIONS メソッドで「このリクエストを送ってよいか」を問い合わせます。これをプリフライトと呼びます。サーバーは許可するメソッドとヘッダーを返す必要があります。
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS" env=CORS_ORIGIN
Header always set Access-Control-Allow-Headers "Content-Type, X-WP-Nonce" env=CORS_ORIGIN
Header always set Access-Control-Max-Age "600" env=CORS_ORIGIN
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteCond %{ENV:CORS_ORIGIN} !^$
RewriteRule ^ - [R=204,L]
</IfModule>
許可されたオリジンからの OPTIONS には、mod_rewrite で中身のない 204 を返しています。このように Apache 自身が作るレスポンスにもヘッダーを付けるため、Header には always を指定しています。Access-Control-Max-Age はプリフライトの結果をブラウザがキャッシュする秒数で、ブラウザごとに上限があります。
WordPress の REST API では Apache 側で重ねない
WordPress の REST API(/wp-json/)は、コアの rest_send_cors_headers() によって、リクエストに Origin ヘッダーがあればその値を Access-Control-Allow-Origin として返し、あわせて Access-Control-Allow-Credentials なども送ります。そのため、Apache 側でも /wp-json/ に CORS ヘッダーを付けると、同じヘッダーが2つ届き、ブラウザが「複数の値がある」としてエラーにすることがあります。
REST API で CORS が問題になったときは、まずブラウザの開発者ツールのネットワークタブで、実際に返っているヘッダーを確認してください。WordPress がすでに返しているなら、Apache 側の設定は不要か、/wp-json/ を対象から外す必要があります。許可するオリジンを絞りたい場合は、WordPress 側で rest_pre_serve_request フィルターなどを使ってヘッダーの扱いを変える方法がありますが、コアの挙動はバージョンで変わることがあるため、使っている版のコードとドキュメントを確認してから実装します(2026年10月現在)。
また、Cookie で認証する REST API の書き込み操作には、CORS とは別に nonce(X-WP-Nonce ヘッダー)が必要です。CORS を許可しても、nonce と権限のチェックが省略されるわけではありません。別ドメインのアプリから書き込みを行う構成では、アプリケーションパスワードなど、Cookie に頼らない認証方式も検討してください。
設定を確認する
設定を変えたら、文法を確認して反映し、curl でヘッダーを確かめます。
sudo apache2ctl configtest
sudo systemctl reload apache2
# 通常のリクエスト
curl -sI -H "Origin: https://www.example.com" https://static.example.com/fonts/sample.woff2
# プリフライト
curl -si -X OPTIONS \
-H "Origin: https://app.example.net" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type" \
https://example.com/some-endpoint
許可したオリジンでは Access-Control-Allow-Origin が返り、許可していないオリジン(例えば https://other.example)では返らないことを、両方試して確認します。CDN を通している場合は、CDN のキャッシュに古いレスポンスが残っていることがあるため、キャッシュを削除してから確かめてください。
まとめ
- CORS はブラウザに読み取りを許可する仕組みで、サーバーを守るアクセス制限ではない
- mod_headers を有効にし、VirtualHost か WordPress の区間外の .htaccess に書く
- フォントなどは
FilesMatchで対象を絞り、Vary: Originを付ける - 複数オリジンは
SetEnvIfで許可リストと照合し、^と$で前後を固定する - Cookie 付きなら
*は使えない。プリフライトにはalways付きのヘッダーと 204 で応える - REST API は WordPress 自身が CORS ヘッダーを出すため、Apache 側で重ねる前に実際のヘッダーを確認する