はじめに
WordPressを複数サイトで運用している際、サイト間で記事を埋め込もうとして「〇〇で拒否されました」というエラーメッセージに遭遇することがあります。筆者も先日、さくらインターネットのレンタルサーバーで運用しているサイトからのoEmbed埋め込みが、他のサイトから一貫して拒否されるという問題に直面しました。
本稿では、この問題の特定から、.htaccess
ファイルにおける各設定の機能解析、そして最終的な問題解決に至るまでのプロセスを詳細に解説します。複数サイト運用で同様の課題に直面している方や、X-Frame-Optionsおよび Content-Security-Policy に関する理解を深めたい方にとって、本稿が有益な情報源となることを期待しています。
環境構成と問題の特定
まず、筆者のウェブサイト環境と発生した具体的な問題について整理します。
本サイトの環境構成
-
サイトA:
https://url1.sakura.net/
(さくらインターネット スタンダードプラン、WordPress) -
サイトB:
https://url2.sakura.biz/
(さくらインターネット スタンダードプラン、WordPress) -
サイトC:
https://url3.xserver.net/
(Xserver、WordPress)
問題の発生状況
サイトC (https://url3.xserver.net/
) で記事を執筆中、サイトA (https://url1.sakura.net/
) で公開済みの関連記事をoEmbed機能を用いて埋め込もうと試みたところ、エディタ上で 「url1.sakura.net で拒否されました」 というエラーメッセージが表示され、埋め込みができませんでした。
このエラーメッセージが、詳細なデバッグ作業の契機となりました。
埋め込み可否の網羅的検証
問題の範囲を明確にするため、各サイト間でのoEmbed埋め込みの成功/失敗パターンを網羅的に検証しました。
埋め込みが不可能であったケース (NG)
- サイトC (
https://url3.xserver.net/
) に サイトA (https://url1.sakura.net/
) の記事を埋め込み - サイトC (
https://url3.xserver.net/
) に サイトB (https://url2.sakura.biz/
) の記事を埋め込み - サイトA (
https://url1.sakura.net/
) に サイトB (https://url2.sakura.biz/
) の記事を埋め込み - サイトB (
https://url2.sakura.biz/)
に サイトA (https://url1.sakura.net/
) の記事を埋め込み
埋め込みが可能であったケース (OK)
- サイトA (
https://url1.sakura.net/
) に サイトC (https://url3.xserver.net/
) の記事を埋め込み - サイトB (
https://url2.sakura.biz/
) に サイトC (https://url3.xserver.net/
) の記事を埋め込み
この検証結果から、共通の傾向が明らかになりました。さくらインターネットのレンタルサーバーで運用されているサイト(サイトAおよびサイトB)の記事を、他のサイトに埋め込もうとすると失敗するという事象です。 対照的に、Xserverで運用されているサイト(サイトC)の記事は、全てのサイトに埋め込みが可能でした。
この分析に基づき、問題の根源はさくらインターネット側のWordPressサイトのサーバー設定にある可能性が高いと判断されました。
原因の特定:「X-Frame-Options」ヘッダーの存在
問題の具体的な原因を特定するため、ChromeブラウザのWebデベロッパーツールを開き、Networkタブにて拒否されたリクエストの詳細を精査しました。
確認手順
- 埋め込みが実行できないサイト(例:
https://url3.xserver.net/
)にて、埋め込みを試みているページを開きます。 - F12キー(または右クリック→「検証」)を押下して開発者ツールを起動し、「Network」タブへ切り替えます。
- ページを再読み込みし、
url1.sakura.net
またはurl2.sakura.biz
へのリクエストを特定します。 - 該当リクエストを選択し、「Headers」タブ内の「Response Headers」セクションを確認します。
この調査の結果、以下の重要なエラーメッセージが確認されました。
Refused to display 'https://url1.sakura.net/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
このエラーメッセージは、問題の直接的な原因を明確に示唆していました。
「X-Frame-Options」 は、ウェブページが<iframe>
、<frame>
、<object>
などのHTML要素内に表示されることを許可するかどうかを制御するHTTPレスポンスヘッダーです。これは、クリックジャッキング攻撃を防ぐためのセキュリティ対策として機能します。
X-Frame-Options の主要な設定値
- DENY: いかなるサイトからのフレーム表示も許可しません。
-
SAMEORIGIN: 同一オリジン(ドメイン、プロトコル、ポートが全て一致する)からのフレーム表示のみを許可します。
- 補足: 同一オリジンとは、スキーム(httpまたはhttps)、ドメイン名(例: example.com)、ポート番号(例: 80や443)の全てが一致する場合を指します。一つでも異なると、異なるオリジンと見なされます。
- ALLOW-FROM uri: 指定されたURIからのフレーム表示のみを許可します(ただし、このディレクティブは一部の古いブラウザではサポートされておらず、現在では非推奨とされています)。
今回のエラーメッセージ「set 'X-Frame-Options' to 'sameorigin'
」から、さくらインターネット側のサイトが、自身のドメイン以外からのフレーム埋め込みを拒否する設定になっていたことが判明しました。WordPressのoEmbed機能は、埋め込みコンテンツのプレビュー表示や、一部の埋め込みパターンにおいて<iframe>
を利用する場合があります。この設定が埋め込み処理を阻害していたものと結論付けられました。さくらインターネットのレンタルサーバーでは、初期設定や特定の環境下においてX-Frame-Options: SAMEORIGIN が出力されるケースが多数確認されています。
解決策:X-Frame-Optionsの緩和とContent-Security-Policyの導入
原因が明確になったため、次なる段階として解決策の実施に着手しました。X-Frame-Optionsヘッダーを削除するか、ALLOW-FROMを用いて埋め込みを許可するドメインを明示的に指定する必要があります。
しかしながら、X-Frame-Optionsはセキュリティ上重要なヘッダーであり、安易な削除はクリックジャッキング等の攻撃に対する脆弱性を生じさせる可能性があります。また、ALLOW-FROMは一部のブラウザで非推奨とされているため、より現代的な標準である Content-Security-Policy (CSP) のframe-ancestors
ディレクティブを使用することが推奨されます。
そこで、筆者は以下の2つのサイトの.htaccess
ファイルに、新たな設定を追加することで問題を解決しました。既存のX-Frame-Optionsヘッダーは.htaccess
ファイルには直接記述されていませんでしたが、WordPress本体やプラグイン(特にWordfenceなどのセキュリティプラグイン)がPHPレベルで動的にこのヘッダーを追加している可能性があったため、Content-Security-Policyによってこれを上書きするアプローチを採用しました。
なお、X-Frame-OptionsとContent-Security-Policy: frame-ancestorsが両方存在する場合、ブラウザ実装によってはより制限の強い方が優先される、あるいは動作が異なる場合があるため、注意が必要です。
サイトA (https://url1.sakura.net/
) の.htaccessに追加した設定
<IfModule mod_headers.c>
# 複数オリジンを許可する場合の動的な設定 (こちらが推奨)
# 埋め込み元となるドメイン (Xserverサイトと、もう一方のさくらサイト) を正規表現で指定
SetEnvIfNoCase Origin "https?://(www\.)?(url3\.xserver\.net|url2\.sakura\.biz)$" ACAO_ORIGIN=$0
Header set Access-Control-Allow-Origin "%{ACAO_ORIGIN}e" env=ACAO_ORIGIN
Header merge Vary Origin
# X-Frame-Options の代わりに Content-Security-Policy を設定
# 'self' は自身のドメインを許可。続けて埋め込みを許可するドメインを指定。
# 既存の X-Frame-Options ヘッダーがPHPやプラグインで設定されている場合、
# このCSPが優先されることで問題を解決します。
Header set Content-Security-Policy "frame-ancestors 'self' https://url2.sakura.biz https://url3.xserver.net;"
</IfModule>
サイトB (https://url2.sakura.biz/
) の.htaccessに追加した設定
<IfModule mod_headers.c>
# 複数オリジンを許可する場合の動的な設定 (こちらが推奨)
# 埋め込み元となるドメイン (Xserverサイトと、もう一方のさくらサイト) を正規表現で指定
SetEnvIfNoCase Origin "https?://(www\.)?(url3\.xserver\.net|url1\.sakura\.net)$" ACAO_ORIGIN=$0
Header set Access-Control-Allow-Origin "%{ACAO_ORIGIN}e" env=ACAO_ORIGIN
Header merge Vary Origin
# X-Frame-Options の代わりに Content-Security-Policy を設定
# 'self' は自身のドメインを許可。続けて埋め込みを許可するドメインを指定。
# 既存の X-Frame-Options ヘッダーがPHPやプラグインで設定されている場合、
# このCSPが優先されることで問題を解決します。
Header set Content-Security-Policy "frame-ancestors 'self' https://url1.sakura.net https://url3.xserver.net;"
</IfModule>
設定の主要点
Access-Control-Allow-Origin (CORS設定)
-
SetEnvIfNoCase Origin ... ACAO_ORIGIN=$0
は、許可したいオリジン(本ケースではXserverサイトおよびもう一方のさくらサイト)からのリクエストにおいて、そのオリジンを環境変数ACAO_ORIGIN
に格納します。 -
Header set Access-Control-Allow-Origin "%{ACAO_ORIGIN}e" env=ACAO_ORIGIN
は、ACAO_ORIGIN
が存在する場合にのみ、その値をAccess-Control-Allow-Originヘッダーとして設定します。これにより、特定のオリジンからのクロスオリジンリクエストが許可されます。この設定は、oEmbed埋め込み問題の直接的な解決策として必須ではない場合もありますが、JavaScriptによるAPI連携など、将来的な広範なクロスオリジン通信の安全性を確保する上で予防的な対策として機能します。 -
Header merge Vary Origin
: Originヘッダーに基づいてコンテンツが変化する可能性があることをブラウザおよびキャッシュサーバーに通知します。これにより、キャッシュの不整合を効果的に防止します。
Content-Security-Policy (CSP) の frame-ancestors
- X-Frame-Optionsの代替として推奨されるCSPのディレクティブです。
-
frame-ancestors 'self' https://url2.sakura.biz https://url3.xserver.net;
のように設定することで、自身のドメインに加え、明示的に指定された他の許可ドメインからのフレーム埋め込みのみを許可します。これにより、不要な埋め込みを防ぎつつ、必要なサイト間連携を安全に実現します。
特記事項
- これらの設定は、
.htaccess
ファイルの# BEGIN WordPress
ブロックの直前に記述しました。 - 設定変更後には、WordPressのキャッシュプラグイン(W3 Total Cacheなど)のキャッシュをクリアし、ブラウザのキャッシュもハードリロード(Ctrl + F5やShift + F5など)によりクリアすることが不可欠です。
解決:スムーズな記事埋め込みの実現
これらの設定を.htaccess
ファイルに追加し、キャッシュをクリアした結果、全てのサイト間でWordPress記事のoEmbed埋め込みが円滑に行えるようになりました。
- サイトC (
https://url3.xserver.net/
) に サイトA (https://url1.sakura.net/
) の記事を埋め込み 成功 - サイトC (
https://url3.xserver.net/
) に サイトB (https://url2.sakura.biz/
) の記事を埋め込み 成功 - サイトA (
https://url1.sakura.net/
) に サイトB (https://url2.sakura.biz/
) の記事を埋め込み 成功 - サイトB (
https://url2.sakura.biz/
) に サイトA (https://url1.sakura.net/
) の記事を埋め込み 成功
これにより、サイト間のコンテンツ連携が大幅に効率化され、読者に対してより充実した情報提供が可能となりました。
まとめと今後の教訓
本トラブルシューティングを通じて、.htaccess
ファイルの重要性およびHTTPヘッダーがウェブサイトの挙動に与える影響の大きさを再認識しました。特に、複数のWordPressサイトや異なるレンタルサーバーを組み合わせて運用する場合、意図しないセキュリティヘッダーが設定されており、それがサイト間の連携を阻害するケースが存在することを学びました。
もし、同様にWordPressのoEmbed機能における記事埋め込みの課題、あるいは「拒否されました」というエラーに直面された際には、以下の点を確認されることを推奨します。
記事埋め込みトラブル時のチェック手順
-
ブラウザの開発者ツールでリクエストヘッダーを確認する
- 特にX-Frame-OptionsおよびContent-Security-Policyのヘッダー情報を詳細に検証してください。
-
.htaccessファイルの内容を精査する
- 意図しないセキュリティヘッダーが設定されていないか、あるいは必要なドメインが適切に許可されているかを確認してください。
-
WordPressプラグインの設定を確認する
- 特にWordfenceなどのセキュリティ関連プラグインが、X-Frame-OptionsなどのヘッダーをPHPレベルで動的に追加していないかを確認し、必要に応じてプラグインの設定を見直してください。
-
セキュリティを保ちながら、CSPの
frame-ancestors
で必要最小限の許可を設定する- 許可するドメインを増やしすぎると、クリックジャッキングなどのセキュリティリスクが拡大する可能性があるため、必要最小限のオリジンのみを許可するように設定することが重要です。
なお、サーバー環境やブラウザ仕様は将来的に変更される場合があります。レンタルサーバー提供会社の仕様変更通知や、WordPress・プラグインのアップデート内容も定期的に確認することを強く推奨いたします。
安易にセキュリティ設定を緩和するのではなく、Content-Security-Policyのframe-ancestors
のように、より詳細に許可するオリジンを指定できる方法を用いることが、セキュリティと利便性の両立を実現する鍵となります。