Nginx add_header の継承:消えるセキュリティヘッダーの謎を解く

intermediate Nginx2026-07-01| Nginx(全バージョン)を実行しているすべてのLinuxディストリビューション(Ubuntu、CentOS、Debian)。

Error Message

X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block
#nginx#webセキュリティ#devops#sysadmin

消えるヘッダーの謎

セキュリティ監査が完了しましたが、結果に頭を抱えています。レポートによると、本番サイトにはX-Frame-OptionsContent-Security-Policy(CSP)ヘッダーが存在しないとのこと。nginx.confserverブロックに追加したはずなのに。ホームページをテストすると問題はありません。ところが、/api/v1/statusのようなAPIエンドポイントにアクセスした途端、重要なセキュリティヘッダーがすっと消えてしまいます。

このエラー—X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block—はNginxユーザーがよく陥る落とし穴です。

デバッグの手順

ブラウザの開発者ツールはキャッシュが邪魔をすることがあるので頼りにしないこと。代わりにcurlを使って、サーバーが実際に送信しているものを確認しましょう。ルートドメインに対してテストを実行し、特定のサブパスと比較してみてください。

# ヘッダーはここにある可能性が高い
curl -I https://example.com/

# ヘッダーがよく消えるのはここ
curl -I https://example.com/api/data

最初のコマンドではヘッダーが表示されるのに、2番目では表示されない場合、設定はおそらく次のようになっているはずです:

server {
    listen 443 ssl;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Content-Security-Policy "default-src 'self';";

    location /api/ {
        # この1行が上の設定をすべて壊す
        add_header X-API-Version "1.0";
        proxy_pass http://backend;
    }
}

このシナリオでは、/api/のレスポンスにX-API-Versionは表示されます。しかし残念ながら、Nginxはそのパスに対してX-Frame-OptionsCSPヘッダーを黙って破棄してしまいます。

Nginxがこのような動作をする理由

ほとんどのNginxディレクティブは親から子へとスムーズに引き継がれます。しかしadd_headerは意地悪な兄弟のような振る舞いをします。locationブロックでadd_headerが1つでも定義されると、その上のserverhttpブロックで定義されたすべてのadd_headerが無視されてしまいます。

Nginxはヘッダーリストをマージしません。完全に上書きします。この設計は、通常のデプロイ時にベテランエンジニアでさえ油断させてしまいます。

修正方法:2つの実証済みアプローチ

方法1:「Include」戦略(ベストプラクティス)

すべてのブロックにヘッダーをコピー&ペーストしないでください。代わりに、部分的な設定ファイルを使用してDRY(Don't Repeat Yourself)原則を守りましょう。これにより、サイト全体の一貫性が保たれます。

  1. /etc/nginx/conf.d/security_headers.confにファイルを作成します:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self';" always;
  1. serverブロックと追加ヘッダーが必要なすべてのlocationブロックでこのファイルを参照します:
server {
    include conf.d/security_headers.conf;

    location /api/ {
        include conf.d/security_headers.conf;
        add_header X-API-Version "1.0" always;
        proxy_pass http://backend;
    }
}

方法2:headers_more モジュールを使用する

OpenRestyを使用しているか、カスタムモジュールをインストールできる場合、headers_moreは救世主となります。more_set_headersディレクティブはデフォルトよりもはるかに賢く動作します。子ブロックが独自のヘッダーを追加しても、親から継承します。

server {
    more_set_headers "X-Frame-Options: SAMEORIGIN";

    location /api/ {
        more_set_headers "X-API-Version: 1.0";
        proxy_pass http://backend;
    }
}

このモジュールを使用すると、追加のinclude行なしで両方のヘッダーがレスポンスに表示されます。

確認:修正の検証

作業を終える前に、必ず構文を検証してください。セミコロン1つの欠落でサイトがダウンする可能性があります。

nginx -t

テストが通ったら、変更を適用するためにサービスをリロードします:

systemctl reload nginx

最後に、特定のAPIパスを再度確認してください。ターミナル出力に完全なヘッダースタックが表示されるはずです。

Nginxヘッダーのプロヒント

- **「always」フラグは必須:**デフォルトでは、Nginxは200や302などの成功レスポンスにのみヘッダーを送信します。`always`を追加すると、404や500エラー時もセキュリティヘッダーが有効に保たれます。
- **継承はオール・オア・ナッシング:**子ブロックでヘッダーを1つ追加するだけで、すべてがリセットされることを忘れずに。
- **セキュリティを一元管理:**`include`ファイルを使用してセキュリティ設定を管理しましょう。6ヶ月後に新しいエンドポイントを追加する際の人的ミスを防ぎます。

Related Error Notes