セキュリティヘッダ 7 種の意味と設定
セキュリティヘッダは、たった数行で攻撃の成立条件を潰せる、費用対効果の非常に高い対策です。ただし設定例をそのまま貼ると、OAuth ログインや広告、流入分析が黙って壊れることがあります。7 種それぞれの効果と副作用を、実際に壊れる箇所まで含めて説明します。
セキュリティヘッダとは何ですか?
セキュリティヘッダは、サーバーがレスポンスに付けて「このページをどう扱ってほしいか」をブラウザに指示する HTTP ヘッダです。ブラウザ側で強制されるため、アプリのコードを 1 行も変えずに、クリックジャッキングや中間者攻撃といった攻撃の成立条件そのものを潰せます。
セキュリティ対策の多くはコードの修正を伴いますが、ヘッダはサーバー設定を数行足すだけで効きます。 しかも効果はブラウザ側で強制されるため、アプリのどこかにバグが残っていても関係なく発動します。 費用対効果でいえば、Web セキュリティの中で最も割のよい対策です。
一方で、「設定例をコピペで全部入れる」のは危険です。以下の 7 種は、防ぐものも、壊しうるものも違います。まずは全体像から。
7 種類のうち、どれから設定すべきですか?
副作用がほぼ無い nosniff・Referrer-Policy・X-Frame-Options から入れてください。この 3 つは数分で終わり、まず壊れません。次に HSTS、最後に CSP です。CSP と HSTS は効果が最も大きい一方で、設定を誤ったときの被害も最大なので、検証してから入れます。
| ヘッダ | 防ぐもの | 推奨値 | 壊すリスク |
|---|---|---|---|
X-Content-Type-Options | MIME スニッフィングによる XSS | nosniff | 低 |
Referrer-Policy | URL に載った情報の外部漏洩 | strict-origin-when-cross-origin | 低 |
X-Frame-Options | クリックジャッキング | SAMEORIGIN | 中 |
Permissions-Policy | 不要なブラウザ機能の悪用 | camera=(), microphone=() | 中 |
Cross-Origin-Opener-Policy | 別オリジンからのウィンドウ操作 | same-origin-allow-popups | 中 |
Strict-Transport-Security | SSL ストリッピング(盗聴・改ざん) | max-age=31536000 | 高 |
Content-Security-Policy | XSS(最大の効果) | nonce 方式 | 高 |
「壊すリスク」は設定を誤ったときに利用者から見えなくなる範囲の大きさです。 上から順に入れていけば、事故の確率を抑えながら着実に固められます。
nosniff(X-Content-Type-Options)は何を防ぎますか?
ブラウザが Content-Type を無視して中身から型を推測する挙動(MIME スニッフィング)を止めます。これが無いと、text/plain として保存したはずのユーザー投稿ファイルが HTML と解釈され、スクリプトが実行されることがあります。副作用がほぼ無いので、無条件で付けて構いません。
具体的な事故はこうです。ユーザーのアップロードを Content-Type: text/plain で配信していたとして、中身が <html><script>… で始まっていたとします。nosniff が無いブラウザは「宣言は text/plain だが、中身は HTML に見える」と判断し、HTML として解釈してスクリプトを実行します。これは自サイトのオリジン上で動くので、Cookie もセッションも読めます。
nosniff を付ければ、ブラウザは宣言された型を無条件で信じます。副作用はContent-Type を間違えている既存ファイルが読めなくなることだけで、これは本来直すべきバグです。迷わず付けて構いません。
Referrer-Policy を厳しくすると、流入元の分析が壊れませんか?
no-referrer にすると壊れます。参照元が全て「direct」になり、流入分析も広告のコンバージョン計測も機能しなくなります。推奨は strict-origin-when-cross-origin です。外部にはドメイン名までしか送らないのでパスは漏れず、分析に必要な参照元は届くという両立点になります。
そもそも、なぜ参照元を絞るのか。URL 自体が秘密を含んでいることがあるからです。 パスワード再設定用のトークン、社内ツールの案件 ID、サイト内検索の検索語などが URL に入っていると、 ユーザーが外部リンクを 1 回クリックしただけで、その URL がまるごと相手先のアクセスログに残ります。
| 値 | 外部サイトに送られるもの | 評価 |
|---|---|---|
no-referrer | 何も送らない | 非推奨。流入分析とコンバージョン計測が壊れる |
strict-origin-when-cross-origin | ドメイン名のみ(例: https://example.com/)。同一サイト内は全 URL | 推奨。主要ブラウザの既定値でもある |
origin-when-cross-origin | ドメイン名のみ。ただし HTTPS → HTTP でも送る | strict 版を使うべき |
unsafe-url | 常に全 URL | 危険。名前のとおり |
strict-origin-when-cross-origin なら、Google Analytics には「どのドメインから来たか」が届くので流入分析は従来どおり機能します。失うのは相手先に渡る「どのページから来たか」の詳細だけです。 セキュリティのために計測を捨てる必要はありません。
クリックジャッキング対策は X-Frame-Options と frame-ancestors のどちらを使うべきですか?
両方書いてください。仕様上は CSP の frame-ancestors が後継で、こちらが優先されます。ただし X-Frame-Options しか見ない古い環境が残っているため、両方書くのが実務上は無難です。値が矛盾していると事故るので、必ず同じ意味に揃えます。
クリックジャッキングは、攻撃者のページに自サイトを透明な iframe で重ね、利用者が「無害なボタン」だと思って押したクリックを、自サイト上の実際の操作に横流しする攻撃です。 「退会」「送金」「権限付与」のような 1 クリックで完了する操作があるサイトは要注意です。
X-Frame-Options: DENY— どこにも埋め込ませないX-Frame-Options: SAMEORIGIN— 自サイト内の iframe だけ許可。通常はこれで十分Content-Security-Policy: frame-ancestors 'self' https://partner.example.com— 提携先を個別に許可できる。複数ドメインを指定できるのは frame-ancestors だけ(X-Frame-Options の ALLOW-FROM は廃止済み)
埋め込みを許可する提携先がある場合は、X-Frame-Options では表現できないので frame-ancestors が必須になります。CSP 側の詳細は CSP(Content-Security-Policy)の導入手順 にまとめています。
HSTS は何を防ぎますか?
HSTS は「このドメインには必ず HTTPS で接続する」ようブラウザに記憶させ、http:// でのアクセスをブラウザ内部で https:// に置き換えさせます。これにより、最初の 1 回の http アクセスを乗っ取って盗聴する SSL ストリッピング攻撃を防げます。リダイレクト 1 回分だけ表示も速くなります。
HTTPS へリダイレクトしていれば十分に見えますが、その最初の 1 回の http リクエストが狙われます。 公衆 Wi-Fi などで通信経路に割り込まれると、リダイレクトを握り潰して http のまま偽サイトへ誘導できてしまいます。HSTS は2 回目以降、ブラウザが http を一切送らなくなるようにする仕組みです。
includeSubDomains と preload の注意
includeSubDomains を付けると、すべてのサブドメインが HTTPS 必須になります。 社内ツールや古い検証環境が http で動いていると、その日から到達不能になります。付ける前にサブドメインを棚卸ししてください。
なお、導入時は max-age=300(5 分)程度から始め、問題が出ないことを確認してから 31536000(1 年)へ伸ばすと安全です。 長い max-age を配ってしまうと、取り消しにその期間ぶん待たされます。
Permissions-Policy は何を止めますか?
カメラ・マイク・位置情報・決済など、ブラウザ機能の利用可否をページ単位で宣言します。使っていない機能を空リストで無効化しておけば、万一スクリプトを注入されても、そこから権限プロンプトを出すこと自体ができなくなります。埋め込んだ iframe にも効きます。
書き方は 機能名=(許可するオリジン) です。() は空リスト、つまりどこにも許可しないという意味になります。 自サイトでのみ使うなら camera=(self) と書きます。
camera=(), microphone=(), geolocation=()— 使っていないなら止める。注入されたスクリプトが権限を要求すること自体を封じられるpayment=(), usb=(), serial=()— 一般的な Web サイトではまず使わないinterest-cohort=()— 今書いても意味がありません。対象だった FLoC は提案自体が撤回済みです。 古い設定例に残っているだけなので、コピペしないでください
COOP は何のために必要ですか?
COOP(Cross-Origin-Opener-Policy)は、window.open で開いた先や開き元との参照関係を切り、別オリジンのページから自ページのウィンドウを操作されるのを防ぎます。ただし same-origin にすると OAuth のポップアップログインが壊れるため、通常は same-origin-allow-popups を選びます。
window.open() で開かれたページは、既定では開き元を window.opener 経由で参照でき、開き元の URL を書き換えることすらできます(タブナビング)。COOP はこの参照を切ります。
same-origin-allow-popups— 自分が開いたポップアップとの参照は残す。OAuth ログインや決済ポップアップを使うなら実質これ一択same-origin— 最も厳格。ただしGoogle ログイン等のポップアップ認証が壊れます。 SharedArrayBuffer が必要な場合(COEP と併用)以外は避けるunsafe-none— 既定値。何もしない
関連ヘッダとして COEP(Cross-Origin-Embedder-Policy)と CORP(Cross-Origin-Resource-Policy)もありますが、COEP は外部リソースの読み込みを軒並み壊す破壊力があり、SharedArrayBuffer を必要とする一部のアプリ以外では見合いません。 通常のサイトは COOP だけで十分です。
まとめて設定するにはどう書きますか?
nginx なら add_header、Caddy なら header ディレクティブ、Next.js なら next.config.ts の headers() に一括で書けます。いずれも設定後は curl -I で実際に返っているヘッダを必ず確認してください。書いたつもりで返っていない、が最も多い失敗です。
nginx
# 7 種まとめて。server ブロックに置く
# ★ 子の location で add_header を 1 つでも書くと、ここの指定は全部消える(追加でなく置換)
# 1. HSTS — HTTPS を記憶させる。preload は付けていない(本文の警告を参照)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 2. CSP — 詳細は /guide/csp。まずは Report-Only から
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
# 3. クリックジャッキング — 旧仕様。CSP の frame-ancestors と意味を揃えること
add_header X-Frame-Options "SAMEORIGIN" always;
# 4. MIME スニッフィング停止。副作用ほぼ無し
add_header X-Content-Type-Options "nosniff" always;
# 5. 参照元 — 外部にはドメインまで。分析は壊れない
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 6. 使わないブラウザ機能を無効化
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
# 7. COOP — same-origin だと OAuth ポップアップが壊れる。allow-popups にする
add_header Cross-Origin-Opener-Policy "same-origin-allow-popups" always;
# おまけ: バージョン露出を消す(攻撃者に既知の脆弱性を教えない)
server_tokens off;
proxy_hide_header X-Powered-By;Caddy
# Caddy は HTTPS 化とリダイレクトを自動でやるので HSTS と相性がよい
example.com {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
Cross-Origin-Opener-Policy "same-origin-allow-popups"
Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
# 露出を消す
-Server
-X-Powered-By
}
reverse_proxy localhost:3000
}Next.js(リバースプロキシを触れない場合)
// リバースプロキシを触れない場合はアプリ側で完結させる
import type { NextConfig } from "next";
const securityHeaders = [
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=()" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin-allow-popups" },
// X-XSS-Protection は **書かない**(本文参照)。書くなら "0"
];
const nextConfig: NextConfig = {
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
export default nextConfig;設定後は必ず実物を確認する
# 書いたつもりで返っていない、が一番多い失敗。必ず実物を見る
curl -sI https://example.com | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy|cross-origin'
# リダイレクト先のヘッダも確認する
# (HSTS は https のレスポンスに付いていないと意味が無い)
curl -sIL http://example.com | grep -i strict-transportリバースプロキシ構成では、アプリとプロキシの両方がヘッダを付けて重複することがあります。 同じヘッダが 2 回返るとブラウザの挙動が不定になるため、どちらか一方に寄せてください。 実務上はプロキシ側(nginx / Caddy)に集約するのが管理しやすい構成です。
セキュリティヘッダは SEO に悪影響がありますか?
適切に設定する限り悪影響はなく、HTTPS 強制はむしろ有利に働きます。悪影響が出るのは設定を誤ったときだけです。特に HSTS preload の登録ミスはサイト全体が表示不能になり、検索流入が消えます。効果とリスクを理解してから入れてください。
検索エンジンのクローラーは、ここで挙げたヘッダをほぼ無視します(クローラーにはブラウザのような実行環境が無いためです)。 つまりセキュリティヘッダが直接ランキングを上げ下げすることはありません。影響が出るのは間接的な経路だけです。
- HSTS のリダイレクト削減 — 1 ホップぶん速くなる。速度は評価要素なのでわずかに有利
- HSTS preload の事故 — HTTPS 化できていないサブドメインが全滅し、そのページ群が検索結果から消える。これが最大のリスク
- CSP の設定ミス — 広告・解析が止まる。順位ではなく収益とデータが失われる
ヘッダの正確な仕様は MDN の HTTP ヘッダーリファレンス、設定の指針は OWASP の HTTP Headers Cheat Sheet が一次情報として信頼できます。ページ速度側の話は Core Web Vitals — LCP・INP・CLS の改善 を参照してください。