WordPress サイトでセキュリティ関連の HTTP ヘッダーを設定しようとすると、意外と悩みます。
.htaccess に書くべきなのか。
Nginx や Caddy の設定に書くべきなのか。
Cloudflare 側で設定するべきなのか。
WordPress プラグインで管理するべきなのか。
functions.php に書いてもよいのか。
この記事では、WordPress におけるセキュリティヘッダーの「置き場所」を整理します。
対象にするヘッダー
ここでいうセキュリティ関連ヘッダーとは、たとえば次のようなものです。
| ヘッダー | 役割 |
|---|---|
Strict-Transport-Security |
HTTPS 利用をブラウザに記憶させる |
Content-Security-Policy |
読み込める JS / CSS / 画像 / iframe などを制御する |
Content-Security-Policy-Report-Only |
CSP を適用せず、違反だけを報告する |
X-Content-Type-Options |
MIME sniffing を抑制する |
Referrer-Policy |
Referer 情報の送信範囲を制御する |
X-Frame-Options |
iframe 埋め込みを制御する |
Permissions-Policy |
カメラ、マイク、位置情報などのブラウザ機能を制御する |
Cross-Origin-Opener-Policy |
クロスオリジンの window 関係を制御する |
Cross-Origin-Embedder-Policy |
クロスオリジンリソースの埋め込みを制御する |
Cross-Origin-Resource-Policy |
他オリジンからのリソース利用を制御する |
一見すると「全部入れれば安全」になりそうですが、実際にはそう単純ではありません。
とくに Content-Security-Policy、Strict-Transport-Security、COOP、COEP、CORP は、サイトの構成や外部サービス連携を壊す可能性があります。
まず結論
私なら、基本方針は次のように分けます。
| 置き場所 | 向いている用途 |
|---|---|
| CDN / Cloudflare | 複数サイト共通、WordPress 以外のアプリも含めた制御 |
| Web サーバー設定 | サーバー全体に共通で適用したいヘッダー |
.htaccess |
Apache 系レンタルサーバーでサーバー本体設定を触れない場合 |
| WordPress プラグイン | 複数 WordPress で共通管理したい場合、管理画面から制御したい場合 |
テーマの functions.php
|
原則避ける。テーマ固有の一時対応ならあり |
| Cookie 発行処理 |
SameSite、Secure、HttpOnly など Cookie 属性 |
重要なのは、ヘッダーの置き場所は「どこで設定できるか」ではなく「どの範囲に責任を持たせるか」で決めることです。
レイヤーで考える
WordPress サイトのリクエストは、ざっくり次のようなレイヤーを通ります。
Browser
↓
CDN / WAF / Cloudflare
↓
Web Server
↓
PHP-FPM / mod_php
↓
WordPress
↓
Theme / Plugin
セキュリティヘッダーは、どのレイヤーでも付けられる場合があります。
ただし、下のレイヤーで付けるほど WordPress 固有の判断はしにくくなり、上のレイヤーで付けるほど WordPress 以外には効きません。
CDN / WAF
→ 広く効く。WordPress 以外にも効く。高速。
Web Server
→ サーバー配下に効く。高速。設定権限が必要。
WordPress Plugin
→ WordPress ごとに管理しやすい。PHP 実行後なので遅い。
Theme
→ テーマ依存になる。保守上は弱い。
Cloudflare に置く場合
Cloudflare を使っているなら、セキュリティヘッダーの共通管理レイヤーとしてかなり有力です。
Cloudflare 側で設定するメリットは次です。
- WordPress より前段で処理できる
- PHP を起動しなくてよい
- Apache / Nginx / LiteSpeed の違いを吸収できる
- WordPress 以外のアプリにも適用できる
- 複数サイトで共通ルール化しやすい
たとえば、複数の WordPress、静的サイト、独自 PHP アプリ、API を Cloudflare 配下に置いているなら、Cloudflare 側で最低限のヘッダーを設定するのは自然です。
Cloudflare
├─ WordPress A
├─ WordPress B
├─ 静的サイト
└─ 独自 API
この場合、WordPress プラグインだけでヘッダーを管理すると、WordPress 以外のアプリには効きません。
Cloudflare 側で置きやすいものは、たとえば次です。
X-Content-Type-Options
Referrer-Policy
Permissions-Policy
X-Frame-Options
Strict-Transport-Security
Content-Security-Policy-Report-Only
Content-Security-Policy
ただし、Content-Security-Policy はサイトごとの差が大きいので、全サイト一律で厳しい設定にするのは危険です。
Web サーバー設定に置く場合
自分で Nginx、Apache、Caddy などを管理できるなら、Web サーバー設定に書くのもよい選択です。
たとえば Nginx なら次のような形です。
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Apache の本体設定なら、たとえば次のようになります。
<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 X-Frame-Options "SAMEORIGIN"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
Web サーバー設定に置く利点は、WordPress を起動する前に処理できることです。
HTTP リクエスト
↓
Web サーバーでヘッダー付与
↓
必要なら WordPress へ
PHP や WordPress プラグインまで到達しないので、速度や責任分界としてはきれいです。
ただし、レンタルサーバーでは本体設定を触れないことがあります。その場合は .htaccess や WordPress プラグインが現実解になります。
.htaccess に置く場合
Apache 系のレンタルサーバーでは、.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 X-Frame-Options "SAMEORIGIN"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
.htaccess は WordPress プラグインより前に処理されるため、ヘッダーを付けるだけなら PHP より軽いです。
ただし、Apache はリクエストごとに .htaccess を確認するため、サーバー本体設定よりは効率が落ちます。
そのため、優先順位としては次のように考えます。
サーバー本体設定を触れる
→ サーバー設定
サーバー本体設定は触れないが .htaccess は使える
→ .htaccess
サーバー種別がバラバラ、WordPress 管理画面で制御したい
→ WordPress プラグイン
WordPress プラグインに置く場合
複数の WordPress を管理するなら、WordPress プラグインで共通管理するのも現実的です。
とくに次のような場合です。
- レンタルサーバーが多い
- Apache / Nginx / LiteSpeed が混在している
- サーバー設定を触れない案件がある
- WordPress 管理画面から設定したい
- サイトごとに ON / OFF したい
- CSP を Report-Only から段階的に試したい
WordPress 側でヘッダーを追加するなら、専用プラグインで wp_headers フィルターを使う方法があります。
<?php
/**
* Plugin Name: Example Security Headers
*/
add_filter( 'wp_headers', function ( array $headers ) {
$headers['X-Content-Type-Options'] = 'nosniff';
$headers['Referrer-Policy'] = 'strict-origin-when-cross-origin';
$headers['X-Frame-Options'] = 'SAMEORIGIN';
$headers['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()';
if ( is_ssl() ) {
$headers['Strict-Transport-Security'] = 'max-age=31536000';
}
return $headers;
} );
ただし、WordPress プラグイン方式には弱点もあります。
- WordPress が起動しないレスポンスには効かない
- 静的キャッシュにより WordPress が実行されない場合がある
- CDN キャッシュ済みレスポンスには反映されない場合がある
- WordPress 以外のアプリには効かない
- PHP 実行後なのでサーバー設定より遅い
つまり、WordPress プラグインは「WordPress サイトごとの運用管理」には向いていますが、「インフラ全体の標準化」には向いていません。
functions.php に置くべきか
テーマの functions.php にヘッダー設定を書くことも技術的にはできます。
add_filter( 'wp_headers', function ( array $headers ) {
$headers['X-Content-Type-Options'] = 'nosniff';
return $headers;
} );
しかし、基本的にはおすすめしません。
理由は、セキュリティヘッダーはテーマの責任ではないからです。
テーマ
→ 見た目、テンプレート、ブロックスタイル
プラグイン
→ 機能、設定、セキュリティ関連の処理
サーバー / CDN
→ サイト全体・複数アプリ共通の制御
テーマを変更したらセキュリティヘッダーが消える、という状態は保守上あまりよくありません。
一時対応や検証用ならありですが、継続運用するならプラグイン、サーバー設定、Cloudflare などに移した方がよいです。
SameSite Cookie は別枠で考える
SameSite は通常のセキュリティヘッダーとは分けて考えます。
SameSite は Set-Cookie の属性です。
Set-Cookie: my_cookie=value; Path=/; Secure; HttpOnly; SameSite=Lax
そのため、Referrer-Policy や X-Content-Type-Options のように、単純なレスポンスヘッダーとして一律追加するものではありません。
自作プラグインで Cookie を発行するなら、その Cookie を発行するコードで指定します。
setcookie(
'my_cookie',
$value,
[
'expires' => time() + HOUR_IN_SECONDS,
'path' => '/',
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
]
);
他プラグインや WordPress 本体の Cookie を後段で一律に書き換えるのは、ログイン、外部認証、iframe 埋め込み、会員機能などに影響する可能性があります。
SameSite は、基本的には Cookie 発行元で個別に考えるべきです。
ヘッダー別の置き場所
実務上は、ヘッダーごとに置き場所を分けると整理しやすいです。
| ヘッダー | 置き場所の候補 | コメント |
|---|---|---|
X-Content-Type-Options |
CDN / サーバー / .htaccess / プラグイン |
低リスクで共通化しやすい |
Referrer-Policy |
CDN / サーバー / .htaccess / プラグイン |
サイト方針に応じて選ぶ |
Permissions-Policy |
CDN / サーバー / .htaccess / プラグイン |
使わない機能を閉じる用途 |
X-Frame-Options |
CDN / サーバー / .htaccess / プラグイン |
iframe 埋め込み要件に注意 |
Content-Security-Policy |
CDN / サーバー / プラグイン | サイト別調整が必要 |
Content-Security-Policy-Report-Only |
CDN / サーバー / プラグイン | 検証段階に向く |
Strict-Transport-Security |
CDN / サーバー | HTTPS 運用が確実な場合だけ |
COOP / COEP / CORP
|
CDN / サーバー / プラグイン | 外部リソースとの衝突に注意 |
SameSite |
Cookie 発行処理 | 通常ヘッダーとは別枠 |
HSTS は慎重に扱う
Strict-Transport-Security、つまり HSTS は強力です。
一度ブラウザに記憶されると、その期間中は HTTP ではなく HTTPS でアクセスしようとします。
Strict-Transport-Security: max-age=31536000; includeSubDomains
とくに includeSubDomains は注意が必要です。
たとえば次のような構成の場合、サブドメインもすべて HTTPS で正常に動く必要があります。
example.com
www.example.com
admin.example.com
old.example.com
dev.example.com
開発用や古いサブドメインが HTTP のままだと、includeSubDomains によって壊れる可能性があります。
最初は短い max-age で検証し、問題がなければ伸ばす方が安全です。
Strict-Transport-Security: max-age=86400
CSP は Report-Only から始める
Content-Security-Policy は、セキュリティヘッダーの中でも特に強力ですが、最も壊れやすいヘッダーでもあります。
WordPress サイトでは、次のような外部リソースを使うことが多いです。
Google Tag Manager
Google Analytics
Google Fonts
YouTube
Google Maps
reCAPTCHA
Stripe
PayPal
SNS 埋め込み
広告タグ
CDN
問い合わせフォーム
そのため、最初から本番 CSP を強く適用すると、フォーム、地図、動画、解析タグ、ブロックエディタなどが壊れる可能性があります。
まずは Content-Security-Policy-Report-Only で違反を確認し、必要な許可リストを整理してから本番適用するのがよいです。
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint
キャッシュとの関係
WordPress でセキュリティヘッダーを考えるときは、キャッシュも重要です。
Cloudflare キャッシュ
Nginx FastCGI cache
LiteSpeed Cache
ページキャッシュプラグイン
静的 HTML キャッシュ
WordPress プラグインでヘッダーを付けている場合、キャッシュ済みのレスポンスでは WordPress が実行されないことがあります。
その場合、プラグインでヘッダーを追加しているつもりでも、実際のレスポンスには出ていない可能性があります。
確認は curl -I やブラウザの DevTools で行います。
curl -I https://example.com/
トップページだけでなく、投稿詳細、固定ページ、ログイン画面、管理画面、REST API、キャッシュ対象外ページも確認した方がよいです。
自分ならどう置くか
個人的には、次の分担が一番わかりやすいです。
Cloudflare / CDN
→ 複数サイト共通の基本ヘッダー
→ WordPress 以外のアプリも含める場合
Web サーバー設定
→ サーバー単位で共通にしたいヘッダー
→ 自分でサーバー管理できる場合
.htaccess
→ Apache 系レンタルサーバーでの現実解
WordPress プラグイン
→ WordPress ごとに設定したい場合
→ 複数 WordPress で共通管理したい場合
→ 管理画面から ON / OFF したい場合
functions.php
→ 原則避ける
→ 検証や一時対応ならあり
Cookie 発行元
→ SameSite / Secure / HttpOnly
最小構成の例
まず入れやすい低リスクなヘッダーだけなら、次のようなところから始められます。
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: SAMEORIGIN
ただし、X-Frame-Options は iframe 埋め込み要件がある場合に注意が必要です。
また、Content-Security-Policy と Strict-Transport-Security は、サイトごとの検証をしてから適用した方がよいです。
まとめ
WordPress におけるセキュリティヘッダーの置き場所は、次の考え方で整理できます。
広く共通化したい
→ CDN / Cloudflare / Web サーバー
サーバー設定を触れない
→ .htaccess / WordPress プラグイン
複数 WordPress で共通管理したい
→ WordPress プラグイン
WordPress 以外のアプリにも効かせたい
→ WordPress プラグインではなく CDN / サーバー
CSP のようにサイト別調整が必要
→ Report-Only から始める
SameSite Cookie
→ Cookie 発行元で個別に設定する
セキュリティヘッダーは「どこにでも書ける」からこそ、置き場所を決めるのが難しくなります。
大事なのは、ヘッダーを出すこと自体ではなく、どのレイヤーがその設定に責任を持つのかを決めることです。
WordPress サイトだけを見ればプラグイン管理は便利です。
しかし、複数アプリや複数サーバーを含めて考えるなら、Cloudflare や Web サーバー設定の方が自然な場合もあります。
「速さ」「管理しやすさ」「適用範囲」「検証しやすさ」を分けて考えると、セキュリティヘッダーの置き場所はかなり整理しやすくなります。