
HTTPセキュリティヘッダの基本設定|まず入れる3つと確認手順
「セキュリティヘッダが設定されていません」
取引先のチェックシートや、無料の診断サイトの結果に、そう書かれていたことはないでしょうか。指摘そのものは短い一行なのに、調べ始めると出てくる名前が多い。CSP、HSTS、X-Frame-Options……。しかも本番で動いているサイトです。うっかり入れて表示が壊れたら、指摘を直すどころの話ではなくなる。
その慎重さは、正しい感覚だと思います。セキュリティヘッダの中には、入れた瞬間にサイトの一部が動かなくなるものが実際にあります。だから「全部いっぺんに入れましょう」という記事は、保守運用の現場ではあまり役に立ちません。
この記事では、副作用がほとんどないものから順に、3つだけ入れるところまでを一緒にやります。残りは、入れなくても責められない理由も含めて整理します。
結論:まず入れるのは、①X-Content-Type-Options: nosniff(ほぼ無害)②X-Frame-Options: SAMEORIGIN(iframe利用の有無だけ確認)③Referrer-Policy: strict-origin-when-cross-origin(実質、いまのブラウザの既定と同じ)の3つです。この3つは、既存サイトを壊す可能性がかなり低いわりに、指摘の大半に答えられます。HSTS は「HTTPSへの移行が完全に終わっているか」を確かめてから、短い期間の指定で。CSP は Report-Only から。そしてX-XSS-Protectionは、いまはもう入れなくて大丈夫です。作業前に、必ず設定ファイルの退避とcurlでの現状確認をしておきます。
なお、設定を書く場所(Webサーバーかアプリか)や記法は、環境によって変わります。ここに載せる例はそのまま貼れることを狙ったものではなく、自分の環境の書き方に置き換える前提の見本です。本番反映の前に、検証環境と公式ドキュメントで確かめてください。
何が起きているか:ヘッダは「サーバーからブラウザへの、守り方のお願い」
そもそもセキュリティヘッダとは何かを、一度だけ整理させてください。ここが腑に落ちると、あとの判断がぐっと楽になります。

Webサーバーがページを返すとき、本文(HTML)の前にヘッダという短い指示の並びが付いています。セキュリティヘッダは、その中の一部です。中身をざっくり言うと、こういうお願いです。
- 「このファイルの種類は、こちらが言ったとおりに扱ってください」
- 「このページを、よそのサイトの枠の中に埋め込まないでください」
- 「よそへ移動するとき、どこから来たかを詳しく渡さないでください」
ここで大事なのは、守る仕事をするのはサーバーではなくブラウザ側だということです。サーバーは「こう扱ってほしい」と伝えるだけ。だから、
- ヘッダを入れても、サーバー側の穴(脆弱性)はふさがりません。パッチ適用や権限の見直しとは別の話です。
- 逆に、アプリを1行も直さずに、ブラウザ側の守りを1段足せます。改修の予算が取れない現場でも打てる手だ、というのが実務上の一番の価値です。
つまりセキュリティヘッダは、万能ではないけれど、費用対効果がとても良い一手です。「これさえ入れれば安全」ではないと分かったうえで、入れられるものを入れておく。その温度感がちょうどいいと思います。
作業の前に:現状を見て、戻せるようにしておく
設定を足す前に、5分だけ準備をします。ここを飛ばすと、あとで「元がどうだったか分からない」状態になりがちです。
いま何が返っているかを見ます。
# 応答ヘッダだけを表示する(-o /dev/null で本文は捨てる)
curl -s -o /dev/null -D - https://example.com/
curl -I でも見られますが、あれは HEAD というリクエストで、環境によっては通常の表示(GET)と挙動が違うことがあります。実際に人が見るときと同じ GET で確かめるほうが確実です。上の書き方なら GET のままヘッダだけ取れます。
セキュリティ関連だけ絞りたいときは、こうします。
curl -s -o /dev/null -D - https://example.com/ \
| grep -i -E 'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'
結果は、作業前のひかえとしてファイルに残しておきます。 何かおかしくなったとき、「入れる前はこうだった」がすぐ出せます。
curl -s -o /dev/null -D - https://example.com/ > /var/backups/headers-before.txt
設定ファイルも、触る前に日付つきでコピーしておきます。 一行足すだけの作業でも、これは省かないほうがいいです。詳しい考え方は本番反映前のチェックリストと作業前バックアップにまとめています。
そして、どこでヘッダを出すかを1か所に決めます。nginx でも Apache でもアプリのフレームワークでも出せるため、二重に出て同じヘッダが2行返ることがあります。ブラウザの解釈が読みにくくなるので、原則どこか1か所にそろえます。現状確認で同じヘッダが2行出ていたら、まずそこの整理からです。
まず入れる3つ(副作用が小さい順)
① X-Content-Type-Options: nosniff
いちばん最初に入れていいのはこれです。 副作用がほぼありません。
ブラウザには、サーバーが申告したファイルの種類が怪しいとき、中身を見て種類を推測する動き(MIMEスニッフィング)があります。親切な機能なのですが、利用者がアップロードしたファイルが、意図せずスクリプトとして解釈されるきっかけになることがあります。nosniff は「推測しないで、こちらの申告どおりに扱って」という指示です。
副作用が出るとしたら、もともとファイルの種類(Content-Type)を間違って返していた場合です。たとえば CSS を text/plain で返していたようなケースでは、推測に助けられていたものが助けられなくなります。ただこれは、そもそも直すべき設定が表に出ただけとも言えます。心配なら、CSS と JavaScript が正しい種類で返っているかだけ、先に見ておきましょう。
② X-Frame-Options: SAMEORIGIN
よそのサイトの枠(iframe)に、自分のページを埋め込ませないという指示です。ボタンの位置に透明な枠を重ねて、意図しない操作をさせる手口への対策になります。
SAMEORIGIN は「同じサイト内の埋め込みなら許す」という設定です。入れる前に、ひとつだけ確認しておきます。
- 自社サイトのページを、別ドメインのページに iframe で埋め込んでいないか。
- 予約システム、決済、チャット、地図など、外部サービスの管理画面に自社ページを表示させる使い方をしていないか。
- 逆に、取引先のサイトに自社のフォームを埋め込んでもらっていないか。
心当たりがあるなら、その埋め込みは壊れます。使っている場所を洗い出してから決めましょう。洗い出しの進め方は改修の影響範囲を見落とさない調査の順番が近い考え方です。
なお、いまのブラウザでは、後述の CSP に frame-ancestors を書くほうが正式な方法とされています。ただ X-Frame-Options もまだ広く解釈されているため、当面は両方書いておくのが無難です。CSP をまだ入れない段階なら、X-Frame-Options だけで十分に意味があります。
③ Referrer-Policy: strict-origin-when-cross-origin
リンクをたどってよそのサイトへ移動するとき、「どのページから来たか」をどこまで渡すかの指定です。社内システムや会員向けページでは、URL のパスやパラメータ自体が知られたくない情報を含むことがあります。
strict-origin-when-cross-origin は、同じサイト内では詳しく、よそへ出るときはドメイン名までしか渡さないという設定です。実はこれ、いまの主要ブラウザの既定値とほぼ同じです。つまり、明示的に書いても挙動はほとんど変わりません。
「変わらないなら書く意味がないのでは」と思うかもしれません。実務上の意味はふたつあります。ひとつは、古いブラウザや、既定が違う環境でも同じ動きにそろえられること。もうひとつは正直に言えば、診断ツールやチェックシートで「未設定」と書かれなくなることです。後者は本質ではありませんが、説明の手間が減るのは現場では地味に助かります。
3つをまとめて書くと
nginx の場合:
# server ブロックの中に書く。always を付けると 4xx・5xx の応答にも付く
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Apache の場合:
# mod_headers を有効にしたうえで、VirtualHost などに書く
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
nginx で、ここだけは知っておきたい落とし穴があります。
nginx の add_header は、下位のブロック(location など)に add_header を1つでも書くと、上位のブロックで書いた add_header が、そのブロックでは効かなくなります。足し算ではなく、上書きに近い挙動です。
server {
add_header X-Content-Type-Options "nosniff" always; # ← 下の location では効かない
location /api/ {
add_header X-Cache-Status "MISS" always; # ← ここに1つでも書くと、上のは消える
}
}
「サーバー全体に入れたつもりが、一部のパスだけ抜けていた」という取りこぼしは、この仕様で起きます。設定を入れたあと、トップページだけでなく、location を分けているパスも curl で確かめてください。 抜けていたら、そのブロックにも同じ行を書き足します。
反映は、まず設定の構文チェック、それから再読み込みの順です。
# nginx
nginx -t && systemctl reload nginx
# Apache
apachectl configtest && systemctl reload apache2
reload は稼働中の接続を切らずに設定を読み直します。いきなり restart しなくて大丈夫です。反映後、もう一度 curl を叩いて、狙いどおり3行増えているかを見ます。ここまでで、指摘事項の多くには答えられる状態になります。
次の段階:確認してから入れるもの
3つ入れて落ち着いたら、次はここです。急がなくて大丈夫です。
HSTS(Strict-Transport-Security):戻しにくいので、短い期間から
「このサイトには、次からは必ず HTTPS で来てください」とブラウザに覚えさせる指示です。効果は高いのですが、このヘッダには他とは違う怖さがあります。
指定した期間、ブラウザ側が「HTTPで来るのを拒否する」状態を覚え込みます。 サーバー側でヘッダを消しても、すでに覚えたブラウザの記憶は消えません。 期限が切れるか、利用者が自分で消すまで残ります。つまり、設定ミスをサーバー側だけでは取り消せないわけです。
だから、入れる前に確認します。
- サイト全体が HTTPS で問題なく見られるか(HTTPでしか動かないページ・機能が残っていないか)。
- 証明書の更新が確実に回っているか。期限切れのとき、HSTS があると利用者は「無視して進む」ができなくなります。ここはSSL証明書の期限切れを防ぐ更新と監視と必ずセットで考えてください。
- サブドメインを含めるかどうか。
includeSubDomainsを付けると、社内向けやテスト用も含めてすべてのサブドメインが対象になります。HTTPでしか動かないものが1つでもあると、そこが見られなくなります。
進め方としては、最初はごく短い期間で試して、問題がなければ延ばすのが安全です。
# 最初は 5分(300秒)ほどで様子を見る
Strict-Transport-Security: max-age=300
# しばらく問題がなければ延ばす(1年)。サブドメインを含めるのは、全部HTTPS化を確認してから
Strict-Transport-Security: max-age=31536000; includeSubDomains
preload(ブラウザに最初から登録してもらう仕組み)もありますが、取り消しに時間がかかります。急いで付ける必要はありません。
CSP(Content-Security-Policy):Report-Only から育てる
いちばん効果が大きく、いちばん壊しやすいヘッダです。「読み込んでよいスクリプトや画像はここから」とブラウザに指定できます。
ただ、既存のサイトにいきなり入れると、かなりの確率で何かが止まります。 広告タグ、アクセス解析、チャット、外部フォント、HTMLに直接書かれたスクリプト。長く運用してきたサイトほど、外から読み込んでいるものが増えています。
そこで、まず「報告だけして、ブロックはしない」モードで様子を見ます。
# ブロックせず、違反だけをブラウザの開発者ツールに出す
Content-Security-Policy-Report-Only: default-src 'self'
この状態でしばらく運用し、自分のサイトが実際どこから何を読み込んでいるかの一覧を作ります。開発者ツールのコンソールに、違反の候補が並びます。その一覧をもとに許可先を足していき、止まるものが無くなってから -Report-Only を外す。これが唯一まっとうな進め方です。
急ぎで一部だけ効かせたいなら、範囲の狭い指定からという手もあります。
# 「よそのサイトの枠に埋め込ませない」だけを CSP で指定する
Content-Security-Policy: frame-ancestors 'self'
これは X-Frame-Options とほぼ同じ意味なので、既存サイトを壊すおそれが小さい部分です。CSP の第一歩としては現実的だと思います。
Permissions-Policy:使う機能だけ残す
カメラ・マイク・位置情報など、ブラウザの機能のうち使わないものを止めておく指定です。以前は Feature-Policy という名前でしたが、いまは Permissions-Policy が現行の名称です。古い記事を参考にすると旧名のまま書いてしまうことがあるので、そこだけ気をつけてください。
# カメラ・マイク・位置情報を、自サイト・埋め込み先ともに使わせない
Permissions-Policy: camera=(), microphone=(), geolocation=()
使っていない機能を止めるだけなので比較的安全ですが、Web会議や地図、写真アップロードのような機能がある場合は、そこを除外する必要があります。優先度はそれほど高くないので、余裕のあるときで大丈夫です。
もう入れなくていいもの:X-XSS-Protection
古い記事や、社内に残っている設定ファイルに X-XSS-Protection: 1; mode=block という行を見かけることがあります。これは、いまは入れる必要がありません。
これは各ブラウザに搭載されていた簡易的な検査機能を制御するヘッダでしたが、その機能自体が主要ブラウザから削除済みです。もう働くものがないので、書いても効果がありません。さらに当時、この機能そのものが別の問題を生むことがあると指摘され、削除に至った経緯があります。
いま指定するとしたら、明示的に無効にする 0 です。
X-XSS-Protection: 0
とはいえ、すでに 1; mode=block が入っていても、慌てて夜中に直すほどの話ではありません。 次に設定ファイルを触るタイミングで、0 に直すか行ごと消せば十分です。
こういう「昔は正しかったが、いまは違う」設定は、保守運用ではよく出てきます。見つけたときに責める必要はありません。書いた当時は正しかったのですから。淡々と現在のかたちへ寄せていけば大丈夫です。
具体例:チェックシートの指摘から2日で返した現場
社内システムと会社サイトを、他の業務と兼務で守っている担当者の例で、順番をたどってみます。
- きっかけ:取引先から届いたセキュリティチェックシートに「セキュリティヘッダの設定状況」の欄があり、何と答えればいいか分からなかった。無料の診断サイトにかけてみると、赤い判定がずらりと並んだ。
- 1日目(見るだけ):まず
curl -s -o /dev/null -D -で現状を取り、ファイルに保存した。返っていたのはX-Powered-By(アプリの種類とバージョン)くらい。ついでにlocation /api/を分けていたことを思い出し、そちらのヘッダも取っておいた。 - 社内確認:iframe の利用がないかを、営業と情シスに聞いた。「取引先の会員サイトに、うちの問い合わせフォームを埋め込んでもらっている」と判明。
X-Frame-OptionsをDENYにしていたら、そこが止まっていた。 今回はSAMEORIGINにとどめ、その埋め込みだけ別の設定で許可する方針にした。 - 2日目(3行だけ入れる):検証環境で3つを設定 →
nginx -t→reload→curlで確認。トップページには3行付いたが、/api/には付いていない。add_headerの継承の仕様を思い出し、そのブロックにも書き足した。 - 本番反映:作業前に設定ファイルを日付つきでコピー。反映後、トップ・ログイン後・API の3か所を
curlで確認し、実際の画面も一通り触った。所要時間は30分ほど。 - 記録:変更管理台帳に「日時・変更内容・戻し方」を1行残した。HSTS と CSP は「HTTPSの棚卸しが終わってから」と、次回の宿題として書いておいた。
この例で効いたのは、高度な知識ではありません。入れる前に iframe の利用を1本電話で確かめたことと、入れたあとに1か所ではなく3か所を確認したこと。この2つだけです。
影響:3行入れると、何が変わるか
正直に言うと、この3行でサイトが劇的に安全になるわけではありません。それでも、変わることがいくつかあります。
- アプリを直さずに、守りが1段増える。改修の予算も工数も要らない対策は、そう多くありません。
- チェックシートや診断への回答が、すぐ出せるようになる。「未設定」の欄が減るだけで、やり取りの往復が減ります。取引先への説明もしやすくなります。
- 設定の現在地が分かるようになる。
curlの結果をひかえておけば、次に何かあったとき「いつからこうなっていたか」が追えます。サーバー構成のドキュメントに1行足しておくと、次の担当者にも渡ります。 - CSP という宿題の輪郭が見える。いきなり全部は無理でも、「まず Report-Only で読み込み先を数える」という次の一歩が具体的になります。
逆に、入れていない状態が続くと、困るのはたいていセキュリティ事故そのものより先に、説明を求められる場面です。取引先の審査、社内の点検、監査。そのたびに一から調べ直すことになります。一度入れて記録しておけば、その手間は次から消えます。
明日やること:まず「見るだけ」から
設定を変えなくていいので、明日は次の順で大丈夫です。
- 現状を1回取る。
curl -s -o /dev/null -D - https://自分のサイト/を叩いて、結果をファイルに保存する。3分で終わります。 locationやVirtualHostを分けているパスがあれば、そこも取る。あとの取りこぼしを防げます。- iframe の利用があるかを、社内に1本だけ聞く。「うちのページを、よそのサイトに埋め込んでもらっている場所はありますか」。この一言で、②の事故はほぼ防げます。
- 検証環境で3行を入れて
curlで確認する。本番はその次の日でも構いません。 - 入れたら、変更管理の記録に1行残す。「いつ・何を・どこに入れたか・戻し方」だけで十分です。
1と3だけでも、今日と明日で確実に前に進みます。設定を入れることより、現状を1回見ておくことのほうが価値が大きい場面は、保守運用ではよくあります。
セキュリティヘッダ チェックリスト
全部そろえる前提ではありません。上から優先順位で並べてあります。
まずここだけ(最低ライン・これだけで効きます)
- いま返っているヘッダを
curlで確認し、作業前のひかえを残したか -
X-Content-Type-Options: nosniffが返っているか -
X-Frame-Options: SAMEORIGIN(または CSP のframe-ancestors)が返っているか - 入れる前に、iframe での埋め込み利用がないかを確認したか
- 設定ファイルを、触る前に日付つきで退避したか
余力があれば(落ち着いたら足す)
-
Referrer-Policyを明示的に指定したか - ヘッダを出す場所を1か所にそろえ、同じヘッダが二重に返っていないか
-
locationなどブロックを分けているパスでも、ヘッダが付いているか確認したか -
nginx -t/apachectl configtestを通してから反映したか - 反映後、実際の画面(ログイン後・フォーム・埋め込み箇所)を一通り触ったか
- 変更管理の記録に、内容と戻し方を1行残したか
できる現場だけ(無理なら飛ばしてよい)
- HTTPSへの移行が完全に終わっていることを確認したうえで、HSTS を短い期間から試したか
- 証明書の更新が確実に回る仕組みがあるか(HSTS を入れるなら必須)
- CSP を
Report-Onlyで流し、読み込み先の一覧を作り始めたか -
X-XSS-Protection: 1; mode=blockが残っていれば、次の機会に0へ直すか消すと決めたか -
Permissions-Policyで、使っていない機能を止めたか
最低ラインの5つが埋まっていれば、チェックシートの多くの欄には自信を持って答えられます。残りに○が付かなくても大丈夫です。
よければ、こちらも
ヘッダの設定は、単体で完結する作業ではありません。周りを少し整えると、次の一歩が軽くなります。
- SSL証明書の期限切れを防ぐ更新と監視|見落とさない仕組み:HSTS を入れるなら、こちらが先です。証明書の更新が確実に回っていることが前提になります。
- 管理画面のアクセス制限|IP制限・Basic認証の基本:ヘッダはブラウザ側の守りです。そもそも管理画面に入れなくする対策と組み合わせると効きます。
- 本番反映前のチェックリストと作業前バックアップの取り方:1行の設定変更でも、退避と確認の順番は同じです。
- 変更管理台帳の付け方|いつ・誰が・何を変えたか残す:ヘッダの追加は、あとから「いつ入れたか」を聞かれやすい作業です。
- サイト改ざんに気づく仕組み|一人運用で始める改ざん検知の作り方:守りを足したあと、変化に気づく側の仕組みです。両輪で効きます。

セキュリティヘッダの話がしんどいのは、名前が多いわりに、どれが自分に必要か誰も教えてくれないからだと思います。調べるほど「入れるべき」ものが増えていって、でも本番は動いていて、結局手が止まる。その状態で立ち止まっていた時間は、慎重さの証拠であって、怠慢ではありません。
今日いきなり全部そろえなくて大丈夫です。まず curl を1回叩いて、いま何が返っているかを見る。 それだけで、「よく分からないもの」が「現在地の分かるもの」に変わります。3行入れるのは、そのあとでいい。
明日、その1行のコマンドを打つところから始めてみませんか。指摘の欄がひとつ埋まるだけでなく、次に同じことを聞かれたときの自分が、ずいぶん楽になります。
ほかの実務ヒントは記事一覧からどうぞ。保守運用の小さな備えを、メールでも少しずつお届けしています。