本文へスキップ
サイト総合診断

セキュリティヘッダ 7 種の意味と設定

セキュリティヘッダは、たった数行で攻撃の成立条件を潰せる、費用対効果の非常に高い対策です。ただし設定例をそのまま貼ると、OAuth ログインや広告、流入分析が黙って壊れることがあります。7 種それぞれの効果と副作用を、実際に壊れる箇所まで含めて説明します。

執筆: 公開: 更新:

セキュリティヘッダとは何ですか?

セキュリティヘッダは、サーバーがレスポンスに付けて「このページをどう扱ってほしいか」をブラウザに指示する HTTP ヘッダです。ブラウザ側で強制されるため、アプリのコードを 1 行も変えずに、クリックジャッキングや中間者攻撃といった攻撃の成立条件そのものを潰せます。

セキュリティ対策の多くはコードの修正を伴いますが、ヘッダはサーバー設定を数行足すだけで効きます。 しかも効果はブラウザ側で強制されるため、アプリのどこかにバグが残っていても関係なく発動します。 費用対効果でいえば、Web セキュリティの中で最も割のよい対策です。

一方で、「設定例をコピペで全部入れる」のは危険です。以下の 7 種は、防ぐものも、壊しうるものも違います。まずは全体像から。

7 種類のうち、どれから設定すべきですか?

副作用がほぼ無い nosniff と Referrer-Policy から入れ、次に X-Frame-Options です。X-Frame-Options は他サイトへの埋め込みを使っていなければ壊れません。その次に HSTS、最後に CSP です。CSP と HSTS は効果が最も大きい一方で、設定を誤ったときの被害も最大なので、検証してから入れます。

セキュリティヘッダ 7 種の比較(導入リスクの低い順)
ヘッダ防ぐもの推奨値壊すリスク
X-Content-Type-OptionsMIME スニッフィングによる XSSnosniff低
Referrer-PolicyURL に載った情報の外部漏洩strict-origin-when-cross-origin低
X-Frame-OptionsクリックジャッキングSAMEORIGIN中
Permissions-Policy不要なブラウザ機能の悪用camera=(), microphone=()中
Cross-Origin-Opener-Policy別オリジンからのウィンドウ操作same-origin-allow-popups中
Strict-Transport-SecuritySSL ストリッピング(盗聴・改ざん)max-age=31536000高
Content-Security-PolicyXSS(最大の効果)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 にすると遷移先の流入分析で自サイト経由の訪問が見えなくなり、提携先の成果計測に響くこともあります。推奨はブラウザ既定値でもある strict-origin-when-cross-origin で、パスは漏らさずドメイン名だけを渡せます。

そもそも、なぜ参照元を絞るのか。URL 自体が秘密を含んでいることがあるからです。 パスワード再設定用のトークン、社内ツールの案件 ID、サイト内検索の検索語などが URL に入っていると、 ユーザーが外部リンクを 1 回クリックしただけで、その URL がまるごと相手先のアクセスログに残ります。

Referrer-Policy の主な値と挙動
値外部サイトに送られるもの評価
no-referrer何も送らない非推奨。遷移先の流入分析や提携先の成果計測で、自サイト経由の訪問が見えなくなる
strict-origin-when-cross-originドメイン名のみ(例: https://example.com/)。同一サイト内は全 URL推奨。主要ブラウザの既定値でもある
origin-when-cross-originドメイン名のみ。ただし HTTPS → HTTP でも送るstrict 版を使うべき
unsafe-url常に全 URL危険。名前のとおり

誤解されやすいのですが、Referrer-Policy が制御するのは自サイトのページが外部へ送る参照元です。 自サイトの Google Analytics に届く参照元は、訪問者が直前にいたサイトの設定で決まるので、ここをどう設定しても 自サイトの流入分析は変わりません。Google 広告のコンバージョン計測も、参照元ではなく URL の gclid パラメータを使います。

no-referrer で困るのはリンク先の相手です。相手の分析では自サイト経由の訪問が「直接」扱いになり、 参照元で成果を判定する提携先やアフィリエイトの計測にも影響しえます。strict-origin-when-cross-origin(ブラウザの既定値)なら 相手に渡るのはドメイン名だけで、パスに含まれる秘密は漏れません。保護と計測を両立できる値です。

クリックジャッキング対策は 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:// に置き換えさせます。一度 HTTPS で訪問した後は http 通信そのものが発生しないため、リダイレクト前の http を乗っ取る SSL ストリッピング攻撃を防げます。ただし初回訪問は守れず、それを塞げるのは preload だけです。

HTTPS へリダイレクトしていれば十分に見えますが、その最初の 1 回の http リクエストが狙われます。 公衆 Wi-Fi などで通信経路に割り込まれると、リダイレクトを握り潰して http のまま偽サイトへ誘導できてしまいます。HSTS は2 回目以降、ブラウザが http を一切送らなくなるようにする仕組みです。裏を返せば、HSTS をまだ受け取っていない 初回訪問は守れません。初回まで守るには、後述の preload でブラウザに最初から HTTPS 専用だと覚えさせる必要があります。

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

/etc/nginx/conf.d/security.conf
# 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(違反を報告するだけで遮断しない)から
#    違反が出なくなったら、nonce 方式に整えて Content-Security-Policy に切り替える
add_header Content-Security-Policy-Report-Only "default-src 'self'; 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

Caddyfile
# 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"
		# CSP はまず Report-Only で違反を洗い出す(強制に切り替えるのは nonce を整えてから)
		Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"

		# 露出を消す
		-Server
		-X-Powered-By
	}
	reverse_proxy localhost:3000
}

Next.js(リバースプロキシを触れない場合)

next.config.ts
// リバースプロキシを触れない場合はアプリ側で完結させる
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"
  // CSP はリクエストごとに nonce を変える必要があるため、ここではなく /guide/csp の方法で付ける
];

const nextConfig: NextConfig = {
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};

export default nextConfig;

nginx と Caddy の例では、CSP を Content-Security-Policy-Report-Only で出しています。このヘッダは違反を 開発者ツールのコンソール(report-to を指定すればその収集先)に報告するだけで、何も遮断しません。default-src 'self' をいきなり強制すると、Next.js のようにインラインスクリプトを使うアプリは動かなくなります。 違反を洗い出し、nonce 方式などで許可を整えてから Content-Security-Policy に切り替えてください。 手順は CSP(Content-Security-Policy)の導入手順 にまとめています。

設定後は必ず実物を確認する

確認コマンド
# 書いたつもりで返っていない、が一番多い失敗。必ず実物を見る
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 の登録ミスはサイト全体が表示不能になり、検索流入が消えます。効果とリスクを理解してから入れてください。

Googlebot は最新版の Chromium でページを描画しますが、ここで挙げたヘッダはランキングのシグナルではありません。 Google が順位のシグナルとして挙げているのは HTTPS 化そのものであり、個々のヘッダの有無ではありません。 つまりセキュリティヘッダが直接ランキングを上げ下げすることはありません。影響が出るのは間接的な経路だけです。

  • HSTS のリダイレクト削減 — 1 ホップぶん速くなる。速度は評価要素なのでわずかに有利
  • HSTS preload の事故 — HTTPS 化できていないサブドメインが全滅し、そのページ群が検索結果から消える。これが最大のリスク
  • CSP の設定ミス — 広告・解析が止まる。自サイトのスクリプトまで止めると、描画に JS が要るページは中身が表示されない

ヘッダの正確な仕様は MDN の HTTP ヘッダーリファレンス、設定の指針は OWASP の HTTP Headers Cheat Sheet が一次情報として信頼できます。ページ速度側の話は Core Web Vitals — LCP・INP・CLS の改善 を参照してください。

参考にした一次情報

関連する解説

自分のサイトは、実際どうなっているか

この記事で説明した項目は、すべて無料の診断ツールで実測できます。URL を入れるだけ・登録不要です。

無料で診断する