エラーの内容
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
このエラーは、フロントエンド(あるドメイン)が別のドメイン、サブドメイン、またはポートにあるバックエンドへリクエストを送った際、NginxがCORSヘッダーを返さないとブラウザのコンソールに表示されます。ブラウザはJavaScriptがレスポンスを受け取る前にブロックします。
発生する原因
ブラウザは同一オリジンポリシーを強制します。フロントエンドのオリジンとバックエンドのオリジンが異なる場合(ドメイン、サブドメイン、ポートが違う場合)、ブラウザはレスポンスにAccess-Control-Allow-Originヘッダーが含まれているか確認します。Nginxはデフォルトではこのヘッダーを付与しないため、明示的に設定する必要があります。
手順ごとの修正方法
1. Nginx設定ファイルを確認する
ls /etc/nginx/sites-enabled/
cat /etc/nginx/sites-enabled/your-api.conf
2. サーバーブロックにCORSヘッダーを追加する
単一の許可オリジン(最も一般的なケース)
server {
listen 80;
server_name api.example.com;
location / {
# まずプリフライトOPTIONSリクエストを処理する
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
proxy_pass http://localhost:8080;
}
}
すべてのadd_headerに付けるalwaysフラグは省略できません。これがないと、Nginxは2xxレスポンスにしかヘッダーを付与しません。エラーレスポンス(4xx、5xx)にはヘッダーが付かず、本来のエラーの代わりにCORSエラーが表示されてしまい、デバッグが非常に困難になります。
複数の許可オリジン(開発・ステージング・本番環境)
特定のオリジンを複数ホワイトリストに登録する場合は、mapブロックを使用します。serverブロックの外側、通常は設定ファイルの先頭か/etc/nginx/conf.d/cors.confに記述します。
map $http_origin $cors_origin {
default "";
"https://app.example.com" $http_origin;
"https://staging.example.com" $http_origin;
"http://localhost:3000" $http_origin;
}
server {
listen 80;
server_name api.example.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Vary' 'Origin' always;
proxy_pass http://localhost:8080;
}
}
Vary: Originヘッダーも重要です。これがないと、CDNやプロキシがあるオリジン向けのヘッダーを含むレスポンスをキャッシュし、別のオリジンにそのまま返してしまう可能性があります。
公開API — すべてのオリジンを許可する場合
add_header 'Access-Control-Allow-Origin' '*' always;
ワイルドカードは本当に公開APIの場合にのみ使用してください。クレデンシャル(Cookie、Authorizationヘッダー)を伴うリクエストでは、ブラウザは*を拒否します。
CookieやAuthorizationヘッダーを含むリクエスト
正確なオリジンを指定し(ワイルドカード不可)、クレデンシャルヘッダーを追加する必要があります。
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
3. 設定を確認してリロードする
# まず構文を検証する
sudo nginx -t
# 接続を切らずにリロードする
sudo nginx -s reload
修正を確認する
curl — 最速の確認方法
# 通常のGETリクエストを確認する
curl -I \
-H "Origin: https://app.example.com" \
https://api.example.com/data
# プリフライトOPTIONSリクエストを確認する
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization" \
-I https://api.example.com/data
出力にAccess-Control-Allow-Origin: https://app.example.comが含まれているか確認してください。curlでは表示されるのにブラウザでまだエラーが出る場合は、シークレットウィンドウで試してみてください。ブラウザがプリフライトの失敗をキャッシュしていることがあります。
ブラウザDevToolsで確認する
Networkタブ → 失敗しているリクエストをクリック → Headersタブ → Response Headersセクション。ヘッダーが存在している必要があります。curlでは表示されるのにここで見つからない場合は、リクエストが別のルートを経由しているか、キャッシュされたService Workerがインターセプトしていないか確認してください。
よくある落とし穴
- 子のlocationブロックが親のヘッダーを上書きする:Nginxでは、ネストされた
locationブロックにいずれかのadd_headerディレクティブがあると、親ブロックのすべてのadd_headerディレクティブが完全に無効になります(マージはされません)。これは最も驚きやすい挙動です。CORSヘッダーが必要なすべてのlocationブロックに直接記述してください。 - バックエンドもCORSヘッダーを送信している:アプリケーション(Node、Flaskなど)がCORSヘッダーを設定しつつNginxでも追加すると、
Access-Control-Allow-Origin: *, https://app.example.comのように重複したヘッダーが返されます。ブラウザはこれを拒否します。CORSの処理は必ずどちらか一方だけで行ってください。 - HTTPとHTTPSのオリジン不一致:
http://app.example.comとhttps://app.example.comは異なるオリジンです。設定内のスキームがブラウザから送られる値と完全に一致していることを確認してください。 - ワイルドカードサブドメインはネイティブでは非対応:Nginxは標準では
*.example.comのような指定ができません。上記のmapを使ったアプローチで、各サブドメインを明示的に列挙してください。 - プリフライトハンドラーの欠落:カスタムヘッダーを含むPOST・PUT・DELETEリクエストの前に、ブラウザはOPTIONSリクエストを送信します。NginxがOPTIONSをバックエンドに転送し、バックエンドがそれを処理しない場合、プリフライトが失敗して実際のリクエストが送信されません。

