자체 도메인(퍼스트 파티)
웹사이트의 서브도메인으로 위젯, 채팅 연결과 오프라인 양식을 제공합니다. 요구 사항, 설정, nginx·Apache·Caddy 템플릿과 문제 해결을 설명합니다.
자체 도메인을 사용하면 채팅이 chat.your-domain.com 같은 웹사이트의 서브도메인을 통해 실행됩니다. 서버는 위젯의 요청을 받아 리버스 프록시로 reactive.chat에 전달합니다. 이 페이지는 해당 서버를 운영하는 사람을 위한 안내입니다.
자체 도메인의 역할
다음은 서브도메인을 통해 실행됩니다.
- 위젯 파일(
loader.js와 이후 로드되는 코어), - 첨부 파일을 포함한 채팅 연결(WebSocket),
- 채팅 연결이 성립하지 않을 때 대신 제공되는 오프라인 양식.
그 결과:
- 타사 도메인으로 요청하지 않습니다. 이 경로에서 방문자의 브라우저는 reactive.chat 도메인 대신 운영자의 서브도메인과만 통신합니다.
- 광고 차단기. 도메인 이름으로 차단하는 필터 목록은 reactive.chat 도메인으로 채팅을 인식하지 못합니다.
- Content Security Policy에 주소 하나를 사용합니다.
script-src와connect-src에 reactive.chat 주소 대신 운영자의 서브도메인을 넣습니다.connect-src에는 두 번 필요합니다. 채팅 연결용wss://와 오프라인 양식·업로드·HTTP 대체 연결(프록시가 WebSocket을 차단하는 경우)용https://를 지정합니다. - 실제 방문자 주소가 전달됩니다. 프록시가 비밀 키로 보호되는 전용 헤더로 방문자 주소를 당사에 전달합니다. 악용 방지(빈도 제한과 차단)는 프록시별이 아니라 방문자별로 집계되며 대화에 방문자 주소가 표시됩니다.
자체 도메인이 의미하지 않는 사항
- 자체 호스팅은 아닙니다. 서버는 전달만 합니다. 대화, 첨부 파일과 설정은 자체 도메인을 사용하지 않을 때와 똑같이 reactive.chat이 처리하고 저장합니다.
- 동의 여부를 판단하지 않습니다. 방문자의 동의가 필요한지, 어떤 처리에 필요한지는 별도 문제입니다. 자체 도메인을 사용해도 데이터 처리 주체는 바뀌지 않습니다. 개인정보 처리방침에 넣을 문구는 개인정보 처리방침용 문구에 있습니다.
- 서버에서 트래픽을 볼 수 있습니다. 방문자의 TLS 연결은 운영자의 프록시에서 종료됩니다. 메시지와 첨부 파일은 여기서 잠시 복호화된 뒤 다시 암호화하여 당사에 전달됩니다. 프록시에 요청 본문을 기록하지 않습니다.
요구 사항
- 자체 서버 또는 리버스 프록시가 인터넷에서 HTTPS 443 포트로 접근 가능하고 자체적으로 외부 HTTPS 연결을 열 수 있어야 합니다. nginx(ssl 모듈 포함), Apache 2.4.47 이상, Caddy 2용 템플릿을 제공합니다.
- Apache의 경우 mod_ssl, mod_proxy, mod_proxy_http, mod_proxy_wstunnel과 mod_headers 모듈이 필요합니다. Apache는 2.4.47부터
upgrade=websocket을 지원합니다. 이전 버전에는 템플릿 끝의 mod_rewrite를 사용하는 대체 방법이 있습니다. - 채팅 전용 서브도메인, 예를 들어
chat.your-domain.com이 필요합니다. 채팅은 호스트 전체를 사용하므로www.your-domain.com/chat/같은 경로는 작동하지 않습니다. 이름만 입력합니다.https://, 포트나 경로는 넣지 않습니다. IP 주소,.local,.lan,.internal같은 내부 도메인과 reactive.chat 하위 이름은 사용할 수 없습니다. - 웹사이트와 같은 도메인. 삽입된 웹사이트의 도메인에 속한 서브도메인일 때만 퍼스트 파티입니다. 그렇지 않아도 작동하지만 계정 페이지에 차이가 안내됩니다.
- 서버를 가리키는 DNS 레코드(A 및/또는 AAAA)가 필요합니다. 이름에 속한 모든 주소는 공인 주소여야 합니다.
- 서브도메인용 TLS 인증서가 필요합니다. nginx와 Apache 템플릿은 certbot(Let’s Encrypt) 경로를 전제로 하며 Caddy는 직접 인증서를 발급받습니다.
- reactive.chat 계정에서 프로젝트를 수정할 권한이 필요합니다.
로드 밸런서나 CDN 뒤에서는 프록시가 처음에 방문자 대신 해당 장치의 주소를 봅니다. 이 경우 실제 방문자 주소를 사용하도록 설정합니다. nginx는 real_ip 모듈(set_real_ip_from, real_ip_header), Apache는 mod_remoteip(RemoteIPHeader, RemoteIPTrustedProxy), Caddy는 trusted_proxies 옵션과 {remote_host} 대신 {client_ip} 자리 표시자를 사용합니다. 당사의 검사는 이 경우를 감지하지 못합니다. 유효한 주소가 설정되었는지만 확인하며 누구의 주소인지는 알 수 없습니다.
단계별 설정
- 페이지 열기. 계정에서 프로젝트를 엽니다. “삽입” 섹션에 자체 도메인 설정 링크가 있습니다.
- 호스트 입력. “1. 호스트”에
chat.your-domain.com같은 이름을 입력하고 저장합니다. - 키 생성. “2. 프록시 키”에서 키 생성을 클릭합니다. 키는
rcpx_로 시작하며 정확히 한 번만 표시됩니다. 당사는 해시만 저장합니다. 즉시 복사합니다. 비밀 정보이므로 프록시 설정에만 넣어야 합니다. 웹페이지, 브라우저 스크립트나 저장소에 넣어서는 안 됩니다. - 템플릿 삽입. “3. 서버 설정”에는 nginx, Apache와 Caddy용 템플릿이 있습니다. 키를 생성한 직후에는 키가 이미 들어 있으며, 나중에는
__RC_PROXY_KEY__로 표시되므로 키로 바꿉니다. 필요하면 인증서 경로를 조정하고 설정을 검사한 뒤(nginx -t또는apachectl configtest) 서버 설정을 다시 로드합니다. - 검사. “5. 검사”에서 지금 검사를 클릭합니다. 당사가 외부에서 서브도메인을 호출해 다섯 경로를 각각 검사합니다(아래 참조). 메시지는 생성하지 않습니다. 다시 검사하려면 최소 10초를 기다려야 합니다.
- 스니펫 교체. 다섯 단계 모두 “정상”일 때만 웹사이트의 기존 삽입 스니펫을 “4. 삽입 스니펫”의 것으로 교체합니다.
새 스니펫은 다음과 같습니다. 두 주소 모두 운영자의 서브도메인을 가리킵니다.
<script src="https://chat.your-domain.com/loader.js"
data-rc-site-key="SITE_KEY"
data-rc-ws-url="wss://chat.your-domain.com/rc_wss/"
async></script>
위젯은 src에서 파일 위치를, data-rc-ws-url에서 채팅 연결을 가져오며 여기에서 오프라인 양식 주소도 도출합니다. data-rc-locale 등의 다른 속성은 그대로 유지합니다. 위젯 삽입에 설명되어 있습니다. 계정 페이지의 스니펫에는 브랜드 색상도 포함됩니다.
다섯 검사 단계
각 단계에는 “정상”, “오류”, “안내” 또는 “검사하지 않음”이 표시됩니다. 다섯 단계가 모두 “정상”이어야 통과입니다. 안내는 통과로 간주하지 않습니다. 검사는 HTTPS 443 포트만 사용하며 리디렉션을 따르지 않고 단계별 제한 시간은 5초입니다.
- 이름 확인. DNS 레코드가 있어야 하며 모든 주소가 공인 주소여야 합니다. A와 AAAA 레코드가 모두 없거나 주소 하나라도 사설·로컬·예약 주소이면 안내합니다. 이런 주소는 검사하지 않습니다. 이후 모든 단계는 여기서 확인한 바로 그 주소에 연결합니다.
- 자체 도메인의 loader.js.
https://chat.your-domain.com/loader.js를 로드해 당사 파일과 비교합니다. 연결이나 응답을 얻지 못하거나 인증서가 유효하지 않으면 나머지 단계는 건너뜁니다(“검사하지 않음”). 프록시가 파일 대신 리디렉션을 반환하면 오류입니다. 파일이 다르면 대개 프록시 캐시 또는/의 잘못된 대상이 원인입니다. - 키와 방문자 주소.
/rc_wss/아래의 검사 주소를 호출합니다. 서버는 유효한 키와 방문자 주소를 받았는지, 키가 어떤 프로젝트에 속하는지 응답합니다. 가능한 오류:X-RC-Proxy-Key헤더 누락, 알 수 없는 키(오타·만료·제거된 키. 생성 직후에는 최대 30초 걸릴 수 있음),X-RC-Client-IP헤더 누락 또는 유효하지 않은 주소, 다른 프로젝트의 키입니다. 키가 유효하지만 다른 호스트에 등록되어 있으면 안내가 표시됩니다. - 채팅 연결(WebSocket).
wss://chat.your-domain.com/rc_wss/로 WebSocket 연결을 열고rc.v1프로토콜의 HTTP 101을 기대합니다. “WebSocket 업그레이드 없음”은 프록시가Upgrade와Connection헤더를 전달하지 않는다는 뜻입니다. - 오프라인 양식. GET으로
https://chat.your-domain.com/api/offline/SITE_KEY를 호출합니다. 엔드포인트는 POST만 허용하므로 HTTP 405로 응답합니다. 이는 메시지를 만들지 않고도 요청이 당사에 도달했음을 보여 줍니다. 다른 결과로는 출처 거부(안내, 문제 해결 참조), 비활성 프로젝트, 인증이 필요한 대상(HTTP 401)이 있습니다.
2–5단계에서는 HTTP 421도 표시될 수 있습니다(Host 헤더, 문제 해결 참조). 3단계나 5단계에 “reactive.chat에서 온 응답이 아닙니다”가 표시되면 프록시의 이 경로가 잘못된 대상을 가리킵니다.
프록시가 전달해야 하는 항목
/rc_wss/를 reactive.chat 채팅 서버로 전달합니다. WebSocket 업그레이드와 긴 시간 제한이 필요하며 템플릿은 3600초를 사용합니다. 파일 업로드도 같은 경로를 사용하며 요금제에 따라 파일당 최대 32 MB입니다./api/offline/을 같은 서버로 전달합니다. 위젯이 이 경로로 오프라인 양식을 보냅니다.- 그 밖의 모든 경로는 reactive.chat 배포 네트워크로 전달합니다.
loader.js와 다른 위젯 파일을 여기에서 제공합니다.
세 경로 모두에 적용됩니다.
- Host 헤더와 SNI는 대상 이름이어야 하며 운영자의 서브도메인이 아닙니다. 당사 서버는 알 수 없는 호스트에 HTTP 421로 응답합니다.
- 대상의 인증서를 검증합니다.
- 쿠키와 인증 정보는 전달하지 않습니다. 브라우저의
Cookie와Authorization헤더를 전달하지 않습니다. - 리디렉션은 사용하지 않습니다. 캐시는 필요하지 않습니다. 추가한다면
loader.js의 캐시 기간은 짧게 유지합니다. 파일 이름에 버전 번호가 없기 때문입니다.
/rc_wss/와 /api/offline/에서는 프록시가 모든 요청에 헤더 두 개를 설정합니다.
X-RC-Proxy-Key– 운영자의 키입니다.X-RC-Client-IP– 프록시에서 확인한 방문자 주소입니다.
“설정”은 브라우저가 이 이름으로 보낸 값을 덮어써서 당사에 도달하지 않도록 한다는 뜻입니다. 배포 네트워크로 가는 경로에서는 두 헤더를 지웁니다. 키는 채팅 서버에만 전달합니다.
자체 도메인의 HTTP 대체 연결
방문자의 네트워크가 WebSocket을 통과시키지 않으면 위젯은 자동으로 HTTP 대체 연결로 전환합니다. 같은 /rc_wss/ 경로를 사용하며 프록시에 추가 항목은 필요하지 않습니다. 두 가지가 필요합니다.
- 버퍼링 없이 응답 전달. 프록시는
/rc_wss/응답을 즉시 전달해야 합니다. nginx에서는proxy_buffering off와proxy_cache off, Apache에서는flushpackets=on, Caddy에서는flush_interval -1을 사용합니다. 아래 템플릿에 이미 포함되어 있습니다. - Content Security Policy의 HTTPS.
connect-src에는 서브도메인 주소의 두 형식, 즉wss://와https://, 둘 다 필요합니다. 예:connect-src 'self' wss://chat.your-domain.com https://chat.your-domain.com.
템플릿
아래 템플릿은 계정 페이지의 것과 일치합니다. 계정 페이지에서는 호스트, 계정에 유효한 대상 주소와 생성 직후의 키를 미리 채웁니다. 따라서 그곳에서 템플릿을 복사하는 것이 가장 좋습니다. 여기의 버전은 참고용입니다. 자리 표시자는 chat.your-domain.com, SITE_KEY, __RC_PROXY_KEY__입니다.
nginx
http 컨텍스트에 넣습니다. 예를 들어 /etc/nginx/conf.d/chat.your-domain.com.conf입니다. RHEL과 CentOS에서는 CA 번들이 /etc/ssl/certs/ca-certificates.crt 대신 /etc/pki/tls/certs/ca-bundle.crt에 있습니다.
# reactive.chat - own domain (first-party proxy) for site key SITE_KEY
# Host: chat.your-domain.com
#
# Needs nginx >= 1.7 with the ssl module. Put this file into the http
# context (e.g. /etc/nginx/conf.d/chat.your-domain.com.conf), adjust the certificate
# paths if you do not use certbot, then: nginx -t && systemctl reload nginx
#
# The proxy key is a secret: never put it into a web page or a repository.
# X-RC-Proxy-Key and X-RC-Client-IP are always SET here - whatever a browser
# sends under these names is overwritten.
server {
listen 80;
listen [::]:80;
server_name chat.your-domain.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-domain.com;
ssl_certificate /etc/letsencrypt/live/chat.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.your-domain.com/privkey.pem;
# Chat connection (WebSocket). Host header and SNI must be the upstream
# name - reactive.chat answers 421 to any other host.
# HTTP fallback (Server-Sent Events / long polling): its requests carry a
# short-lived connection token in the query string - EventSource cannot
# send headers. Same settings as /rc_wss/ below, but kept out of the
# access 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;
}
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;
# Unbuffered: if a visitor's network blocks WebSockets, the widget
# falls back to Server-Sent Events / long polling on this path.
proxy_buffering off;
proxy_cache off;
# File attachments are uploaded through this path as well (PUT
# /rc_wss/upload/...). nginx allows 1 MB by default; reactive.chat
# accepts up to 32 MB per file depending on the plan.
client_max_body_size 32m;
proxy_request_buffering off;
}
# Offline form fallback (HTTP POST from the widget).
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 for site search (suche.js and the HTTP API). Same rules as
# the offline route: key and visitor address are set here.
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;
# The answer is a stream (Server-Sent Events): pass it on unbuffered.
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 120s;
}
# Widget files (loader.js, core.*.js ...). No caching required; if you
# add a cache, keep it short for loader.js (it is not versioned).
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;
}
}
# On RHEL/CentOS the CA bundle is /etc/pki/tls/certs/ca-bundle.crt.
# Behind a load balancer/CDN, $remote_addr is the balancer: configure the
# real_ip module so that it holds the visitor's address.
Apache
템플릿에는 443 포트의 VirtualHost만 들어 있습니다. 80 포트에서의 리디렉션과 인증서는 다른 호스트처럼 설정합니다. SSLProxyVerifyDepth 5가 필요합니다. 이 설정이 없으면 Apache는 대상의 인증서 체인을 한 단계만 검사하므로 “Certificate Chain too long”으로 연결이 실패합니다.
# reactive.chat - own domain (first-party proxy) for site key SITE_KEY
# Host: chat.your-domain.com
#
# Needs Apache >= 2.4.47 with mod_ssl, mod_proxy, mod_proxy_http,
# mod_proxy_wstunnel, mod_headers and mod_setenvif:
# a2enmod ssl proxy proxy_http proxy_wstunnel headers setenvif
# Adjust the certificate paths if you do not use certbot, then:
# apachectl configtest && systemctl reload apache2
#
# The proxy key is a secret: never put it into a web page or a repository.
<VirtualHost *:443>
ServerName chat.your-domain.com
# HTTP fallback requests (/rc_wss/h/...) carry a short-lived connection
# token in the query string - keep them out of the access log.
SetEnvIf Request_URI "^/rc_wss/h/" rc_no_log
# ${APACHE_LOG_DIR} is set on Debian/Ubuntu; elsewhere e.g. /var/log/httpd.
CustomLog ${APACHE_LOG_DIR}/chat.your-domain.com_access.log combined env=!rc_no_log
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/chat.your-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/chat.your-domain.com/privkey.pem
# Upstream TLS: verified, SNI = upstream name (ProxyPreserveHost Off
# sends the upstream name as Host - reactive.chat answers 421 otherwise).
SSLProxyEngine on
SSLProxyVerify require
# Apache checks only ONE certificate deep by default; the upstream chain
# (leaf, intermediate, root) is longer: "Certificate Chain too long".
SSLProxyVerifyDepth 5
SSLProxyCheckPeerName on
SSLProxyCACertificateFile /etc/ssl/certs/ca-certificates.crt
ProxyRequests Off
ProxyPreserveHost Off
ProxyAddHeaders Off
# Whatever the browser sends under these names never reaches upstream.
RequestHeader unset X-RC-Proxy-Key early
RequestHeader unset X-RC-Client-IP early
RequestHeader unset Cookie early
RequestHeader unset Authorization early
# Longest path first. flushpackets=on on /rc_wss/: if a visitor's network
# blocks WebSockets, the widget falls back to Server-Sent Events / long
# polling on this path, and those must not be buffered.
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>
# Apache < 2.4.47 has no upgrade=websocket. Instead of the first 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
Caddy는 직접 인증서를 발급받습니다. WebSocket에는 별도 설정이 필요하지 않습니다. 값이 있는 header_up은 브라우저가 해당 이름으로 보낸 내용을 대체합니다.
# reactive.chat - own domain (first-party proxy) for site key SITE_KEY
# Host: chat.your-domain.com
#
# Caddy 2 obtains the certificate itself. WebSockets need no extra setting.
# The proxy key is a secret: never put it into a web page or a repository.
# header_up with a value REPLACES what the browser sent under that name.
# If you enable access logging (log directive), skip the HTTP fallback
# requests - they carry a short-lived token in the query string:
# log_skip /rc_wss/h/* (Caddy < 2.8: skip_log /rc_wss/h/*)
chat.your-domain.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
}
# The answer is a stream (Server-Sent Events): pass it on unbuffered.
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
}
}
}
}
프록시 키
- 교체. 새 키 생성으로 즉시 새 키를 발급합니다. 이전 키는 24시간 동안 유효하므로 중단 없이 새 키로 바꾼 뒤 다시 검사할 수 있습니다. 유효한 키는 최대 두 개입니다. 24시간 안에 다시 교체하면 가장 오래된 키는 즉시 무효화됩니다.
- 제거. 키 제거는 해당 키와 교체 후 아직 유효한 이전 키를 즉시 무효화합니다. 자체 도메인 제거는 호스트와 키를 함께 제거합니다. 먼저 웹사이트의 스니펫을 일반 형식으로 되돌립니다.
- 키가 없거나 잘못된 경우에도 채팅은 계속 작동합니다. 다만 두 헤더를 평가하지 않습니다. 모든 방문자가 프록시 주소로 표시됩니다. 악용 방지는 주소별로 연결과 요청을 집계하므로 프록시 전체를 빈도 제한하거나 차단해 모든 방문자가 동시에 영향을 받을 수 있습니다. 오프라인 양식에도 적용됩니다.
- 키 하나당 프로젝트 하나. 키는 정확히 하나의 프로젝트와 등록된 호스트에 속합니다. 유효한 키가 설정된 프록시를 통해 다른 프로젝트의 사이트 키를 가진 위젯이 연결하면 채팅 연결을 거부합니다. 추가 프로젝트에는 자체 키가 있는 별도 서브도메인을 설정합니다.
- 키가 유출된 경우: 키를 아는 사람은 해당 프로젝트에 임의의 방문자 주소를 지정할 수 있습니다. 키를 제거한 뒤 새로 생성해 프록시에 넣습니다. 그때까지 채팅은 프록시 주소로 계속 작동합니다.
문제 해결
HTTP 421(Misdirected Request)
당사 서버가 Host 헤더를 거부합니다. 프록시가 대상 이름 대신 자체 이름을 보낸 것입니다. Host 헤더와 SNI는 대상 이름이어야 합니다. 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 또는 SSL 오류
프록시가 당사와 TLS 연결을 설정하지 못하거나 인증서를 신뢰하지 않습니다.
- Apache: 오류 로그에 “Certificate Chain too long”이 있으면
SSLProxyVerifyDepth 5가 빠진 것입니다. - nginx:
proxy_ssl_trusted_certificate의 경로가 올바른가요(RHEL과 CentOS는 위 참조)?proxy_ssl_server_name on이 설정되어 있나요? - 일반: 서버가 443 포트로 외부 연결을 열 수 있어야 합니다.
첨부 파일 업로드 시 HTTP 413
업로드는 /rc_wss/를 통합니다. 별도 설정이 없으면 nginx는 1 MB만 허용합니다. 따라서 템플릿은 /rc_wss/ 섹션에 client_max_body_size 32m을 설정합니다. 프록시 앞에 로드 밸런서나 CDN이 있으면 최소한 같은 한도가 필요합니다.
업그레이드 없는 채팅 연결
검사에 “WebSocket 업그레이드 없음”과 대신 받은 HTTP 상태가 표시됩니다. 채팅은 HTTP 대체 연결로 계속되지만 WebSocket 시도가 두 번 실패한 후에야 연결됩니다.
- nginx:
/rc_wss/섹션에proxy_http_version 1.1,proxy_set_header Upgrade $http_upgrade,proxy_set_header Connection "upgrade"가 필요합니다. - Apache: 2.4.47 이상에서
upgrade=websocket과 mod_proxy_wstunnel이 활성화되어 있어야 합니다. 이전 버전은 템플릿 끝의 대체 방법을 사용합니다. - 앞단: 프록시 앞의 CDN이나 로드 밸런서가 WebSocket을 통과시켜야 합니다.
오프라인 양식의 HTTP 401 또는 403
- 401: 경로 중간에서 인증을 요구합니다. 대개 프록시 호스트의 비밀번호 보호 때문입니다.
/api/offline/은 인증 없이 접근할 수 있어야 합니다. - 403, 출처 관련 안내: 요청은 당사에 도달하지만 출처가 프로젝트와 일치하지 않습니다. 프로젝트의 “허용된 출처”에는 웹사이트의 도메인을 넣어야 합니다. 채팅 서브도메인이 아니라 채팅이 삽입된 도메인입니다.
- 403과 “reactive.chat에서 온 응답이 아닙니다”: 웹 애플리케이션 방화벽이나 서버의 접근 규칙 등 앞단에서 거부한 것입니다.
DNS가 사설 주소를 가리키는 경우
이름의 주소 중 하나라도 사설·로컬·예약 주소(예: 10.0.0.0/8, 192.168.0.0/16, 100.64.0.0/10, fc00::/7)이면 검사에서 공인 주소가 아니라고 안내합니다. 흔한 원인은 내부 DNS 레코드나 내부 로드 밸런서입니다. 방문자도 이런 주소에 접근할 수 없으므로 공개 레코드는 공인 주소를 가리켜야 합니다.
수동 검사
3단계의 검사 주소를 직접 호출할 수 있습니다.
curl -s https://chat.your-domain.com/rc_wss/eigene-domain-probe
올바른 설정의 응답은 다음과 같습니다.
{"rc":"eigene-domain-probe","v":1,"schluessel":"gueltig","client_ip":"gesetzt","site":"SITE_KEY","host":"chat.your-domain.com"}
값은 독일어입니다. schluessel(키)은 gueltig(유효), unbekannt(알 수 없음), fehlt(없음) 중 하나입니다. client_ip는 gesetzt(설정됨), fehlt(없음), ungueltig(유효하지 않음) 중 하나입니다. site와 host는 유효한 키가 있을 때만 포함됩니다. 응답에는 주소가 포함되지 않습니다.
문서에서 더 알아보기
자세한 내용: 자체 도메인과 퍼스트 파티 프록시. 모든 장은 문서에서 확인할 수 있습니다.
최종 업데이트: