
## この記事でわかること

- Nginx でセキュリティヘッダを書く 3 つの場所と使い分け
- `add_header` の **`always`** キーワードを必ず付ける理由
- `location` ブロック内 `add_header` の罠（上位の `add_header` が無効化される）
- Web 担当者向けの推奨設定（コピペ用）

## Nginx でヘッダを書く 3 つの場所

![Nginx でヘッダを書く 3 つの場所](/blog/nginx-security-headers/config-location.svg)

### 1. `http {}` ブロック（全サイト一括）
`/etc/nginx/nginx.conf` の `http {}` ブロックに書くと、サーバー上の全サイトに適用されます。**1 サーバー 1 サイト**の構成なら楽ですが、サイト別に上書きしたい場合は適しません。

### 2. `server {}` ブロック（推奨）
`/etc/nginx/sites-available/<site>.conf` の `server {}` ブロックに書く。**最も標準的な場所**で、サイト別に独立して設定できます。

### 3. `location {}` ブロック（パス別）
特定のパス（例: `/wp-admin/`）だけ別のヘッダを付けたい場合に使います。ただし**罠があるので注意**（後述）。

## 推奨設定（コピペ用）

```nginx
server {
    listen 443 ssl http2;
    server_name example.co.jp;

    # SSL 証明書 / 鍵の設定 ...

    # セキュリティヘッダ（必ず always キーワードを付ける）
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

    # CSP は最初 Report-Only で開始（観察目的なので unsafe-inline は入れない）
    add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; report-uri /csp-report" always;

    # ... 他の設定 ...
}
```

![Nginx server {} ブロックの推奨設定](/blog/nginx-security-headers/full-config.svg)

設定後の反映:

```bash
sudo nginx -t           # 構文チェック
sudo nginx -s reload    # リロード（無停止）
```

## `always` キーワードを必ず付ける理由

```nginx
# 悪い例（always なし）
add_header X-Frame-Options "SAMEORIGIN";

# 良い例（always 付き）
add_header X-Frame-Options "SAMEORIGIN" always;
```

`always` キーワードが**ない**場合、Nginx は限られた応答コード（**200 / 201 / 204 / 206 / 301 / 302 / 303 / 304 / 307 / 308**）にしかヘッダを付けません（公式 `ngx_http_headers_module` の仕様）。

**問題**:
- 4xx / 5xx エラーページにセキュリティヘッダが付かない
- 攻撃者が意図的にエラーを発生させて、ヘッダなしの応答を引き出せる
- HSTS が効かないエラーページから http に降格させられる可能性

`always` を付けると、**全ステータスコードに対してヘッダ付与**されます。セキュリティヘッダは必ず `always` を付けてください。

## `location` ブロックの罠

セキュリティヘッダ設定で**最も間違いやすいポイント**です。

```nginx
# 親 server {} ブロック
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

location /api/ {
    # 子 location ブロック内で add_header を使うと…
    add_header X-API-Version "v1";

    # ↑ ここで上の 2 つの add_header が「全部消える」!!
}
```

Nginx の仕様で「**子ブロックで `add_header` を 1 つでも書くと、親ブロックの `add_header` が継承されない**」という罠があります（追加ではなく上書きの動作）。

### 正しい書き方

```nginx
# 親 server {} ブロック
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

location /api/ {
    # 親で設定した全ヘッダを再記述する
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header X-API-Version "v1" always;
}
```

または `more_headers`（ngx_headers_more モジュール）を使うと継承挙動が改善されますが、追加モジュールが必要です。

## `/wp-admin/` 等で CSP を分けたい場合

WordPress のように管理画面と公開ページで CSP を変えたい場合:

```nginx
server {
    # 公開ページ用の厳しい CSP
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    # ... 他の always 系ヘッダ

    location /wp-admin/ {
        # 管理画面は緩める。ただし他のヘッダも全部書き直す必要あり
        add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        # ... 他の always 系ヘッダを再記述
    }
}
```

## CSP に `nonce` を動的に埋める場合

Nginx 単体では nonce のランダム生成ができないため、**バックエンド（PHP / Node.js / Rails 等）で nonce を生成 → CSP ヘッダを返す**設計が必要です。Nginx は `proxy_pass` で渡し、バックエンドの返したヘッダをそのまま使います。

```nginx
location / {
    proxy_pass http://backend;
    proxy_pass_header Content-Security-Policy;  # バックエンドの CSP を尊重
}
```

このパターンは [CSP の unsafe-inline 撤去手順](/blog/csp-unsafe-inline-mondai) で詳しく解説しています。

## 設定後の確認

### 構文チェック
```bash
sudo nginx -t
```
エラーが出たら直してから reload。

### コマンドラインでヘッダ確認
```bash
curl -I https://example.co.jp/
```

### ブラウザの開発者ツール
F12 → Network → 任意のページ → Response Headers

### ドメイン番人の単発チェック
[Web セキュリティヘッダ 単発チェック](/security-headers/check) で 30 秒で全ヘッダの状態がスコアリングされます。

## まとめ

- 推奨は **`server {}` ブロック**にヘッダを書く
- すべての `add_header` に **`always`** を付ける（4xx/5xx でもヘッダ付与）
- `location` 内で `add_header` を使うと**親の add_header が継承されない罠**に注意
- 設定後は必ず `nginx -t` で構文確認 → `nginx -s reload`

## まずは現状を把握しましょう

ドメイン番人の [Web セキュリティヘッダ 単発チェック](/security-headers/check) で 30 秒で確認できます。設定支援が必要な場合は [Web セキュリティヘッダ診断＋設定支援](/contact)（3 万円〜）でご相談ください。

サーバー別の設定: [WordPress でセキュリティヘッダを設定する](/blog/wordpress-security-headers) / [Cloudflare でセキュリティヘッダを設定する](/blog/cloudflare-security-headers) も参照してください。

各ヘッダの個別解説:
- [CSP（Content-Security-Policy）とは](/blog/csp-content-security-policy-toha)
- [HSTS の設定方法](/blog/hsts-settei-houhou)
- [クリックジャッキング対策](/blog/x-frame-options-clickjacking)
- [Referrer-Policy のおすすめ設定値](/blog/referrer-policy-osusume)
- [Permissions-Policy とは](/blog/permissions-policy-toha)

総合点検は [無料のドメイン診断](/diagnose) を、SSL 単独は [SSL 単発チェック](/ssl/check) をご利用ください。
