2026年5月31日

2026年5月31日

ApacheでCORS(クロスオリジン)を設定する方法|WordPressで二重ヘッダーを避ける手順

Apache で CORS を許可するには、mod_headers で Access-Control-Allow-Origin などのレスポンスヘッダーを付けます。ただし WordPress の REST API はコア自身が CORS ヘッダーを送るため、Apache 側でも同じヘッダーを足すと「値が二重」になってかえってブラウザに拒否されます。この記事では、静的ファイル(Webフォントや画像)を別ドメインから読ませる設定と、REST API を別オリジンのアプリから呼ぶ設定を分けて、それぞれの書き方と確認方法を説明します。

CORS で何が起きているのかを先に押さえる

ブラウザは、ページのオリジン(スキーム+ホスト+ポート)と異なるオリジンへ JavaScript から通信するとき、サーバーの返事に「このオリジンからの読み取りを許可する」という宣言があるかを確認します。宣言が無ければ、通信自体は届いていてもレスポンスを JavaScript に渡しません。つまり CORS は「サーバーを守る仕組み」ではなく、「ブラウザが他サイトのデータを勝手に読ませないための仕組み」で、許可を出すのはデータを返す側のサーバーです。

https://example.com と https://www.example.com、http:// と https:// も別オリジンです。CDN 用のサブドメインを置いた、ヘッドレス構成にした、といった変更の直後に CORS エラーが出始めるのはこのためです。

もう一つ知っておきたいのが「プリフライト」です。Content-Type: application/json の POST や、独自ヘッダー(X-WP-Nonce など)付きのリクエストでは、ブラウザが本番のリクエストの前に OPTIONS メソッドで「このメソッドとヘッダーで送ってよいか」を問い合わせます。この OPTIONS への返事に許可ヘッダーが無いと、本番のリクエストは送られません。

エラーメッセージから原因を読み取る

設定を変える前に、ブラウザの開発者ツールのコンソールとネットワークタブで、どのリクエストが何と言われて失敗しているかを確認します。Chrome 系の表示は概ね次のような意味です。

  • 「No 'Access-Control-Allow-Origin' header is present」…許可ヘッダーが付いていません。Apache の設定が効いていないか、エラー応答(404・500)でヘッダーが落ちています。
  • 「The 'Access-Control-Allow-Origin' header contains multiple values」…ヘッダーが二重です。WordPress と Apache の両方が付けているケースがほとんどです。
  • 「Response to preflight request doesn't pass access control check」…OPTIONS への返事に問題があります。OPTIONS が 401・403・405 になっていないかを見ます。
  • 「The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'」…Cookie 付き通信で * を返しています。

ネットワークタブで失敗したリクエストを選び、レスポンスヘッダーに何が付いているか、ステータスコードは何かを見れば、下のどの設定を直すべきかが絞れます。

事前準備:mod_headers を有効にする

CORS ヘッダーは mod_headers の Header ディレクティブで付けます。Debian / Ubuntu 系では次のように有効化します。RHEL 系(AlmaLinux・Rocky Linux など)は標準で読み込まれていることが多いので、httpd -M の一覧で headers_module を確認してください。

# Debian / Ubuntu
sudo a2enmod headers
sudo apache2ctl configtest
sudo systemctl reload apache2

# RHEL 系:読み込み済みか確認
httpd -M | grep headers

.htaccess に書く場合、そのディレクトリで AllowOverride に FileInfo(または All)が許可されている必要があります。許可されていないと、500 エラーになるか、書いた行が効きません。VirtualHost の設定ファイルを編集できるなら、そちらに書く方が管理しやすくなります。設定ファイルを編集する前には、元のファイルをコピーしてバックアップを取っておきます。

静的ファイル(フォント・画像)を別オリジンに許可する

よくあるのが、Webフォントを static.example.com から配信して www.example.com のページで使うケースです。フォントは CSS から読み込まれる際に CORS のチェックを受けるため、配信側で許可が要ります。

<IfModule mod_headers.c>
    <FilesMatch "\.(woff2?|ttf|otf|eot|svg)$">
        Header always set Access-Control-Allow-Origin "https://www.example.com"
        Header always merge Vary "Origin"
    </FilesMatch>
</IfModule>

ポイントは三つあります。まず Header set ではなく Header always set を使うと、リダイレクトやエラー応答にもヘッダーが付きます。次に、許可するオリジンは末尾のスラッシュを付けず、スキームまで含めて書きます。https://www.example.com/ のようにスラッシュを付けると一致しません。最後に Vary: Origin を付けておくと、CDN やブラウザのキャッシュが「別のオリジン向けの返事」を使い回す事故を防げます。

公開フォントのように、どのサイトから使われても構わない資源なら "*" を指定しても問題はありません。ただし * は Cookie などの資格情報を伴う通信では使えません。

許可したいオリジンが複数ある場合

Access-Control-Allow-Origin に書けるオリジンは一つだけで、カンマ区切りで並べることはできません。複数を許可したいときは、リクエストの Origin ヘッダーを見て、許可リストに一致したときだけその値を返します。

<IfModule mod_headers.c>
    SetEnvIf Origin "^(https://(www\.example\.com|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>

正規表現は先頭 ^ と末尾 $ で囲み、ドットを \. でエスケープします。これを怠ると https://www.example.com.attacker.example のようなドメインまで一致してしまいます。

WordPress の REST API は WordPress 側で制御する

WordPress コアは、REST API(/wp-json/)へのリクエストに Origin ヘッダーがあると、rest_send_cors_headers() という関数で Access-Control-Allow-Origin にそのオリジンを返し、Access-Control-Allow-Credentials: true も付けます(2026年10月現在)。初期状態で別オリジンからの呼び出しが通ることが多いのはこのためです。ただし Cookie を使うログイン済みの操作には別途 nonce(X-WP-Nonce)が要るので、他サイトからログイン中ユーザーとして勝手に書き込める、ということにはなりません。

ここに Apache の Header always set Access-Control-Allow-Origin を全体に掛けると、WordPress の出したヘッダーと Apache のヘッダーが両方出て、前述の「multiple values」エラーになります。REST API の許可オリジンを絞りたいときは、Apache ではなく WordPress 側で制御するのが筋です。次のコードは mu-plugin(wp-content/mu-plugins/ に置く PHP ファイル)として使う例で、コアの CORS 処理を外し、許可リストのオリジンにだけヘッダーを返します。

<?php
/**
 * Plugin Name: REST API CORS allowlist
 */
add_action( 'rest_api_init', function () {
    // コアの CORS ヘッダー送出(優先度10で登録される)を外す
    remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );

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

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

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

許可リストに無いオリジンにはヘッダーを返さないので、ブラウザ側で読み取りが拒否されます。外部アプリが Cookie を使わずアプリケーションパスワードなどで認証する設計なら、Access-Control-Allow-Credentials の行は外して構いません。追加の独自ヘッダーを許可したい場合は、コアに rest_allowed_cors_headers フィルターがあるので、そこで配列に追加します。

プリフライトの OPTIONS も WordPress の REST API が自分で応答します。Apache 側で「OPTIONS は一律 204 を返す」といった書き換えルールを全体に入れると、この処理を飛ばしてしまうので、入れるとしても静的ファイルのパスに限定してください。また、セキュリティ系プラグインや WAF が OPTIONS や /wp-json/ を遮断していると、プリフライトが 403 になります。その場合は該当プラグインの REST API 制限の設定を確認します。

curl で設定を確認する

ブラウザのキャッシュに惑わされないよう、確認は curl で Origin ヘッダーを付けて送り、返ってくるヘッダーを見ます。

# 単純なリクエスト:許可ヘッダーが1つだけ返るか
curl -s -o /dev/null -D - \
  -H "Origin: https://www.example.com" \
  https://static.example.com/fonts/site.woff2

# プリフライト:OPTIONS が 200/204 で、許可ヘッダーが返るか
curl -s -o /dev/null -D - -X OPTIONS \
  -H "Origin: https://app.example.net" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type" \
  https://www.example.com/wp-json/wp/v2/posts

見るべき点は、Access-Control-Allow-Origin が1行だけであること、値がリクエストした Origin と一致していること、許可していないオリジンを指定したときにはヘッダーが返らないことの三つです。CDN を経由している場合は、CDN 側でヘッダーが付け足されたり、Vary を無視してキャッシュされたりすることがあるため、オリジンサーバーへ直接送った結果と CDN 経由の結果を比べてください。

つまずきやすい点

  • .htaccess を編集したのに変化が無いときは、AllowOverride で上書きが禁止されている可能性があります。VirtualHost 側を確認します。
  • PHP がエラーで落ちて 500 を返すと、PHP 側で付けていたヘッダーは出ません。CORS エラーの裏に PHP の致命的エラーが隠れていることがあるので、サーバーのエラーログも確認します。
  • Access-Control-Allow-Origin: * と資格情報付き通信は組み合わせられません。Cookie が要る通信は、オリジンを個別に返す方式にします。

設定の変更は、まずステージング環境で試し、curl とブラウザの両方で確認してから本番に反映するのが安全です。

まとめ

  • CORS はブラウザが別オリジンのレスポンスを読ませるかどうかの仕組みで、許可はデータを返すサーバー側で出す。
  • Apache では mod_headers の Header always set で付け、Vary: Origin も併せて付ける。複数オリジンは SetEnvIf で一致したものだけを返す。
  • WordPress の REST API はコアが CORS ヘッダーを送るため、Apache で重ねると二重ヘッダーになる。絞り込みは mu-plugin で rest_pre_serve_request を使って行う。
  • 確認は curl で Origin を付けて送り、ヘッダーが1行だけで値が一致しているかを見る。

お気軽にご相談ください

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