はじめに
「とりあえずHTTPS化はしたけれど、セキュリティヘッダは手つかず」というサイトは少なくありません。HTTPレスポンスヘッダを数行足すだけで、クリックジャッキング・MIMEスニッフィング・中間者攻撃・情報漏えいといった典型的なリスクをまとめて下げられます。
この記事では、Webサイトに最低限入れておきたいセキュリティヘッダ7種を、役割・推奨値・設定するとどこが壊れやすいかまで含めて整理します。nginx / Apache(.htaccess) / WordPress のコピペ用設定と、設定後の確認方法までワンセットでまとめました。対象は自社サイトやホームページを運用・制作する初〜中級エンジニア/Web担当者です。
値はあくまで「多くのサイトで安全側に倒した推奨値」です。サイトの構成(外部CDN・埋め込み・サブドメイン構成など)によって最適値は変わります。本番反映前に必ず検証環境やReport-Onlyで確認してください。
まず全体像:今回設定する7つのヘッダ
| ヘッダ | 役割(何を防ぐか) | 本記事の推奨値(出発点) |
|---|---|---|
| Strict-Transport-Security | 常にHTTPS接続を強制(中間者攻撃・ダウングレード対策) | max-age=31536000; includeSubDomains |
| Content-Security-Policy | XSS・リソース読み込み制御(最重要かつ最も繊細) | まず Report-Only で検証 |
| X-Content-Type-Options | MIMEタイプの推測実行を禁止 | nosniff |
| X-Frame-Options | iframe埋め込みによるクリックジャッキング対策 | SAMEORIGIN |
| Referrer-Policy | リファラからの情報漏えいを抑制 | strict-origin-when-cross-origin |
| Permissions-Policy | カメラ・位置情報など強力なAPIの利用制限 | geolocation=(), camera=(), microphone=() |
| Cross-Origin-Resource-Policy | 自サイトのリソースを他オリジンから読ませない | same-origin |
以降、1つずつ意味と注意点を見ていきます。設定例だけ欲しい方は「環境別 設定例」へどうぞ。
各ヘッダの詳細と推奨値
1. Strict-Transport-Security(HSTS)
一度アクセスしたブラウザに「次回以降このドメインは必ずHTTPSで接続せよ」と覚えさせるヘッダです。HTTPへのダウングレードや、初回以降の中間者攻撃を防ぎます。
Strict-Transport-Security: max-age=31536000; includeSubDomains
-
max-ageは秒数。31536000は1年。 -
includeSubDomainsは全サブドメインも完全にHTTPS化している場合のみ付けます。HTTPでしか動かないサブドメインがあると、それらにアクセスできなくなります。 -
preload(ブラウザに事前組み込みさせるオプション)は強力ですが、一度有効化すると解除に時間がかかる片道切符です。サイトが恒久的にHTTPSで運用できる確信が持てるまで付けないのが無難です。 - 注意: サイトが完全にHTTPS化される前にHSTSを付けると、HTTPでしか配信していないページに自分自身がアクセスできなくなります。HTTPS化が完了してから設定してください。
2. Content-Security-Policy(CSP)
読み込めるスクリプト・スタイル・画像などの「出どころ」を制限し、XSSの被害を大幅に抑える最重要ヘッダです。一方で、最も既存サイトを壊しやすいヘッダでもあります。
いきなり本番に強いポリシーを入れると、インラインスクリプト・外部CDN・広告・解析タグなどが軒並みブロックされて表示が崩れます。そこでまず Content-Security-Policy-Report-Only で「ブロックせず違反だけ記録」して実態を把握してから、本番ヘッダに切り替えるのが定石です。
# ステップ1:まずはレポートのみ(サイトは壊れない)
Content-Security-Policy-Report-Only: default-src 'self'
# ステップ2:違反を潰しきってから本番ポリシーへ(例)
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
-
object-src 'none'とbase-uri 'self'はほぼ全サイトで安全に付けられる効果の高い指定です。 -
frame-ancestors 'self'は X-Frame-Options の上位互換(後述)。 -
'unsafe-inline'を style-src に付けているのは、多くのテーマ/プラグインがインラインCSSを使うための妥協です。本来は外したいので、可能ならnonce/hash方式への移行を検討してください。 -
WordPressでの現実: テーマやプラグインがインラインスクリプトを多用するため、
script-src 'self'だけで動くサイトは多くありません。WPで厳格なCSPを完成させるのは難易度が高く、Report-Onlyでの地道な調整が前提になります。
3. X-Content-Type-Options
ブラウザがContent-Typeを無視してMIMEタイプを「推測」する挙動を止めます。これにより、画像やテキストに偽装したスクリプトの実行などを防げます。値は1つだけで副作用もほぼありません。全サイト即入れて問題ないヘッダです。
X-Content-Type-Options: nosniff
4. X-Frame-Options
自サイトを他サイトの<iframe>に埋め込ませないことで、透明なiframeを重ねてクリックを盗む「クリックジャッキング」を防ぎます。
X-Frame-Options: SAMEORIGIN
-
DENY(どこにも埋め込ませない)/SAMEORIGIN(同一オリジンのみ許可)が有効値です。 - かつての
ALLOW-FROMは非推奨で、主要ブラウザでサポートされていません。特定の許可先を指定したい場合は CSP のframe-ancestorsを使います。 - 自サイトを意図的に他社サイトへ埋め込ませている(決済ウィジェット等)場合は、埋め込みが壊れないか確認してください。
5. Referrer-Policy
別サイトへ遷移した際に送られるRefererヘッダの中身を制御し、URLに含まれるパスやクエリ(セッションIDや内部パス等)の漏えいを抑えます。
Referrer-Policy: strict-origin-when-cross-origin
-
strict-origin-when-cross-originは近年の主要ブラウザの既定値でもあり、実用とプライバシーのバランスが良い設定です。 - もっと厳しくするなら
same-originやno-referrer。ただしリファラを使った解析や、外部サービスのリファラ判定が効かなくなる場合があるので影響を確認してください。
6. Permissions-Policy
位置情報・カメラ・マイク・決済APIなど、強力なブラウザ機能を「どのオリジンに許可するか」を宣言します。使っていない機能を明示的に無効化しておくと、万一スクリプトが混入しても悪用範囲を狭められます。
Permissions-Policy: geolocation=(), camera=(), microphone=()
- 空のかっこ
()は「どのオリジンにも許可しない(=完全無効)」の意味です。 - 旧
Feature-Policyの後継で構文が異なります。古い記事のFeature-Policy構文をそのまま流用しないよう注意してください。 - 自サイトで位置情報やカメラを使う機能がある場合は、その機能だけ
geolocation=(self)のように許可します。 - ディレクティブごとにブラウザ対応状況が異なるため、未対応の指定は単に無視されます(壊れはしません)。
7. Cross-Origin-Resource-Policy(CORP)
自サイトのリソース(画像・スクリプト等)を、他オリジンのページから読み込ませないようにします。リソースの不正な巻き込み(Spectre系のサイドチャネル等)対策の一つです。
Cross-Origin-Resource-Policy: same-origin
-
same-origin/same-site/cross-originがあります。 -
注意: 画像や静的ファイルを他サイト・別サブドメインから正規に読み込ませている場合、
same-originだと読めなくなります。配信用CDNや別ドメインから取得している構成ではsame-siteやcross-originを検討してください。
補足(発展):
Cross-Origin-Opener-Policy(COOP)とCross-Origin-Embedder-Policy(COEP)を組み合わせると「クロスオリジン分離」が有効になり、SharedArrayBuffer等が使えます。ただし外部埋め込みが広範に壊れやすいため、必要になってから慎重に導入する上級者向けの設定です。最低限の7種には含めていません。
環境別 設定例(コピペ用)
いずれも反映前に検証環境で動作確認を。CSPだけは本番でもまず
Report-Onlyから始めるのを強く推奨します。
nginx
server { ... } ブロック内に記述します。
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
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;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
反映:
nginx -t && systemctl reload nginx
- 末尾の
alwaysを付けると、4xx/5xxなどのエラー応答にもヘッダが付きます(付けないと200系などにしか付きません)。 -
最大のハマりどころ: nginxの
add_headerは、locationブロック内に別のadd_headerが1つでもあると、そのlocationでは上位(server)のadd_headerがすべて無効化されます(継承されない)。特定パスでヘッダが消える場合はこれが原因のことが多いです。共通ヘッダをincludeファイルにまとめて各locationで読み込む、などの対策を検討してください。
Apache(.htaccess)
mod_headers が必要です。
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
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"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
Header always set Cross-Origin-Resource-Policy "same-origin"
</IfModule>
事前にモジュール有効化(Debian/Ubuntu系の例):
a2enmod headers && systemctl restart apache2
-
.htaccessで効かせるにはサーバ側でAllowOverrideが許可されている必要があります。許可されていない共有レンタルサーバでは、コントロールパネルからの設定になる場合があります。 -
Header setとHeader always setの違いは、後者がエラー応答にもヘッダを付ける点です。基本はalwaysを推奨します。
WordPress(テーマの functions.php)
サーバ設定に触れない場合の手段です。send_headers フックで付与します。子テーマの functions.php に書くのが安全です。
add_action('send_headers', function () {
// 管理画面はCSP等で壊れやすいのでフロント側のみに付与する例
if (is_admin()) {
return;
}
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: geolocation=(), camera=(), microphone=()');
header('Cross-Origin-Resource-Policy: same-origin');
// CSPはまずReport-Onlyで検証することを推奨
header("Content-Security-Policy-Report-Only: default-src 'self'");
});
- 重要な制約: この方法はPHPを通るリクエスト(主にHTML)にしか効きません。Webサーバが直接返す静的ファイル(画像・CSS・JS等)にはヘッダが付きません。可能であればサーバ(nginx/Apache)側で設定するほうが網羅的で確実です。
-
キャッシュ系プラグインに注意: ページキャッシュが効いていると、PHPを通らずキャッシュHTMLが返ってPHPの
header()が実行されない場合があります。 - プラグインで管理したい場合は、レスポンスヘッダ設定に対応したセキュリティ系プラグイン(例: 各種セキュリティ強化プラグインやヘッダ専用プラグイン)も選択肢です。導入後は後述の方法で実際に付与されているか必ず確認してください。
もう使わないほうがよい「レガシーヘッダ」
古い記事を参考にすると、今は非推奨/廃止のヘッダを足してしまいがちです。次のものは新規に追加しないのが現在のセオリーです。
| ヘッダ | 現状 | 対応 |
|---|---|---|
X-XSS-Protection |
主要ブラウザのXSSオーディター機能が廃止済み。むしろ脆弱性を生む挙動が知られている | 付けないか、付けるなら X-XSS-Protection: 0。XSS対策はCSPで行う |
Expect-CT |
Chromeで廃止。役割を終えた | 付けない |
Public-Key-Pins(HPKP) |
運用事故が多く廃止 | 付けない |
Feature-Policy |
Permissions-Policy に置き換わった |
Permissions-Policy を使う(構文が違う点に注意) |
設定後の確認方法
設定したつもりでも、キャッシュやnginxの継承問題で「実は付いていない」ことはよくあります。反映後は必ず外形から確認しましょう。
まずは手元で curl で生のヘッダを見るのが確実です。
curl -sI https://example.com | grep -iE 'strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy|cross-origin'
ブラウザのDevTools(Network → 対象リクエスト → Headers)でも同様に確認できます。
スコア化・抜け漏れチェックには無料ツールが便利です。それぞれ評価軸が違うので、複数併用すると把握しやすくなります。
- securityheaders.com:URLを入れるとヘッダの有無をグレード表示。手軽さNo.1。
- Mozilla Observatory:ヘッダに加え総合的なWeb設定を採点。改善提案が具体的。
- Google Lighthouse(Chrome DevTools内蔵 / PageSpeed Insights):「Best Practices」にセキュリティ関連の指摘が出る。
- OWASP ZAP:ローカルで動かせるOSSスキャナ。ヘッダだけでなく受動的な脆弱性検査まで踏み込みたいとき。
- サイトドック(sitedock.jp):URLを入れると登録不要で、ヘッダ・Cookie属性・SPF/DMARCなどをパッシブに健診してスコア化する無料サービス(対象に負荷をかけるポートスキャンや能動的な脆弱性検査は、自ドメインの所有者確認を済ませた場合のみ)。ヘッダの抜け漏れを第三者視点でまとめて確認したいときの手段の一つとして。
ツールごとに減点基準が微妙に異なるため、「あるツールで満点でない=危険」とは限りません。指摘内容を見て、自サイトの構成に照らして取捨選択するのが現実的です。
まとめ
- セキュリティヘッダは数行のコピペで効果が出る、費用対効果の高い対策。
-
副作用がほぼないもの(
X-Content-Type-Options: nosniff、X-Frame-Options: SAMEORIGIN、Referrer-Policy、Permissions-Policy)はまず即入れてよい。 -
CSPは別格。いきなり本番に入れず、
Content-Security-Policy-Report-Onlyで違反を観測してから段階導入する。WordPressでは特に時間がかかる前提で。 -
HSTS は完全HTTPS化後に。
includeSubDomainsとpreloadは影響範囲を理解してから。 - nginxは
add_headerの継承されない仕様、WordPressのsend_headersは静的ファイルに効かない/キャッシュで実行されないという落とし穴に注意。 - 古い
X-XSS-Protection/Expect-CT/HPKP/Feature-Policyは新規追加しない。 - 反映後は
curl -Iと securityheaders.com / Mozilla Observatory / Lighthouse / OWASP ZAP などで実際に付いているかを必ず確認する。
想定タグ: Security nginx Apache WordPress CSP
想定読了時間: 約8〜10分
難易度: 初〜中級(CSPの実運用のみ中級+)
関連記事
- 兵庫県の企業サイト307件を外形調査したら、DMARC未設定55%・CSP欠落92.5%だった — 本記事のヘッダが実際どれだけ普及していないかの実データ
- WordPressセキュリティ 最低限やることチェックリスト10
- CookieのSameSite / Secure / HttpOnly の正しい付け方
お知らせ — バーチャルセキュリティコンシェルジュ「一ノ瀬あかり」がデビューしました🎉
JIISセキュリティラボから、セキュリティ用語をやさしく動画で解説する案内役「一ノ瀬あかり」が登場しました。よければデビューのご挨拶もご覧ください。