.htaccess Generator for WordPress

セキュリティヘッダー解説ガイド

EN

セキュリティヘッダーとは

セキュリティヘッダーは、サーバーがブラウザへのレスポンスに付与する HTTP ヘッダーのこと。ブラウザはこのヘッダーを読み取り、スクリプトの実行制限・通信の強制 HTTPS 化・iframe 埋め込みのブロックなど、様々なセキュリティ動作を自動的に行う。

アプリケーション側のコード(PHP・JavaScript)を変更せずに適用できるため、.htaccessによるサーバーレベルでの防御として広く使われる。

設定するメリット

  • コード変更不要で防御を追加できる — プラグインやテーマのコードに手を入れずに、サーバー設定だけで多くの攻撃を防げる。
  • ブラウザの安全機能を確実に有効化できる — 現代のブラウザはセキュリティ機能を多数持っているが、ヘッダーが指示しないと自動で有効にならないものが多い。
  • 脆弱性スキャンの評価が上がる — セキュリティ診断ツール(例: Mozilla Observatory、Security Headers)でのスコアが向上し、サイトの信頼性をアピールできる。
  • WordPress のプラグイン脆弱性の影響を局限できる — XSS 脆弱性を持つプラグインが攻撃されても、CSP や X-Content-Type-Options が第二の防御線として機能する。

注意点・デメリット

  • 設定ミスでサイトが壊れる可能性がある — 特に CSP は設定が厳しすぎると JavaScript・CSS・画像が読み込めなくなる。まず Report-Only モードでテストするのが安全。
  • プラグインとの競合が起きる場合がある — WordPress プラグインがセキュリティヘッダーを独自に出力している場合、 .htaccess と二重になる。プラグイン側の設定を無効化するか、どちらかに統一する。
  • HSTS は一度設定すると取り消しが難しい — ブラウザにキャッシュされるため、HTTP 運用に戻したい場合は max-age を短くしてから段階的に解除する必要がある。

ジェネレーターで設定できるヘッダー一覧

ヘッダー名 主な防御対象
Strict-Transport-Security (HSTS) HTTP→HTTPS ダウングレード攻撃・中間者攻撃(MITM)
Content-Security-Policy (CSP) XSS・Mixed Content・クリックジャッキング
X-Content-Type-Options MIME スニッフィングを悪用した XSS
X-Frame-Options クリックジャッキング
Referrer-Policy URL に含まれる機密情報の外部漏洩
Permissions-Policy カメラ・マイク等のデバイス API の不正利用

これらのヘッダーはすべて Apache のmod_headersモジュールで出力する。<IfModule mod_headers.c>でラップすることで、モジュールが無効な環境でも 500 エラーを防げる。


Header set vs Header always set

ディレクティブ 適用範囲
Header set 成功レスポンス(200系)のみ
Header always set エラーレスポンス(403 / 404 等)にも付与

セキュリティヘッダーは Header always set を使う。エラーページでもブラウザに安全な動作を指示するため、 always が必須。


HSTS(HTTP Strict Transport Security)

Header always set Strict-Transport-Security \
  "max-age=63072000; includeSubDomains; preload" \
  "expr=%{HTTPS} == 'on' || %{HTTP:X-Forwarded-Proto} == 'https'"

役割

ブラウザに「今後必ず HTTPS で接続してください」と宣言するヘッダー。

.htaccess の HTTPS リダイレクトはリクエストがサーバーに到達してからリダイレクトを行う。HSTS はブラウザ側で事前に HTTPS 化するため、 中間者攻撃(MITM)のリスクをゼロ にできる。

各パラメータの意味

パラメータ 意味
max-age=63072000 HSTS ポリシーを保持する期間(秒)。63072000 秒 = 2年
includeSubDomains サブドメインにも HSTS を適用する
preload ブラウザの HSTS プリロードリストへの登録を許可する(初回アクセスも HTTPS 化)

expr= 条件の役割(Nginx リバースプロキシ対応)

XServer・ConoHa WING などのレンタルサーバーは Nginx → Apache のリバースプロキシ構成を採用している。この場合、ユーザーが HTTPS でアクセスしてもサーバーの Apache は HTTP で受け取るため、 %{HTTPS} だけでは判定できない。

ユーザー →(HTTPS)→ Nginx(TLS終端) →(HTTP)→ Apache

expr= 条件は Apache の式(Expression)評価エンジンを使い、HTTPS 接続または X-Forwarded-Proto: https のどちらかが成立する場合のみヘッダーを送信する。これにより HTTP アクセス時に HSTS ヘッダーが送られることを防ぐ。

includeSubDomains を有効にする前に、すべてのサブドメインが HTTPS に対応していることを確認する。対応していないサブドメインにアクセスできなくなる。

ジェネレーターでは includeSubDomainspreload をそれぞれ個別に ON/OFF できる。サブドメインが HTTPS 非対応の場合は includeSubDomains を外す、プリロードリスト登録が不要なら preload を外す、といった調整が可能。


CSP(Content Security Policy)

Header always set Content-Security-Policy \
  "upgrade-insecure-requests; \
   script-src 'self' 'unsafe-inline' 'unsafe-eval'; \
   style-src 'self' 'unsafe-inline'; \
   img-src 'self' data:; \
   frame-src https://www.youtube.com https://www.google.com; \
   frame-ancestors 'self'"

upgrade-insecure-requests の役割

ページ内の http:// リソース(画像・CSS・JS など)を自動的に https:// に変換する指示。Mixed Content(HTTP と HTTPS が混在するページ)を自動修正するために使う。

ジェネレーターでは CSP を有効にすると upgrade-insecure-requests は常に出力される(削除不可)。その上で各ディレクティブを個別に ON/OFF できる。

ジェネレーターで設定できるディレクティブ

ディレクティブ 制限対象 デフォルト値
default-src 他のディレクティブのフォールバック(指定がない場合に適用) 'self'
script-src JavaScript の読み込み元 'self'
style-src CSS の読み込み元 'self'
img-src 画像の読み込み元 'self' data:
font-src フォントの読み込み元 'self'
connect-src Ajax / Fetch / WebSocket の接続先 'self'
frame-src iframe で読み込める外部 URL。YouTube / Google Maps のショートカットあり 'none'
frame-ancestors このページを iframe に埋め込める親 URL(X-Frame-Options の上位互換) 'self'

WordPress 固有の注意点

WordPress のコアや Gutenberg ブロックエディター、管理画面プラグインは多数のインラインスクリプト・インラインスタイルや eval() を使用するため、wp-admin では 'unsafe-inline' / 'unsafe-eval' が必要になる。

ジェネレーターは CSP 有効時に wp-admin / wp-login.php とフロントエンドを自動的に分岐出力する。フロントエンドは unsafe-* なしで厳格化しつつ、管理画面には必要な unsafe-* を付与した CSP を別途適用するため、両立が可能。

img-src と OG image の落とし穴

img-src を明示すると default-src のフォールバックが効かなくなる。以下のミスに注意する。

# NG: 'self' を入れ忘れると自サイト画像もブロックされる
img-src data:

# OK: 必ず 'self' を含める
img-src 'self' data:

OG image(SNS シェア時に使われるサムネイル画像)もブロック対象になる。 img-src を書くなら必ず 'self' を含める。

管理画面用 CSP 分岐(WordPress)

ジェネレーターで CSP を有効にすると、フロントエンドと wp-admin / wp-login.php で自動的に異なる CSP を出力する。フロントエンドは script-src / style-src から unsafe-* を除外し、wp-admin にはあらかじめ unsafe-inline / unsafe-eval を許可した CSP が適用される。

# フロントエンド(wp-admin・wp-login.php 以外)
<If "%{REQUEST_URI} !~ m#^/wp-(admin(?:/|$)|login\.php)#">
    Header always set Content-Security-Policy "upgrade-insecure-requests; ..."
</If>

# 管理画面・ログインページ
<If "%{REQUEST_URI} =~ m#^/wp-(admin(?:/|$)|login\.php)#">
    Header always set Content-Security-Policy "upgrade-insecure-requests; default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self'"
</If>

管理画面側の CSP はユーザーが選択したディレクティブをベースに動的生成される。 script-src には自動的に 'unsafe-inline''unsafe-eval' が、 style-src には 'unsafe-inline' のみが付与され、Gutenberg ブロックエディターやコアの管理画面が正常動作する。

script-src / style-src を有効にしていない場合でも、 default-src が有効なら管理画面 CSP はその値を継承して script-src / style-src を明示的に追加する。

Report-Only モード

いきなり本番環境に CSP を適用するとサイトが壊れるリスクがある。まず Content-Security-Policy-Report-Only ヘッダーでテストし、違反レポートを確認してから本番適用するのが安全。

# テスト用(実際のブロックは行わず、違反をレポートのみ)
Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report"

Report-Only モードでは upgrade-insecure-requests は無視される。これはアクション指示(リソースのアップグレード)であり、制限指示ではないため。本番 CSP にのみ含める。

DevTools で違反を確認する

Report-Only ヘッダーを設定した状態でサイトを表示し、ブラウザの DevTools(F12)→ Console タブを開く。CSP 違反が発生すると以下のようなエラーが出力される。

Refused to load the script 'https://www.googletagmanager.com/gtm.js'
because it violates the following Content Security Policy directive:
"script-src 'self'"

Console に出力された違反を確認し、許可すべきオリジンを各ディレクティブに追加してから Enforce モード( Content-Security-Policy )へ移行する。

違反レポートを収集する(report-uri / report-to)

CSP 違反レポートは、従来の report-uri に加えて、CSP Level 3 では report-toReporting-Endpoints (互換目的では Report-To )でも送信できる。 report-uri は現在も多くのブラウザで動作するが非推奨のため、DevTools だけでは把握しきれない実ユーザー環境での違反を収集する場合は新旧両方を併記するのが現実的。

Header always set Reporting-Endpoints "csp-endpoint=\"/wp-json/csp-report/v1/collect\""
Header always set Report-To "{\"group\":\"csp-endpoint\",\"max_age\":10886400,\"endpoints\":[{\"url\":\"https://example.com/wp-json/csp-report/v1/collect\"}]}"
Header always set Content-Security-Policy-Report-Only \
  "default-src 'self'; report-uri /wp-json/csp-report/v1/collect; report-to csp-endpoint"

Reporting-Endpoints は新しい書き方で、 Report-To は旧来の Reporting API ヘッダー。対応状況に差があるため、互換性を重視するなら両方を併記する。将来的には report-to 系への移行を前提にしつつ、当面は report-uri と併用するのが安全。

WordPress REST API で report-uri 向け受信エンドポイントを実装する例:

report-to (Reporting API)はボディ形式が異なり( application/reports+json ・配列 + type: "csp-violation" )、受信処理を別途実装する必要がある。以下の例は report-uri 専用。

add_action( 'rest_api_init', function () {
    register_rest_route( 'csp-report/v1', '/collect', array(
        'methods'             => 'POST',
        'callback'            => function ( WP_REST_Request $request ) {
            // Content-Type 検証(report-uri: application/csp-report または application/json)
            $content_type = (string) $request->get_header( 'Content-Type' );
            if ( false === strpos( $content_type, 'application/csp-report' )
                && false === strpos( $content_type, 'application/json' ) ) {
                return new WP_REST_Response( null, 415 );
            }
            // ボディサイズ上限(4KB)
            $body = $request->get_body();
            if ( strlen( $body ) > 4096 ) {
                return new WP_REST_Response( null, 413 );
            }
            $data   = json_decode( $body, true );
            $report = isset( $data['csp-report'] ) ? $data['csp-report'] : array();
            // 必要なフィールドのみ保存(document-uri / referrer は PII を含むため除外)
            $log = array(
                'blocked-uri'         => isset( $report['blocked-uri'] ) ? $report['blocked-uri'] : '',
                'violated-directive'  => isset( $report['violated-directive'] ) ? $report['violated-directive'] : '',
                'effective-directive' => isset( $report['effective-directive'] ) ? $report['effective-directive'] : '',
            );
            error_log( 'CSP violation: ' . wp_json_encode( $log ) );
            return new WP_REST_Response( null, 204 );
        },
        'permission_callback' => '__return_true',
    ) );
} );

CSP レポートは第三者が任意に POST できる。そのまま保存するとログ汚染・ログ肥大化(DoS)や URL・Referer などの個人情報(PII)の記録につながる。本番運用では document-uri / referrer など PII を含むフィールドの保存を避け、ボディサイズ上限・Content-Type 検証を必ず実施すること。大量のレポートが予想される場合は外部サービスへの委任も有効。

外部サービスを使う

自前でエンドポイントを用意せず、SaaS サービスに収集させる方法もある。

サービス 特徴
report-uri.com CSP・CT・HPKP レポートを収集・可視化。無料プランあり
Sentry エラー監視ツール。CSP レポートも受け付ける

X-Content-Type-Options

Header always set X-Content-Type-Options "nosniff"

役割と防御対象

ブラウザが MIME タイプを自動推測(MIME スニッフィング)する機能を無効にするヘッダー。

MIME スニッフィングを悪用した攻撃として、画像ファイルに偽装した JavaScript をアップロードしてブラウザに実行させる XSS 攻撃がある。 nosniff を指定すると、サーバーが返す Content-Type ヘッダーの値をブラウザが厳格に遵守するため、この攻撃を防げる。


X-Frame-Options

Header always set X-Frame-Options "SAMEORIGIN"

役割と防御対象

他サイトがこのサイトのページを<iframe> で埋め込むことを制限するヘッダー。クリックジャッキング攻撃を防止する。

クリックジャッキングとは、攻撃者が自分のサイトに対象サイトを透明な iframe で重ね、ユーザーに見えないボタンをクリックさせる攻撃手法。

設定値の意味

意味
SAMEORIGIN 同一オリジンからの埋め込みのみ許可(推奨)
DENY すべての埋め込みを禁止

ジェネレーターでは SAMEORIGIN (デフォルト)と DENY を選択できる。自サイトで iframe 埋め込みが不要なら DENY を選ぶとより厳格になる。


Referrer-Policy

Header always set Referrer-Policy "strict-origin-when-cross-origin"

役割

リンクを踏んで他サイトに移動したとき、ブラウザが Referer ヘッダーにどれだけの情報を含めるかを制御するヘッダー。URL には管理画面のパスや個人情報を含む場合があり、外部サイトへの漏洩を防ぐ。

選択可能なポリシー一覧

ジェネレーターでは以下の 8 つのポリシーから選択できる。デフォルトは strict-origin-when-cross-origin (推奨)。

ポリシー 動作
no-referrer リファラーを一切送信しない
no-referrer-when-downgrade HTTPS→HTTP ダウングレード時のみ非送信。それ以外はフル URL を送信
origin 常にオリジン(ドメイン)のみ送信
origin-when-cross-origin 同一オリジンはフル URL、クロスオリジンはオリジンのみ
same-origin 同一オリジンのみフル URL を送信。クロスオリジンは非送信
strict-origin オリジンのみ送信。ダウングレード時は非送信
strict-origin-when-cross-origin 同一オリジンはフル URL、クロスオリジンはオリジンのみ、ダウングレード時は非送信( 推奨
unsafe-url 常にフル URL を送信( 非推奨 — パス情報が外部に漏洩する)

strict-origin-when-cross-origin の動作詳細

移動先 送信内容
同一オリジン(自サイト内) 完全な URL(パスを含む)を送信
外部オリジン(https→https) オリジン部分のみ送信( https://example.com
外部オリジン(https→http) 何も送信しない

Permissions-Policy

Header always set Permissions-Policy \
  "camera=(), microphone=(), payment=(), usb=(), gyroscope=(), magnetometer=(), geolocation=()"

役割

ブラウザが持つ強力な API(カメラ・マイク・決済など)の使用を制限するヘッダー。WordPress サイトで通常使用しないデバイス API を無効化することで、マルウェアや XSS 攻撃が不正にこれらの API を呼び出すことを防ぐ。

空の括弧 () はそのディレクティブを完全に無効化することを意味する(すべてのオリジンからの使用を禁止)。

制御可能な機能一覧

ジェネレーターでは以下の機能を個別に ON/OFF できる。チェック ON で制限( feature=() を出力)、OFF でポリシーから除外(ブラウザデフォルトで許可)。なお geolocation のみ例外で、ON/OFF の 2 択ではなく「完全無効化 / Google マップ許可 / ポリシーから除外」の 3 段階で設定できる。

機能 説明 OFF にするケース
camera カメラ API ビデオ通話・QR コードリーダー等を使う場合
microphone マイク API 音声入力・通話機能を使う場合
payment Payment Request API Web Payment を使う EC サイト
usb WebUSB API USB デバイスとの通信が必要な場合
gyroscope ジャイロスコープ API デバイスの回転検知が必要な場合
magnetometer 磁力センサー API コンパス機能が必要な場合
geolocation 位置情報 API Google マップ等の地図表示・現在地取得を使う場合

geolocation の 3 段階設定

geolocation はジェネレーターで以下の 3 択から選択できる。

選択肢 生成される値 用途
完全無効化 geolocation=() 地図・現在地機能を一切使わないサイト(デフォルト)
Google マップ許可 geolocation=(self "https://www.google.com") Google マップの「現在地を表示」機能を使うサイト
ポリシーから除外 (出力なし) ブラウザデフォルトに委ねる場合

Google マップを埋め込んでいても「現在地を表示」ボタンを使わないサイトなら「完全無効化」のままで問題ない。「現在地を表示」が必要な場合のみ「Google マップ許可」を選ぶ。


防御マップ — 各ヘッダーの担当範囲

┌── HSTS ──────────── HTTP→HTTPS 強制(ブラウザレベル)
├── CSP ───────────── Mixed Content 自動修正 + リソース制限
├── X-Content-Type ── MIME 偽装による XSS 防止
├── X-Frame-Options ─ クリックジャッキング防止
├── Referrer-Policy ─ URL 情報の漏洩制御
└── Permissions ───── デバイス API の不正利用防止

これらのヘッダーはすべてレスポンスヘッダーとして mod_headers モジュールで出力する。 <IfModule mod_headers.c> でラップすることで、モジュールが無効な環境でも 500 エラーを防げる。

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" "expr=%{HTTPS} == 'on' || %{HTTP:X-Forwarded-Proto} == 'https'"
    Header always set Content-Security-Policy "upgrade-insecure-requests; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-src https://www.youtube.com https://www.google.com; 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 "camera=(), microphone=(), payment=(), usb=(), gyroscope=(), magnetometer=(), geolocation=()"
</IfModule>
← ジェネレーターに戻る