- 技術 💻
セキュリティヘッダー一覧|HSTS・CSPほか設定コード集
セキュリティヘッダーとは、Webサーバーがレスポンスに付けて「このページをどう扱うか」をブラウザに指示するHTTPヘッダーのことです。 HTTPSの強制(HSTS)、他サイトのiframeへの埋め込み制御(X-Frame-Options / frame-ancestors)、MIMEタイプの推測停止(X-Content-Type-Options)などを、サーバー設定の数行で有効にできます。
この記事では、主要なセキュリティヘッダー11種を「推奨/条件つき推奨/非推奨・廃止」に分けた一覧表と、Nginx・Apache・Next.js・Express(helmet)・Cloudflare・WordPress別のコピペ設定をまとめました。Nginx 1.24.0 と Express 5.2.1 + helmet 8.3.0 は実際に起動して curl -I で確認し、Nginxの add_header が location でまるごと消える落とし穴も実行結果つきで再現しています。CSPは別記事で詳しく扱っているため、本記事では概要と最小例に絞ります。
セキュリティヘッダーの早見表
推奨値は OWASP HTTP Headers Cheat Sheet と MDN の記載(2026年10月時点で確認)をもとにしています。
| ヘッダー | 区分 | 推奨値(まずはこれ) | 何を防ぐ・何をする |
|---|---|---|---|
| Strict-Transport-Security(HSTS) | 推奨 | max-age=31536000; includeSubDomains |
以後の接続をHTTPSに固定する |
| Content-Security-Policy(CSP) | 推奨 | 最小なら frame-ancestors 'self'; object-src 'none'; base-uri 'self' |
読み込めるスクリプト・埋め込み元を制限する |
| X-Content-Type-Options | 推奨 | nosniff |
MIMEタイプの推測(スニッフィング)を止める |
| X-Frame-Options | 推奨(CSPのframe-ancestorsと併用) | DENY または SAMEORIGIN |
iframe埋め込みによるクリックジャッキング対策 |
| CSP frame-ancestors | 推奨 | frame-ancestors 'self' |
X-Frame-Optionsの後継。複数オリジンを指定できる |
| Referrer-Policy | 推奨 | strict-origin-when-cross-origin |
外部サイトへ送るURL情報を減らす |
| Permissions-Policy | 推奨 | camera=(), microphone=(), geolocation=() |
使わないブラウザ機能を無効にする |
| Cross-Origin-Opener-Policy(COOP) | 推奨 | same-origin |
別オリジンのウィンドウとの参照を切り離す |
| Cross-Origin-Resource-Policy(CORP) | 条件つき推奨 | same-site(または same-origin) |
自分のリソースを他サイトから読ませない |
| Cross-Origin-Embedder-Policy(COEP) | 条件つき(必要なときだけ) | require-corp または credentialless |
SharedArrayBuffer等が必要なときにCOOPと組で使う |
| X-XSS-Protection | 非推奨 | 0(または送らない) |
旧ブラウザのXSSフィルター。無効化が推奨 |
| Server / X-Powered-By | 除去推奨 | 送らない(バージョンを隠す) | ソフトウェア名・バージョンの露出を減らす |
| Expect-CT / Public-Key-Pins | 廃止 | 送らない | どちらも使わない(OWASPが明記) |
| Feature-Policy | 廃止(改名) | Permissions-Policy に置き換える | Permissions-Policyの旧名 |
迷ったら「HSTS・CSP(最小)・nosniff・X-Frame-Options・Referrer-Policy・Permissions-Policy・COOP」の7つを入れ、Server/X-Powered-Byを消す、が出発点です。COEP は外部画像や広告の表示に影響しやすいので、必要になったときに追加します。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「セキュリティヘッダー一覧|HSTS・CSPほか設定コード集」
各ヘッダーの意味と値の選び方
HSTS(Strict-Transport-Security)でHTTPSに固定する
HSTSは「このドメインには今後 max-age 秒のあいだHTTPSでしかアクセスしない」とブラウザに覚えさせるヘッダーです。 一度受け取ったブラウザは、http:// のリンクをクリックしても通信前に https:// へ内部で切り替えます。
Strict-Transport-Security: max-age=31536000; includeSubDomains
| ディレクティブ | 意味 | 注意点 |
|---|---|---|
max-age=秒 |
記憶する期間。31536000=1年 | 導入初期は 300(5分)など短くして様子を見ると戻しやすい |
includeSubDomains |
サブドメインにも適用 | HTTPのみのサブドメイン(社内用の検証環境など)があると開けなくなる |
preload |
ブラウザ組み込みリストへの登録を希望する印 | 下の「preloadの注意」を読んでから付ける |
- HSTSはHTTPSのレスポンスでだけ有効です。 HTTPのレスポンスに付けてもブラウザは無視します(RFC 6797の仕様)。HTTP側は301でHTTPSへリダイレクトし、HTTPS側でHSTSを返します。
- 段階的に伸ばすのがおすすめです。
300→86400→604800→31536000と確かめながら伸ばします。
HSTS preloadの注意:登録すると簡単には外せない
preload を付けて hstspreload.org に申請すると、ドメインが Chrome などのブラウザに組み込みリストとして同梱されます。初回アクセスからHTTPSが強制される一方、次の点を押さえておきます。
| 項目 | 内容 |
|---|---|
| 申請条件(hstspreload.orgの記載) | 有効な証明書、HTTP→HTTPSのリダイレクト、すべてのサブドメインがHTTPS、ベースドメインで max-age 31536000以上・includeSubDomains・preload を返す |
| 削除 | 削除申請はできるが、各ブラウザのリリースに反映されて利用者の手元に届くまで数か月単位かかる |
| 影響範囲 | ドメイン配下のすべてのサブドメインがHTTPS必須になる |
preloadは「全サブドメインをHTTPSで運用し続ける」と決めてから付けるのが安全です。普段の運用は preload なしのHSTSでも十分に効果があります。
CSP(Content-Security-Policy)は最小構成から
CSPは、ページが読み込んでよいスクリプト・画像・埋め込み元などを列挙するヘッダーです。 効果が大きい反面、サイトごとの調整が必要なので、詳しい書き方・段階導入(Report-Only)・nonceは別記事「CSP設定(Content-Security-Policy)|nginx段階導入」で解説しています。
ここでは、既存のスクリプトにほぼ影響せずに入れられる最小例だけ紹介します。
Content-Security-Policy: frame-ancestors 'self'; object-src 'none'; base-uri 'self'
| ディレクティブ | 効果 |
|---|---|
frame-ancestors 'self' |
自サイト以外からのiframe埋め込みを禁止(X-Frame-Options SAMEORIGIN相当) |
object-src 'none' |
<object> <embed> を無効化 |
base-uri 'self' |
<base> タグで相対URLの基準を書き換えられないようにする |
X-Frame-Options と frame-ancestors はどちらを使う?
両方送るのが確実です。 MDNでは、より柔軟な指定ができる CSP の frame-ancestors が推奨され、両方ある場合は対応ブラウザで frame-ancestors が優先されます。X-Frame-Optionsは古いブラウザ向けの備えとして残します。
| やりたいこと | X-Frame-Options | CSP frame-ancestors |
|---|---|---|
| どこからも埋め込ませない | DENY |
frame-ancestors 'none' |
| 自サイトからだけ許可 | SAMEORIGIN |
frame-ancestors 'self' |
| 特定の別オリジンも許可 | 指定できない(ALLOW-FROM は廃止) |
frame-ancestors 'self' https://partner.example.com |
ポイント:frame-ancestors は <meta http-equiv> では効きません。 HTTPヘッダーで送ります。
X-Content-Type-Options
nosniff の1択です。ブラウザが Content-Type を無視して中身から種類を推測するのを止めます。JSやCSSを正しい Content-Type で配信していることが前提なので、設定後に表示崩れがあれば、サーバーのMIMEタイプ設定を確認します。
Referrer-Policy
主要ブラウザの既定値は strict-origin-when-cross-origin ですが、明示しておくと意図が設定ファイルに残ります。
| 値 | 同一オリジンへ | 他オリジン(HTTPS)へ | 向いている用途 |
|---|---|---|---|
strict-origin-when-cross-origin |
フルURL | オリジンのみ | 一般的なサイトの推奨値 |
same-origin |
フルURL | 送らない | 外部に一切出したくない管理画面 |
no-referrer |
送らない | 送らない | 最も控えめ(アクセス解析の参照元も取れなくなる) |
Permissions-Policy
カメラ・マイク・位置情報など、使わない機能を ()(空リスト)で無効にします。 旧名の Feature-Policy とは書式が違うので注意します。
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
自サイトだけ許可したい場合は geolocation=(self)、特定オリジンも許可するなら geolocation=(self "https://maps.example.com") と書きます。
COOP・CORP・COEP(Cross-Origin系3兄弟)
| ヘッダー | 推奨値 | 影響しやすいもの |
|---|---|---|
| COOP | same-origin |
OAuthなどのポップアップログインで window.opener を使う場合は same-origin-allow-popups |
| CORP | same-site |
自社の別サブドメインから画像を読む構成なら same-site、外部に配る画像・CDNは cross-origin |
| COEP | 必要なときだけ require-corp / credentialless |
外部画像・埋め込み動画など、CORP/CORSのないリソースが読めなくなる |
COEPは「SharedArrayBufferや高精度タイマーを使う(crossOriginIsolatedが必要)」ときに、COOPと組で入れるものです。通常のサイトでは無理に入れなくて大丈夫です。
X-XSS-Protection は「0」にする
かつてのブラウザ内蔵XSSフィルターを制御するヘッダーですが、現行の主要ブラウザではフィルター自体が削除されています。OWASPは0 を送るか、ヘッダーを送らないことを推奨しています。XSS対策はCSPと出力時のエスケープで行います。
Server・X-Powered-By を消す
Server: nginx/1.24.0 (Ubuntu) や X-Powered-By: Express は、使っているソフトとバージョンを外部に知らせます。脆弱性への対処そのものはアップデートで行い、その上で情報の露出を減らすのが目的です。
サーバー・フレームワーク別のコピペ設定
Nginx(1.24.0で動作確認)
ヘッダーは別ファイルにまとめて include すると、後述の継承の落とし穴を避けやすくなります。
# /etc/nginx/snippets/security_headers.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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 "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'" always;
# /etc/nginx/sites-available/example.conf
server_tokens off; # Server: nginx(バージョンを出さない)
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri; # HTTPはHTTPSへ
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/html;
include /etc/nginx/snippets/security_headers.conf;
location /static/ {
include /etc/nginx/snippets/security_headers.conf; # location で add_header を書くなら再掲
add_header Cache-Control "public, max-age=86400" always;
}
}
sudo nginx -t && sudo systemctl reload nginx
server_tokens の違いは次のとおりです(実行結果)。
server_tokens on; → Server: nginx/1.24.0 (Ubuntu)
server_tokens off; → Server: nginx
Server ヘッダー自体を消すには、headers-more モジュール(more_clear_headers Server;)や、商用版の設定が必要です。
Apache(mod_headers)
# a2enmod headers を有効にしたうえで、VirtualHost か .htaccess に記述
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
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 "camera=(), microphone=(), geolocation=()"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
Header unset X-Powered-By
</IfModule>
# httpd.conf / apache2.conf(.htaccessでは書けない)
ServerTokens Prod
ServerSignature Off
Header always setのalwaysを付けると、エラーレスポンス(404など)やリダイレクトにも付きます。- PHPの
X-Powered-Byは、php.iniのexpose_php = Offでも消せます。 - Apacheの設定はこの記事では実行していません。書式は Apache公式の mod_headers ドキュメントで確認しています。
Next.js(next.config)
App Router / Pages Router どちらでも、next.config の headers() で全パスに付けられます。
// 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=()" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Content-Security-Policy", value: "frame-ancestors 'self'; object-src 'none'; base-uri 'self'" },
];
const nextConfig: NextConfig = {
poweredByHeader: false, // X-Powered-By: Next.js を消す
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
export default nextConfig;
- nonce付きのCSPを使う場合は、
headers()ではなく Middleware(Proxy)でリクエストごとに生成します(Next.js公式のCSPガイド参照)。 output: "export"(静的書き出し)ではheaders()が効かないため、配信先(Nginx、Cloudflareなど)で設定します。- Next.jsの設定はこの記事では実行していません。書式は Next.js公式ドキュメント(next.config の headers)で確認してください。
Express(helmet 8.3.0で動作確認)
helmet を1行入れるだけで、主要なセキュリティヘッダーがまとめて付き、X-Powered-By も消えます。
npm i express helmet # 確認時のバージョン:express 5.2.1 / helmet 8.3.0 / Node.js 22.22.2
// app.mjs
import express from "express";
import helmet from "helmet";
const app = express();
app.use(helmet());
app.get("/", (req, res) => res.send("ok"));
app.listen(7220, () => console.log("listening on 7220"));
helmetなしとありで curl -I した結果です(Date・ETag等は省略)。
$ curl -sI localhost:7222/ # helmet なし
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
$ curl -sI localhost:7220/ # helmet() あり
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
Content-Type: text/html; charset=utf-8
読み取れるポイント:
- helmet 8 の既定値で X-XSS-Protection は 0、X-Powered-By は削除されています(OWASPの推奨と同じ)。
- Referrer-Policy の既定は no-referrer です。アクセス解析で参照元を使うなら下のように変更します。
- Permissions-Policy は helmet には含まれません。 必要なら自分でミドルウェアを足します。
値を調整した版です。
// app2.mjs
import express from "express";
import helmet from "helmet";
const app = express();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
"default-src": ["'self'"],
"img-src": ["'self'", "data:"],
"frame-ancestors": ["'self'"],
},
},
strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true },
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
xFrameOptions: { action: "sameorigin" },
})
);
app.use((req, res, next) => {
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
next();
});
app.get("/", (req, res) => res.send("ok"));
app.listen(7221, () => console.log("listening on 7221"));
実行すると Referrer-Policy: strict-origin-when-cross-origin と Permissions-Policy: camera=(), microphone=(), geolocation=() が付くことを確認しました。
意外な点:directives で3つだけ指定しても、CSPには helmet の既定ディレクティブ(script-src など)が残ります。 これは useDefaults が既定で true のためです。指定したものだけにしたい場合は contentSecurityPolicy: { useDefaults: false, directives: {...} } とします。
Cloudflare
Cloudflareを前段に置いている場合は、オリジンを触らずにダッシュボードで付けられます。
| やりたいこと | 設定場所(2026年10月時点の公式ドキュメントの名称) |
|---|---|
| HSTSを有効にする | SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) |
| 任意のヘッダーを付ける・消す | Rules → Transform Rules → Response Header Transform Rules(Set static / Remove) |
| Pages・Workersの静的アセットに付ける | プロジェクト直下の _headers ファイル |
# _headers(Cloudflare Pages / Workers Static Assets)
/*
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-Policy: same-origin
オリジン(Nginxなど)とCloudflareの両方で同じヘッダーを付けると重複することがあるので、どちらで付けるかを1か所に決めます。Cloudflareの画面名は変わることがあるため、公式ドキュメントで確認してください(この記事では未操作)。
WordPress(functions.php)
テーマの functions.php(子テーマ推奨)に追加します。
<?php
// functions.php
add_action('send_headers', function () {
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: camera=(), microphone=(), geolocation=()');
header_remove('X-Powered-By');
});
send_headersはフロント側のページで呼ばれるフックです。画像・CSSなどの静的ファイルや wp-admin にはNginx/Apache側で付けるほうが確実です。- サーバー設定とfunctions.phpの両方で付けると、同じヘッダーが2つ返ることがあります。どちらか一方にそろえます。
- WordPress上での動作はこの記事では未実行です(PHPの
header()/header_remove()は標準関数)。
Nginxのadd_header継承の落とし穴を再現する
Nginxの add_header は「その階層に add_header が1つもないときだけ」上の階層から継承されます。 location に Cache-Control を1つ足しただけで、server に書いたセキュリティヘッダーがすべて消えます。
直す前:location で add_header を書く
server {
listen 7223;
root /tmp/t122html;
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
location /static/ {
add_header Cache-Control "public, max-age=86400";
}
}
実行結果(nginx 1.24.0、Date等は省略)です。
$ curl -sI localhost:7223/
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
$ curl -sI localhost:7223/static/a.css
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/css
Cache-Control: public, max-age=86400 ← nosniff と X-Frame-Options が消えた
$ curl -sI localhost:7223/nothing
HTTP/1.1 404 Not Found
Server: nginx
Content-Type: text/html ← always がないので404には付かない
消える理由は2つです。
| 現象 | 原因 | 直し方 |
|---|---|---|
/static/ でヘッダーが消える |
location に add_header があるため、server の add_header が継承されない |
location にも include で再掲する(1.29.3以降は add_header_inherit merge; も可) |
| 404・500で付かない | always なしの add_header は 200, 201, 204, 206, 301, 302, 303, 304, 307, 308 にだけ付く |
always を付ける |
直した後:include で再掲し always を付ける
前述の security_headers.conf を server と location の両方で include した結果です。
$ curl -skI https://localhost:7224/static/a.css | grep -cE "Strict|X-Content|X-Frame|Referrer|Permissions|Cross-Origin-Opener|Content-Security"
7
$ curl -skI https://localhost:7224/static/a.css | grep Cache-Control
Cache-Control: public, max-age=86400
$ curl -skI https://localhost:7224/nothing | head -1
HTTP/1.1 404 Not Found ← 404 にも同じ7つのヘッダーが付いた
Nginx 1.29.3 以降なら add_header_inherit merge; で上位の値を引き継げます(nginx公式ドキュメントで確認。1.24.0では使えないため未実行)。
# nginx 1.29.3+
location /static/ {
add_header_inherit merge;
add_header Cache-Control "public, max-age=86400" always;
}
設定を確認するコマンドとツール
| 方法 | コマンド・場所 | 確認できること |
|---|---|---|
| curl | curl -sI https://example.com/ |
レスポンスヘッダー全体 |
| curl(絞り込み) | curl -sI https://example.com/ | grep -iE "strict|content-security|x-frame" |
特定ヘッダーの有無 |
| curl(エラー時) | curl -sI https://example.com/not-found-page |
404にも付いているか(always の確認) |
| curl(静的ファイル) | curl -sI https://example.com/static/app.css |
locationごとに消えていないか |
| ブラウザ | DevTools → Network → 対象リクエスト → Headers | 実際にブラウザが受け取った値 |
| HTTP Observatory | MDN上の HTTP Observatory(developer.mozilla.org/observatory) | 公開サイトの総合評価と改善点 |
| 設定の静的チェック | gixy(Nginx設定の解析ツール) | add_header の再定義などを検出 |
URLを並べて一括チェックできるNode.jsスクリプトです(Node.js 18以降の組み込み fetch を使用)。
// check-headers.mjs 使い方: node check-headers.mjs https://example.com/
const url = process.argv[2];
const want = [
"strict-transport-security",
"content-security-policy",
"x-content-type-options",
"x-frame-options",
"referrer-policy",
"permissions-policy",
"cross-origin-opener-policy",
];
const unwanted = ["x-powered-by", "server"];
const res = await fetch(url, { redirect: "manual" });
console.log(`${res.status} ${url}`);
for (const h of want) console.log(res.headers.has(h) ? "OK " : "なし", h, res.headers.get(h) ?? "");
for (const h of unwanted) if (res.headers.has(h)) console.log("確認", h, res.headers.get(h));
上の「直す前」の Nginx の /static/a.css に実行すると、7項目すべてが「なし」、確認 server nginx と表示されました(直した後の 7224 番では7項目すべて「OK」)。
トップページだけでなく、静的ファイル・404ページ・APIのパスも1つずつ確認すると、継承の漏れに気づけます。
導入時のよくあるつまずきと対処
| 症状 | 原因 | 対処 |
|---|---|---|
| 一部のパスだけヘッダーがない | Nginxのadd_header継承 | location に include で再掲、always を付ける |
| 同じヘッダーが2つ返る | Nginx・アプリ・CDNの複数箇所で付与 | 付ける場所を1か所に決める(アプリ側で消すなら proxy_hide_header) |
| 外部サービスのiframe埋め込みが表示されない | 埋め込まれる側(自サイト)の X-Frame-Options / frame-ancestors | 許可したいオリジンを frame-ancestors に追加 |
| ポップアップログイン後に画面が戻らない | COOP same-origin で opener が切れた |
same-origin-allow-popups にする |
| HTTPのサブドメインが開けない | HSTS includeSubDomains |
サブドメインをHTTPS化するか、includeSubDomains を外して max-age 切れを待つ |
よくある質問
Q. セキュリティヘッダーはどれから入れればよいですか? A. 影響が小さく効果が大きい順に、X-Content-Type-Options、Referrer-Policy、X-Frame-Options(+frame-ancestors)、Permissions-Policy、HSTS(短いmax-ageから) がおすすめです。CSPは Report-Only で様子を見てから本番の値にします。
Q. X-Frame-Options は廃止されたのですか?
A. 廃止ではありません。より柔軟な CSP の frame-ancestors が推奨されていますが、古いブラウザ向けに X-Frame-Options も併記するのが一般的です。両方ある場合、対応ブラウザでは frame-ancestors が使われます。
Q. HSTSを入れたあとHTTPに戻したくなったら?
A. max-age=0 を HTTPS で返すと、次回アクセス時にブラウザの記憶が消えます。 ただし一度もアクセスしない利用者のブラウザには max-age の期間残ります。preload登録済みの場合は、リストからの削除が各ブラウザに届くまで数か月かかります。
Q. helmet を使えば Express は十分ですか?
A. 主要なヘッダーはそろいますが、Permissions-Policy は含まれず、CSP の既定値はサイトに合わせた調整が必要です。Referrer-Policy の既定 no-referrer も用途に合わせて見直します。
まとめ
- 基本の7つ(HSTS・最小CSP・nosniff・X-Frame-Options・Referrer-Policy・Permissions-Policy・COOP)を入れ、Server/X-Powered-Byを減らすのが出発点です。
- X-XSS-Protection は
0、Expect-CT と Public-Key-Pins は送らないのが現在の推奨です。 - HSTSは短い max-age から伸ばし、preload は全サブドメインのHTTPS運用を決めてから付けます。
- Nginxは location に add_header を書くと上位の設定が消えるので、include で再掲し
alwaysを付けます(1.29.3以降はadd_header_inherit merge)。 - Express は helmet 1行で多くがそろう一方、Permissions-Policy の追加と既定値の見直しが必要です。
- 確認は curl -I をトップ・静的ファイル・404で行うと、継承漏れに気づけます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「セキュリティヘッダー一覧|HSTS・CSPほか設定コード集」
株式会社RJCでは、Webサイトを安全に届けるための設定を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。