サイトが返すレスポンスには、訪問者の目に触れないヘッダーが付いています。その一部はブラウザへのセキュリティ上の指示です。スクリプトはここからだけ読み込む、このホストにはHTTPSで接続する、ほかのサイトにこのページをフレーム表示させない、このCookieはJavaScriptから読ませない、といった内容です。ヘッダーがなければ、ブラウザはより緩い既定の動作に戻ります。値を誤れば、ログインや決済の画面が動かなくなることもあります。
この記事では、サイトが実際に送っているヘッダーの読み方、各ヘッダーの役割、最初に設定しても問題の起きにくい値を説明します。nginx、Apacheの.htaccess、Cloudflare PagesやNetlifyのような静的ホスティング向けの基本設定と、一部のレスポンスだけヘッダーが抜けてしまう典型的なミスも扱います。例のドメインはすべてexample.comです。
このガイドの対象範囲
無料の公開準備スナップショットが読むのは1つのレスポンスのヘッダーです。入力したオリジンのトップページについて、最大5回のリダイレクトをたどった後の最終レスポンスを見ます。主にヘッダーの有無を確認するもので、値がサイトに合っているかまでは判断せず、ペネトレーションテストでもありません。
1. まず自分で確認する:開発者ツールとcurl
ChromeやEdgeでは開発者ツールを開き、ネットワークパネルを選んでページを再読み込みします。最初のリクエスト(HTMLドキュメント)をクリックし、ヘッダータブのレスポンス ヘッダーまでスクロールします。ログを保持にチェックを入れるとページ遷移やリダイレクトをまたいでリクエストが残り、キャッシュを無効化にチェックを入れるとキャッシュではなく新しいレスポンスを取得できます(Chrome DevToolsのネットワーク リファレンス)。Firefoxのネットワークモニターも同じ要領で使えます。
ターミナルからはcurlで、サーバーが送っている内容をそのまま表示できます。
curl -sS -D - -o /dev/null https://example.com/-Lを付けるとリダイレクトをたどり、途中のレスポンスごとのヘッダーも表示されます。Windowsではcurl.exeと明示して呼び出し、-o NULを使います。curl -Iは短く書けますが、GETではなくHEADリクエストを送ります。アプリケーションによってはHEADを別の処理で返すため、見つかった結果はGETでも確かめてください。
同じ確認を、http://のアドレス(HTTPSへの301または308のリダイレクトが返るはずです)、もう一方のホスト名(wwwありとなし)、存在しないページ、ログインページ、APIのレスポンス、静的ファイルでも繰り返します。アプリケーション、Webサーバー、CDNがそれぞれ別のヘッダーを付けていることは珍しくありません。意味を持つのは、ブラウザに実際に届いたものだけです。
2. 各ヘッダーの役割と、最初に設定する安全な値
| ヘッダー | 役割 | 最初に設定する値 |
|---|---|---|
| Content-Security-Policy | スクリプト、スタイル、フレームなどを読み込める取得元を制限する | ポリシー案を、まずContent-Security-Policy-Report-Onlyとして送る |
| CSP frame-ancestors | ページをフレームに埋め込めるサイトを決める(クリックジャッキング対策) | frame-ancestors 'self'または'none' |
| X-Frame-Options | 古いブラウザ向けに残しておく、従来のフレーム制御 | frame-ancestorsに揃えてSAMEORIGINまたはDENY |
| Strict-Transport-Security | max-age秒のあいだ、このホストにはHTTPSだけで接続させる | max-age=300から段階的に延ばす |
| X-Content-Type-Options | MIMEタイプの推測を止め、誤ったタイプで届いたスクリプトとスタイルシートをブロックする | nosniff |
| Referrer-Policy | 他サイトに送るRefererヘッダーに、ページのURLをどこまで含めるかを決める | strict-origin-when-cross-origin |
| Permissions-Policy | カメラなどのブラウザ機能を、ページとその中のフレームで無効にする | camera=(), microphone=(), geolocation=() |
| Cross-Origin-Opener-Policy | 別オリジンのポップアップや、自分を開いたページからウィンドウを切り離す | same-origin。OAuthや決済のポップアップを使う場合はsame-origin-allow-popups |
| Set-Cookieの属性 | セッションCookieの送られ方と、読み取れる範囲を制限する | Secure; HttpOnly; SameSite=Lax |
| X-XSS-Protection | 古いブラウザのフィルターを制御していた。現在は非推奨 | 削除するか、0を送る |
nosniffを送ると、ブラウザは宣言されたContent-Typeを信頼し、タイプが誤っているスクリプトやスタイルシートをブロックします。有効にする前に、各ファイルのタイプが正しいか確認してください。Referrer-Policyヘッダーがなくても、現在のブラウザはstrict-origin-when-cross-originを既定にしています。OWASPは古いブラウザのために明示的に送ることを勧めており、リファラーを必要とする処理がなければno-referrerのほうが厳格です。Permissions-Policyの()は、ページとその中のすべてのフレームで機能を無効にします。機能が必要なウィジェットがあれば、そのオリジンにだけ許可します。COOPのsame-originはクロスオリジンの情報漏えい攻撃(XS-Leaks)への対策になりますが、別オリジンから開くログインや決済のポップアップを壊すことがあります。
3. Content-Security-Policyとframe-ancestors
CSPは、ページがスクリプト、スタイル、画像、フレーム、通信先をどこから読み込めるかをブラウザに伝えます。注入されたスクリプトにできることを制限する仕組みであり、入力値のエスケープや検証の代わりにはなりません。MDNのCSPガイドは、許可するホストを長く並べるより、レスポンスごとに新しく生成するnonce、またはハッシュを使う厳格なCSPを勧めています。'unsafe-inline'を入れるとCSPの効果の多くが失われ、nonceやハッシュを含むディレクティブではブラウザに無視されます。厳格なポリシーを難しくしているのは、たいていタグマネージャーやチャットウィジェットのように、さらに別のスクリプトを読み込むサードパーティのコードです。例外を追加するたびに、その理由を記録しておきましょう。
default-srcはscript-srcなどの取得系ディレクティブのフォールバックになりますが、frame-ancestorsには効きません。default-src 'none'だけのポリシーでは、どのサイトからでもページをフレームに埋め込めてしまいます。frame-ancestors 'self'、またはX-Frame-Options: DENYに近い'none'を明示してください(MDN)。OWASP HTTP Headers Cheat Sheetによれば、対応ブラウザではframe-ancestorsがX-Frame-Optionsに取って代わり、X-Frame-Optionsは古いブラウザ向けに引き続き役立ちます。両方を送るなら、許可する埋め込みの範囲を揃えてください。落とし穴が二つあります。frame-ancestors、Report-Onlyのポリシー、X-Frame-Optionsは<meta>タグでは機能しません。またX-Frame-Options: ALLOW-FROMを指定すると、現在のブラウザはヘッダー全体を無視します。
サードパーティのスクリプトを使わないサイトなら、OWASP Secure Headers Projectの推奨ポリシーを最初の案にできます。
default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requestsこのポリシーはインラインのスクリプトとスタイル、他ホストからの読み込みをすべてブロックするので、最初は大量の違反報告が出ると考えてください。だからこそReport-Onlyから始めます(手順は8を参照)。使うのはCSPの行だけにします。このプロジェクトの推奨セット全体にはCache-Control: no-storeやClear-Site-Dataも含まれ、そのまま使うとすべてのレスポンスでキャッシュが無効になり、訪問者のCookieや保存データが消去されます。ブラウザが描画しないJSONのAPIレスポンスでは、CSPの効果はほとんどありません。
4. HSTS:max-ageは段階的に延ばし、preloadは慎重に
Strict-Transport-Securityは、そのホストにはHTTPSだけで接続し、以降のhttp://へのリクエストを自動でHTTPSに切り替えるようブラウザに指示します。平文のHTTPで届いたこのヘッダーはブラウザに無視されるため、HTTPからHTTPSへのリダイレクトではなく、HTTPSのレスポンスに付けます。このヘッダーだけでは、訪問者の最初の接続は守れません(MDN)。
HSTSには副作用もあります。HSTSが有効なホストでは、訪問者は証明書エラーの警告を越えて先へ進めません。証明書が期限切れになると、以前に訪れたことのある利用者はサイトを開けなくなります。2026年3月15日以降に発行される、公的に信頼されたTLS証明書の有効期間は最長200日になり(CA/Browser Forum)、Let's Encryptは有効期限の通知メールを終了しています。先に更新の自動化と、自前の有効期限の監視を整えてください。証明書についてはTLSとセキュリティヘッダーの解説で扱っています。
hstspreload.orgが勧めるとおり、max-age(秒)は段階的に延ばし、段階ごとに不具合がないか確かめます。300(5分)、604800(1週間)、2592000(1か月)、そして31536000(1年)か、OWASPが使う63072000(2年)です。max-age=0でポリシーを取り消せますが、HTTPSで送った場合に限られ、効くのは再訪してそれを受け取った訪問者だけです。includeSubDomainsは、すべてのサブドメインがHTTPSで動くようになってから追加し、各サブドメインにもそれぞれヘッダーを送らせます。
プリロード(preload)は、ドメインをブラウザに組み込み、初回の訪問からHTTPSを使わせる仕組みです。申請先のhstspreload.orgは現在、HSTSは勧める一方でプリロードは勧めていません。ChromeとSafariはすでにHTTPのページ遷移をHTTPSに切り替えているため上乗せの効果は小さく、リストからの削除が利用者に届くまで数か月かかるからです。OWASP HTTP Headers Cheat Sheetの例には今もpreloadが含まれていますが、OWASP Secure Headers Projectの推奨値には含まれていません。初期設定として付けたり、テンプレートからそのままコピーしたりしないでください。
6. 外してよいヘッダー:X-XSS-Protectionなどの名残
- X-XSS-Protectionは古いブラウザのフィルターを有効にするものでしたが、そのフィルター自体がXSSの脆弱性を生むことがありました(MDN)。OWASPは、設定しないか
0で無効にするよう勧めています。代わりになるのはCSPです。 - Expect-CT:OWASPは使わないよう勧め、Mozillaの見解を引いて既存の設定からも削除するよう勧めています。
- Public-Key-Pins(HPKP)は2018年にChromiumから削除され、現在のブラウザはどれも対応していません。
- Feature-PolicyはPermissions-Policyに置き換えられました。
ServerやX-Powered-Byは、使っているソフトウェアを、ときにはバージョン番号付きで示します。OWASPは削除するか情報を含まない値にするよう勧めていますが、OWASP自身のテストガイドはこれを「隠蔽によるセキュリティ」(security through obscurity)と呼び、MDNもソフトウェアを最新に保つほうが重要だとしています。バージョン番号は消し(nginxのserver_tokens off;が消すのはヘッダーではなくバージョンです)、労力はアップデートに回しましょう。
7. nginx・Apache・静的ホスティングの基本設定
次のnginxの基本設定は、リスクの低いヘッダーを設定し、HSTSとCSPを試験段階で始め、同じホストのままHTTPをHTTPSへリダイレクトします。alwaysパラメーターを付けると、エラーレスポンスにもヘッダーが付きます。
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ssl_certificate と ssl_certificate_key をここに記述
server_tokens off;
add_header Strict-Transport-Security "max-age=300" 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=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'" always;
}ヘッダーが抜ける原因の多くは、nginxのheadersモジュールに書かれている二つの仕様で説明できます。alwaysがないと、add_headerはステータスコード200、201、204、206、301、302、303、304、307、308にしか適用されず、404や500のページはヘッダーなしで返ります。またadd_headerは、その階層に一つも定義がない場合に限って上位の階層から継承されます。locationブロックにadd_headerを一つ書くだけで、そこではサーバー階層のセキュリティヘッダーがすべて消えます。ヘッダーを繰り返し書くか、共通ファイルをincludeするか、nginx 1.29.3以降ならadd_header_inherit merge;を使います。
Apache(.htaccess)
Apacheではmod_headersのHeaderディレクティブで設定します。サーバーの設定ファイルのほか、上書きが許可されていれば.htaccessにも書けます。
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy-Report-Only "default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'"
</IfModule>条件を省いたHeader setは、既定のonsuccessの内部テーブルに対して働きます。alwaysを付けると、エラーのレスポンスにもヘッダーが付き、内部リダイレクト(ErrorDocumentなど)の後にも残ります。この二つのテーブルは別々に管理されているため、同じヘッダーを両方で設定したり、バックエンドのアプリケーション(mod_proxy_fcgi経由のPHPなど)も同じヘッダーを送っていたりすると、ヘッダーが重複することがあります。Apacheのドキュメントのほか、OWASP HTTP Headers Cheat Sheetもこの点を指摘しています。<IfModule>で囲むとモジュールが無効なサーバーでもエラーにはなりませんが、その場合ヘッダーは一つも付きません。設定後は必ずcurlで結果を確かめてください。
静的ホスティング(_headersファイル)
Cloudflare PagesやNetlifyのような静的ホスティングは、サイトと一緒にデプロイした_headersファイルを読みます。次の例はCloudflareのドキュメントの書式に沿っており、Netlifyも同じ書式を使います。
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: SAMEORIGIN
Content-Security-Policy-Report-Only: default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'制限も把握しておきます。Cloudflare PagesはPages Functionsが生成するレスポンスにはこのファイルを適用せず、ヘッダーのルールは100件まで、1行は2,000文字までで、同じヘッダーが二重に適用されると値をカンマでつなぎます。Netlifyがカスタムヘッダーを付けるのは自身が配信するファイルだけで、プロキシしたコンテンツ、Functions、Edge Functions、サーバーサイドでレンダリングしたページには付きません。それらのレスポンスでは、それぞれ自分でヘッダーを設定する必要があります。HSTSはこの例から外しています。プラットフォームがすでに何を送っているかをcurlで確認してから追加してください。
8. 段階的な導入手順とよくあるミス
- nosniff、Referrer-Policy、Permissions-Policy、フレーム埋め込みの保護を追加し、ログイン、決済、埋め込みウィジェットの動作を試します。
- ポリシー案を
Content-Security-Policy-Report-Onlyで送ります。何もブロックされず、違反はブラウザのコンソールに表示されます。ポリシーで報告先を指定していれば、そこにも届きます。ログインが必要なページを含めて主要な操作の流れをひととおりたどり、ポリシーを調整します。 - HSTSを短い
max-ageで始め、段階的に延ばします。 - CSPを強制モードに切り替えます。次に締める内容を試すため、より厳しいReport-Onlyのポリシーを並行して送ることもできます。
- サーバー、CDN、フレームワークを変更するたびに、curlでの確認をやり直します。
よくあるミス:
- 一部のレスポンスにしかヘッダーがない。トップページには付いていても、404ページ、API、静的ファイル、別系統で配信しているログインページには付いていない。
- 一度のリダイレクトでホストとスキームを同時に変える。
http://example.comからhttps://www.example.comへ直接リダイレクトすると、ブラウザはexample.comのHSTSを一度も受け取りません。まず同じホストのままHTTPSへリダイレクトし、そのHTTPSのレスポンスでHSTSを送ってから、wwwへリダイレクトします。 - CDNがオリジンの設定を上書きする。CDNやプロキシは、ヘッダーを追加、置換、削除、重複させることがあります。ヘッダーごとにどの層で設定するかを決め、curlで結果を確かめてください。
- 何でも許可するCSP。エラーを消すために
'unsafe-inline'、'unsafe-eval'、*を詰め込んだポリシーは、ヘッダーの有無のチェックは通っても、ほとんど何もブロックしません。 - includeSubDomainsが早すぎる。HTTPでしか動かないサブドメインを忘れていると、ポリシーを記憶したブラウザからはそのサブドメインに接続できなくなります。
ヘッダーは対策の一層にすぎません。Webサイト公開前チェックリストではDNS、TLS、リダイレクトと並べて確認でき、Webセキュリティ監査のガイドではログイン、権限、繰り返し使える点検の手順を扱っています。アプリケーション側の対策は、IPAの「安全なウェブサイトの作り方」がWebサイトの開発者・運営者向けに日本語でまとめています。
9. 無料の公開準備スナップショットはヘッダーをどう確認するか
Sitelemetryの無料の公開準備スナップショットは、登録なしで1つの公開オリジンに8項目のパッシブなチェックを行い、その一つがHTTPセキュリティヘッダーの確認です。ペネトレーションテストではなく、6領域の監査でもありません。
- 入力したオリジンのトップページだけをリクエストし(パスとクエリ文字列は破棄されます)、最大5回のリダイレクトをたどった最終レスポンスで判定します。ほかのページはリクエストしません。
- 見るのは主にヘッダーの有無です。CSPがない場合、HTTPSの対象でHSTSがない場合、フレーム埋め込みの保護がない場合、
Access-Control-Allow-Origin: *がある場合は、重要度「中」と判定されます。X-Content-Type-Optionsがnosniffでない場合、Referrer-Policyがない場合、ServerまたはX-Powered-Byヘッダーがある場合は「低」です。そのため、バージョン番号のないServer: nginxでも「低」のシグナルになります。そのレスポンスで設定されたCookieにHttpOnlyがない場合、またはHTTPSでSecureがない場合は「中」で、SameSiteは確認しません。 - これとは別に、送られているヘッダーとTLSの結果をまとめて採点する堅牢化グレード(AからF)があります。CSPが
'unsafe-inline'(スクリプトでは'unsafe-eval'も)を許可していると、CSPの配点は小さくなります。Permissions-PolicyとCOOPが反映されるのは、このグレードだけです。 - スナップショットの「リダイレクト状態」のチェックは、入力の仕方で内容が変わります。ドメイン名だけ、または
https://のアドレスを入力した場合は、HSTSのmax-ageが180日未満なら「低」として示し(段階的に延ばしている途中なら想定どおりの結果です)、ページのHTMLにhttp://の参照(混在コンテンツ)がないかを調べます。HTTPからHTTPSへのリダイレクト自体はテストしません。リダイレクトをテストするのはhttp://のアドレスを入力したときだけで、その場合はTLSのチェックが省かれ、HSTSがないことも報告されません。
結果には、スコア、堅牢化グレード、リスクシグナルと合格チェックの数、最大3件のシグナル(リスクが先)が表示されます。すべてのヘッダーとその値の一覧は出ないので、値を確かめたいときや、スナップショットがリクエストしないレスポンスを確認するときは、開発者ツールかcurlを使ってください。
よくある質問
自分のサイトのセキュリティヘッダーはどう確認すればよいですか?
ブラウザの開発者ツールでネットワークパネルを開き、ページを再読み込みしてHTMLドキュメントを選び、レスポンスヘッダーを読みます。ターミナルなら curl -sS -D - -o /dev/null https://example.com/ で表示できます。http://のアドレス、404ページ、APIのレスポンス、静的ファイルもあわせて確認してください。これらはヘッダーが異なることがよくあります。
どのセキュリティヘッダーから設定すればよいですか?
不具合を起こしにくいX-Content-Type-Options: nosniff、Referrer-Policy、Permissions-Policy、フレーム埋め込みの保護から始めます。それでも設定後はスクリプト、ログイン、埋め込みウィジェットの動作を確かめてください。次にContent-Security-PolicyをReport-Onlyで試し、HSTSのmax-ageを段階的に延ばします。
X-XSS-Protectionはまだ送るべきですか?
いいえ。削除するかX-XSS-Protection: 0を送り、代わりにContent-Security-Policyを使います。このヘッダーが制御していた古いブラウザのフィルターは、それ自体がXSSの脆弱性を生むことがありました。
frame-ancestorsを使っていればX-Frame-Optionsは不要ですか?
対応ブラウザでは、CSPのframe-ancestorsディレクティブがX-Frame-Optionsに取って代わります。両方で許可する範囲が揃っていれば、古いブラウザ向けにX-Frame-Options: DENYまたはSAMEORIGINを残しておくのは妥当です。ALLOW-FROMは使わないでください。現在のブラウザはこの値を見るとヘッダー全体を無視します。
HSTSのプリロードリストに登録すべきですか?
通常は不要です。申請先のhstspreload.orgは現在、HSTSは勧める一方でプリロードは勧めていません。ChromeとSafariがすでにHTTPのページ遷移をHTTPSに切り替えているためです。リストからの削除が利用者に届くまで数か月かかる点にも注意してください。
セキュリティヘッダーを設定すればサイトは安全になりますか?
ヘッダーだけでは安全になりません。注入されたスクリプト、クリックジャッキング、プロトコルのダウングレード、Cookieの盗用による被害を抑えるものであり、脆弱なコード、古いソフトウェア、弱いアクセス制御は直せません。
出典・参考資料
- MDN:コンテンツセキュリティポリシー(CSP)ガイドdeveloper.mozilla.org
- MDN:CSP frame-ancestorsdeveloper.mozilla.org
- MDN:Strict-Transport-Securitydeveloper.mozilla.org
- MDN:Set-Cookiedeveloper.mozilla.org
- OWASP HTTP Headers Cheat Sheetcheatsheetseries.owasp.org
- OWASP Secure Headers Project:推奨ヘッダー値raw.githubusercontent.com
- HSTS Preload List Submissionhstspreload.org
- nginx:ngx_http_headers_module(add_header)nginx.org
- Apache HTTP Server:mod_headershttpd.apache.org
- Cloudflare Pages:Headersdevelopers.cloudflare.com
- Netlify:Custom headersdocs.netlify.com
- IPA:安全なウェブサイトの作り方www.ipa.go.jp
Sitelemetry編集チームが作成しています。詳しく学ぶには、掲載した参考資料をご覧ください。



