独自ドメインとファーストパーティープロキシ
Webサイトのサブドメインからウィジェット、チャット接続、オフラインフォーム、Smart Answerを配信:プロキシキー、実際の訪問者アドレス、nginx・Apache・Caddy用テンプレート。
独自ドメインでは、chat.your-company.comなど、Webサイトのサブドメインを通してチャットを運用します。運営側のサーバーがウィジェットのリクエストを受け、リバースプロキシとしてreactive.chatに転送します。このページはサーバーの管理者向けです。編集担当者向けの短い説明は、独自ドメイン(ファーストパーティー)にあります。
サブドメインを経由するもの
- ウィジェットファイル –
loader.js、後から読み込むコア(core.<hash>.js)、その他のモジュール。 - チャット接続 –
/rc_wss/のWebSocket、/rc_wss/h/のHTTPフォールバック、/rc_wss/upload/のアップロード。 - オフラインフォーム – チャット接続を確立できない場合の
POST /api/offline/<SITE_KEY>。 - Smart Answer – サイト内検索のAI回答を使う場合の
/api/suche/。
得られる効果:
- 第三者のドメインへのリクエストがありません。 これらの経路では、訪問者のブラウザーは運営側のサブドメインとだけ通信します。
- Content Security Policyのアドレスを1つにできます。
script-srcとconnect-srcには、reactive.chatの代わりに運営側のサブドメインを指定します(セキュリティとCSP参照)。 - 実際の訪問者のアドレスが届きます。 プロキシが、秘密のキーで保護した専用ヘッダーで伝えます。頻度制限とブロックは、プロキシではなく訪問者ごとに数えます。
次の点は変わりません。
- セルフホスティングではありません。 会話、添付ファイル、設定は、引き続きreactive.chatが処理・保存します。
- 同意の要否を示すものではありません。 独自ドメインでも、データの処理者は変わりません。
- プロキシには通信が見えます。 TLSはプロキシで終端します。リクエスト本文をログに記録せず、
/rc_wss/h/もアクセスログから除外してください。これらのリクエストは、クエリに短期の接続トークンを含みます。
要件
- ポート443のHTTPSで公開アクセスでき、外向きのHTTPS接続も開始できるサーバーまたはリバースプロキシ。nginx(sslモジュール付き)、Apache 2.4.47以降、Caddy 2のテンプレートがあります。
- Apacheには
mod_ssl、mod_proxy、mod_proxy_http、mod_proxy_wstunnel、mod_headers、mod_setenvifが必要です。 - チャット専用のサブドメイン。 ホスト全体が必要で、
www.your-company.com/chat/のようなパスは使えません。https://, ポート、パスを付けず、名前だけを入力します。IPアドレス、.local、.lan、.internalなどの内部用の末尾、reactive.chat配下の名前は使えません。 - Webサイトと同じドメイン。 埋め込み先と同じドメインに属するサブドメインの場合だけ、ファーストパーティーになります。
- DNS(A、AAAAのいずれか、または両方)のすべてのアドレスが公開アドレスであること。
- サブドメインのTLS証明書(nginx/Apacheのテンプレートはcertbotのパスを想定。Caddyは自動取得)。
- プロジェクトを編集する権限。
設定
- アカウントでプロジェクトを開き、埋め込み → 独自ドメインを設定に進みます。
- 1. ホストに
chat.your-company.comなどの名前を入力し、保存します。 - 2. プロキシキーでキーを作成をクリックします。キーは
rcpx_で始まり、表示は1回だけです。保存するのはハッシュだけです。プロキシ設定のみに置き、Webページ、ブラウザーのスクリプト、リポジトリには絶対に含めないでください。 - 3. サーバー設定でサーバー用のテンプレートを取得します。キーの作成直後にはキーが入り、その後は
__RC_PROXY_KEY__を表示します。nginx -tまたはapachectl configtestで設定をテストし、サーバーを再読み込みします。 - 5. 確認で今すぐ確認をクリックします(最大10秒に1回)。メッセージは作成しません。
- 5つの確認がすべて「OK」になった場合だけ、4. 埋め込みスニペットのものに置き換えます。
<script src="https://chat.your-company.com/loader.js"
data-rc-site-key="SITE_KEY"
data-rc-ws-url="wss://chat.your-company.com/rc_wss/"
async></script>
ウィジェットはsrcからファイルの場所を、data-rc-ws-urlからチャット接続を取得し、そこからオフラインフォーム、アップロード、HTTPフォールバックのアドレスを導きます。他の属性はすべてそのままです(scriptタグのリファレンス)。
プロキシが転送する内容
/rc_wss/→ チャットサーバーhttps://ws.reactive.chat/rc_wss/, WebSocketアップグレード、長いタイムアウト(テンプレートは3,600秒)、バッファリングなし、本文上限32 MB以上(アップロード)。/api/offline/→https://ws.reactive.chat/api/offline/(小さな本文。テンプレートは32 kB、30秒)。/api/suche/→https://ws.reactive.chat/api/suche/, バッファリングなし(応答はServer-Sent Eventsストリーム)、本文8 kBまで、120秒。- その他すべて → 配信ネットワーク
https://cdn.reactive.chat/.
すべての経路で、次を適用します。
- HostヘッダーとSNIには転送先の名前を指定します。 運営側のサブドメインではありません。不明なホストにはHTTP 421を返します。
- 転送先の証明書を検証します。
- Cookieや認証情報は転送しません。 ブラウザーの
CookieとAuthorizationを転送せず、Set-Cookieも返しません。 - リダイレクトしません。 キャッシュは不要です。設定する場合、
loader.jsには短い期間を指定してください。ファイル名にバージョンがありません(コアにはあります。構成参照)。
/rc_wss/、/api/offline/、/api/suche/では、各リクエストに2つのヘッダーを設定します。
- X-RC-Proxy-Key – 運営側のキー
rcpx_…。 - X-RC-Client-IP – プロキシから見た訪問者のアドレス。
「設定」とは、ブラウザーが同じ名前で送った値を上書きすることです。配信ネットワークへの経路では、両方を削除します。
ロードバランサーやCDNの背後では、最初に見えるのは機器のアドレスです。実際のアドレスを引き継ぐよう設定してください。nginxはreal_ip(set_real_ip_from、real_ip_header)、Apacheはmod_remoteip(RemoteIPHeader、RemoteIPTrustedProxy)、Caddyはtrusted_proxiesと、{remote_host}の代わりに{client_ip}を使います。この状況は確認機能では検出できず、有効なアドレスが設定されていることだけが分かります。
nginxのテンプレート
アカウントページではホスト、転送先アドレス、キーが入力済みなので、そちらからコピーすることをお勧めします。ここは参照用で、プレースホルダーはchat.your-company.com、SITE_KEY、__RC_PROXY_KEY__です。RHEL/CentOSのCAバンドルは/etc/pki/tls/certs/ca-bundle.crtにあります。
server {
listen 80;
listen [::]:80;
server_name chat.your-company.com;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name chat.your-company.com;
ssl_certificate /etc/letsencrypt/live/chat.your-company.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.your-company.com/privkey.pem;
# HTTP fallback (SSE / long polling): token in the query, do not log
location /rc_wss/h/ {
access_log off;
proxy_pass https://ws.reactive.chat/rc_wss/h/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_cache off;
}
# Chat connection (WebSocket) and uploads
location /rc_wss/ {
proxy_pass https://ws.reactive.chat/rc_wss/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_cache off;
client_max_body_size 32m;
proxy_request_buffering off;
}
# Offline form
location /api/offline/ {
proxy_pass https://ws.reactive.chat/api/offline/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
client_max_body_size 32k;
proxy_read_timeout 30s;
}
# Smart Answer (website search), answer as a stream
location /api/suche/ {
proxy_pass https://ws.reactive.chat/api/suche/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
client_max_body_size 8k;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 120s;
}
# Widget files
location / {
proxy_pass https://cdn.reactive.chat/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host cdn.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name cdn.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "";
proxy_set_header X-RC-Client-IP "";
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
}
}
Apacheのテンプレート(2.4.47以降)
ポート443のVirtualHostだけです。ポート80のリダイレクトと証明書は、他のホストと同じように設定します。SSLProxyVerifyDepth 5が必要です。ない場合、Apacheは転送先のチェーンを1段階しか検証せず、「Certificate Chain too long」を報告します。
<VirtualHost *:443>
ServerName chat.your-company.com
SetEnvIf Request_URI "^/rc_wss/h/" rc_no_log
CustomLog ${APACHE_LOG_DIR}/chat.your-company.com_access.log combined env=!rc_no_log
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/chat.your-company.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/chat.your-company.com/privkey.pem
SSLProxyEngine on
SSLProxyVerify require
SSLProxyVerifyDepth 5
SSLProxyCheckPeerName on
SSLProxyCACertificateFile /etc/ssl/certs/ca-certificates.crt
ProxyRequests Off
ProxyPreserveHost Off
ProxyAddHeaders Off
RequestHeader unset X-RC-Proxy-Key early
RequestHeader unset X-RC-Client-IP early
RequestHeader unset Cookie early
RequestHeader unset Authorization early
ProxyPass "/rc_wss/" "https://ws.reactive.chat/rc_wss/" upgrade=websocket timeout=3600 flushpackets=on
ProxyPass "/api/offline/" "https://ws.reactive.chat/api/offline/" timeout=30
ProxyPass "/api/suche/" "https://ws.reactive.chat/api/suche/" timeout=120 flushpackets=on
ProxyPass "/" "https://cdn.reactive.chat/"
<Location "/rc_wss/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
</Location>
<Location "/api/offline/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
Header unset Set-Cookie
</Location>
<Location "/api/suche/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
Header unset Set-Cookie
</Location>
</VirtualHost>
2.4.47より前のApacheはupgrade=websocketに対応しません。その場合、最初のProxyPass行を次に置き換えます。
RewriteEngine on
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/rc_wss/(.*) "wss://ws.reactive.chat/rc_wss/$1" [P,L]
ProxyPass "/rc_wss/" "https://ws.reactive.chat/rc_wss/" timeout=3600 flushpackets=on
Caddy 2のテンプレート
値付きのheader_upは、ブラウザーが同じ名前で送った内容を置き換えます。アクセスログを有効にする場合は、/rc_wss/h/*を除外します(log_skip、Caddy 2.8より前はskip_log)。
chat.your-company.com {
handle /rc_wss/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
transport http {
tls_server_name ws.reactive.chat
}
flush_interval -1
}
}
handle /api/offline/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name ws.reactive.chat
}
}
}
handle /api/suche/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name ws.reactive.chat
}
flush_interval -1
}
}
handle {
reverse_proxy https://cdn.reactive.chat {
header_up Host cdn.reactive.chat
header_up -X-RC-Proxy-Key
header_up -X-RC-Client-IP
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name cdn.reactive.chat
}
}
}
}
5つの確認ステップ
各ステップには「OK」「エラー」「注意」「未確認」が表示されます。「OK」が5つの場合だけ合格です。確認はポート443のHTTPSのみで、リダイレクトなし、各5秒です。
- 名前解決 – A/AAAAがあり、すべて公開アドレスであること。以降のすべてのステップは、そのアドレスに正確に接続します。
- 独自ドメイン経由のloader.js – ファイルを読み込み、当社のものと比較します。接続、応答、有効な証明書がなければ、残りをスキップします。リダイレクトはエラーです。ファイルが異なる場合は、キャッシュや
/の転送先の誤りが考えられます。 - キーと訪問者のアドレス –
/rc_wss/eigene-domain-probeを呼び出します。考えられるエラーは、X-RC-Proxy-Keyがない、キーが不明(作成直後は最大30秒の遅延)、X-RC-Client-IPがないか無効、別のプロジェクトのキー、というものです。キーは有効でも別のホストに登録されている場合は、注意を表示します。 - WebSocket –
wss://chat.your-company.com/rc_wss/が、HTTP 101とサブプロトコルrc.v1で応答する必要があります。 - オフラインフォーム –
GET /api/offline/SITE_KEYが405で応答する必要があります(POSTのみを受け付けるため、メッセージを作成せずに確認できます)。
ステップ3の確認用アドレスを自分で呼び出すこともできます。
curl -s https://chat.your-company.com/rc_wss/eigene-domain-probe
正しく設定されていれば、次の応答になります。
{"rc":"eigene-domain-probe","v":1,"schluessel":"gueltig","client_ip":"gesetzt","site":"SITE_KEY","host":"chat.your-company.com"}
- schluessel –
gueltig(有効)、unbekannt(不明)、fehlt(なし)。 - client_ip –
gesetzt(設定済み)、fehlt(なし)、ungueltig(無効)。 - site、host – キーが有効な場合だけです。応答にアドレスは含みません。
プロキシキー
- ローテーション: 新しいキーを作成は即座に新しいキーを発行し、以前のものは24時間有効です。有効なキーは最大2つで、24時間以内に再度ローテーションすると、最も古いものが即座に無効になります。
- 削除: キーを削除は、ローテーション中の古いものも含めて直ちに無効にします。独自ドメインを削除はホストとキーをまとめて削除します。先にWebサイトのスニペットを戻してください。
- キーがない、または誤っている: チャットは続行しますが、2つのヘッダーを無視します。全訪問者がプロキシのアドレスになり、不正利用対策はアドレス単位なので、プロキシ全体、つまり全訪問者を同時に頻度制限する場合があります。
- 1キー1プロジェクト: 有効なキーのプロキシ経由で、別のプロジェクトのサイトキーを持つウィジェットが接続すると、拒否します。別のプロジェクトには、独立したサブドメインとキーが必要です。
- キーの漏えい: キーを知る人は、プロジェクトに任意の訪問者アドレスを申告できます。削除し、新規作成して設定してください。
トラブルシューティング
- HTTP 421(Misdirected Request): プロキシが転送先ではなく自身の名前をHostに送っています。nginx:
proxy_set_header Hostとproxy_ssl_server_name on/proxy_ssl_name。Apache:ProxyPreserveHost Off。Caddy:header_up Hostとtls_server_name。 - 転送先とのHTTP 502/TLSエラー: Apacheに
SSLProxyVerifyDepth 5がない、nginxのproxy_ssl_trusted_certificateのパスが誤っているかproxy_ssl_server_name onがない、外向きHTTPSがブロックされている、などです。 - アップロードでHTTP 413: nginxは未設定では1 MBのみです。
/rc_wss/セクションにclient_max_body_size 32mを設定し、手前のロードバランサーにも同じかそれ以上の上限を設定します。 - WebSocketへのアップグレードなし:
Upgrade/Connectionを転送していません。チャットはHTTPフォールバックで続行しますが、WebSocketが2回失敗してから接続します。 - オフラインフォームで401:
/api/offline/の前にパスワード保護があります。 - オリジンに関する注意付きの403: プロジェクトの許可するオリジンに、Webサイトのドメインがありません(チャットのサブドメインではありません)。
- 403「reactive.chatからの応答ではありません」: 転送先の手前のファイアウォールやアクセスルールが拒否しています。
- DNSがプライベートアドレスを指す(
10.0.0.0/8、192.168.0.0/16、100.64.0.0/10、fc00::/7など):公開レコードには公開アドレスが必要です。
次:通信とプロトコル · セキュリティとCSP
最終更新: