3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WordPress におけるセキュリティ関連ヘッダーの置き場所をどう考えるか

3
Posted at

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-PolicyStrict-Transport-SecurityCOOPCOEPCORP は、サイトの構成や外部サービス連携を壊す可能性があります。

まず結論

私なら、基本方針は次のように分けます。

置き場所 向いている用途
CDN / Cloudflare 複数サイト共通、WordPress 以外のアプリも含めた制御
Web サーバー設定 サーバー全体に共通で適用したいヘッダー
.htaccess Apache 系レンタルサーバーでサーバー本体設定を触れない場合
WordPress プラグイン 複数 WordPress で共通管理したい場合、管理画面から制御したい場合
テーマの functions.php 原則避ける。テーマ固有の一時対応ならあり
Cookie 発行処理 SameSiteSecureHttpOnly など 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 は通常のセキュリティヘッダーとは分けて考えます。

SameSiteSet-Cookie の属性です。

Set-Cookie: my_cookie=value; Path=/; Secure; HttpOnly; SameSite=Lax

そのため、Referrer-PolicyX-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-PolicyStrict-Transport-Security は、サイトごとの検証をしてから適用した方がよいです。

まとめ

WordPress におけるセキュリティヘッダーの置き場所は、次の考え方で整理できます。

広く共通化したい
  → CDN / Cloudflare / Web サーバー

サーバー設定を触れない
  → .htaccess / WordPress プラグイン

複数 WordPress で共通管理したい
  → WordPress プラグイン

WordPress 以外のアプリにも効かせたい
  → WordPress プラグインではなく CDN / サーバー

CSP のようにサイト別調整が必要
  → Report-Only から始める

SameSite Cookie
  → Cookie 発行元で個別に設定する

セキュリティヘッダーは「どこにでも書ける」からこそ、置き場所を決めるのが難しくなります。

大事なのは、ヘッダーを出すこと自体ではなく、どのレイヤーがその設定に責任を持つのかを決めることです。

WordPress サイトだけを見ればプラグイン管理は便利です。
しかし、複数アプリや複数サーバーを含めて考えるなら、Cloudflare や Web サーバー設定の方が自然な場合もあります。

「速さ」「管理しやすさ」「適用範囲」「検証しやすさ」を分けて考えると、セキュリティヘッダーの置き場所はかなり整理しやすくなります。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?