무엇을 도와드릴까요?

문의 내용을 간단히 알려 주세요. 이메일로 답변드릴게요.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
홈으로

자체 도메인(퍼스트 파티)

웹사이트의 서브도메인으로 위젯, 채팅 연결과 오프라인 양식을 제공합니다. 요구 사항, 설정, nginx·Apache·Caddy 템플릿과 문제 해결을 설명합니다.

자체 도메인을 사용하면 채팅이 chat.your-domain.com 같은 웹사이트의 서브도메인을 통해 실행됩니다. 서버는 위젯의 요청을 받아 리버스 프록시로 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. 페이지 열기. 계정에서 프로젝트를 엽니다. “삽입” 섹션에 자체 도메인 설정 링크가 있습니다.
  2. 호스트 입력. “1. 호스트”에 chat.your-domain.com 같은 이름을 입력하고 저장합니다.
  3. 키 생성. “2. 프록시 키”에서 키 생성을 클릭합니다. 키는 rcpx_로 시작하며 정확히 한 번만 표시됩니다. 당사는 해시만 저장합니다. 즉시 복사합니다. 비밀 정보이므로 프록시 설정에만 넣어야 합니다. 웹페이지, 브라우저 스크립트나 저장소에 넣어서는 안 됩니다.
  4. 템플릿 삽입. “3. 서버 설정”에는 nginx, Apache와 Caddy용 템플릿이 있습니다. 키를 생성한 직후에는 키가 이미 들어 있으며, 나중에는 __RC_PROXY_KEY__로 표시되므로 키로 바꿉니다. 필요하면 인증서 경로를 조정하고 설정을 검사한 뒤(nginx -t 또는 apachectl configtest) 서버 설정을 다시 로드합니다.
  5. 검사. “5. 검사”에서 지금 검사를 클릭합니다. 당사가 외부에서 서브도메인을 호출해 다섯 경로를 각각 검사합니다(아래 참조). 메시지는 생성하지 않습니다. 다시 검사하려면 최소 10초를 기다려야 합니다.
  6. 스니펫 교체. 다섯 단계 모두 “정상”일 때만 웹사이트의 기존 삽입 스니펫을 “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초입니다.

  1. 이름 확인. DNS 레코드가 있어야 하며 모든 주소가 공인 주소여야 합니다. A와 AAAA 레코드가 모두 없거나 주소 하나라도 사설·로컬·예약 주소이면 안내합니다. 이런 주소는 검사하지 않습니다. 이후 모든 단계는 여기서 확인한 바로 그 주소에 연결합니다.
  2. 자체 도메인의 loader.js. https://chat.your-domain.com/loader.js 를 로드해 당사 파일과 비교합니다. 연결이나 응답을 얻지 못하거나 인증서가 유효하지 않으면 나머지 단계는 건너뜁니다(“검사하지 않음”). 프록시가 파일 대신 리디렉션을 반환하면 오류입니다. 파일이 다르면 대개 프록시 캐시 또는 /의 잘못된 대상이 원인입니다.
  3. 키와 방문자 주소. /rc_wss/ 아래의 검사 주소를 호출합니다. 서버는 유효한 키와 방문자 주소를 받았는지, 키가 어떤 프로젝트에 속하는지 응답합니다. 가능한 오류: X-RC-Proxy-Key 헤더 누락, 알 수 없는 키(오타·만료·제거된 키. 생성 직후에는 최대 30초 걸릴 수 있음), X-RC-Client-IP 헤더 누락 또는 유효하지 않은 주소, 다른 프로젝트의 키입니다. 키가 유효하지만 다른 호스트에 등록되어 있으면 안내가 표시됩니다.
  4. 채팅 연결(WebSocket). wss://chat.your-domain.com/rc_wss/로 WebSocket 연결을 열고 rc.v1 프로토콜의 HTTP 101을 기대합니다. “WebSocket 업그레이드 없음”은 프록시가 Upgrade와 Connection 헤더를 전달하지 않는다는 뜻입니다.
  5. 오프라인 양식. 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/와 /api/offline/에서는 프록시가 모든 요청에 헤더 두 개를 설정합니다.

“설정”은 브라우저가 이 이름으로 보낸 값을 덮어써서 당사에 도달하지 않도록 한다는 뜻입니다. 배포 네트워크로 가는 경로에서는 두 헤더를 지웁니다. 키는 채팅 서버에만 전달합니다.

자체 도메인의 HTTP 대체 연결

방문자의 네트워크가 WebSocket을 통과시키지 않으면 위젯은 자동으로 HTTP 대체 연결로 전환합니다. 같은 /rc_wss/ 경로를 사용하며 프록시에 추가 항목은 필요하지 않습니다. 두 가지가 필요합니다.

템플릿

아래 템플릿은 계정 페이지의 것과 일치합니다. 계정 페이지에서는 호스트, 계정에 유효한 대상 주소와 생성 직후의 키를 미리 채웁니다. 따라서 그곳에서 템플릿을 복사하는 것이 가장 좋습니다. 여기의 버전은 참고용입니다. 자리 표시자는 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
            }
        }
    }
}

프록시 키

문제 해결

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 연결을 설정하지 못하거나 인증서를 신뢰하지 않습니다.

첨부 파일 업로드 시 HTTP 413

업로드는 /rc_wss/를 통합니다. 별도 설정이 없으면 nginx는 1 MB만 허용합니다. 따라서 템플릿은 /rc_wss/ 섹션에 client_max_body_size 32m을 설정합니다. 프록시 앞에 로드 밸런서나 CDN이 있으면 최소한 같은 한도가 필요합니다.

업그레이드 없는 채팅 연결

검사에 “WebSocket 업그레이드 없음”과 대신 받은 HTTP 상태가 표시됩니다. 채팅은 HTTP 대체 연결로 계속되지만 WebSocket 시도가 두 번 실패한 후에야 연결됩니다.

오프라인 양식의 HTTP 401 또는 403

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는 유효한 키가 있을 때만 포함됩니다. 응답에는 주소가 포함되지 않습니다.

문서에서 더 알아보기

자세한 내용: 자체 도메인과 퍼스트 파티 프록시. 모든 장은 문서에서 확인할 수 있습니다.

최종 업데이트: