Чем мы можем помочь?

Кратко опишите вопрос; мы ответим по электронной почте.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
На главную

Собственный домен (first-party)

Предоставляйте виджет, подключение чата и офлайн-форму через поддомен своего сайта: требования, настройка, шаблоны 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 и место для подстановки {client_ip} вместо {remote_host}. Наша проверка не может распознать этот случай: она видит лишь, что задан корректный адрес, но не какой именно.

Пошаговая настройка

  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. Замените код встраивания. Только когда все пять шагов показывают «OK», замените текущий код на сайте кодом из раздела «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, остаются прежними; они описаны в разделе Встраивание виджета. Код на странице аккаунта также содержит фирменный цвет.

Пять шагов проверки

Каждый шаг показывает «OK», «Ошибка», «Примечание» или «Не проверено». Проверка пройдена, только если все пять показывают «OK»; примечание не считается успехом. Проверки используют только 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). Мы открываем WebSocket-соединение через wss://chat.your-domain.com/rc_wss/ и ожидаем HTTP 101 с протоколом rc.v1. «Нет переключения на WebSocket» означает, что прокси не передаёт заголовки Upgrade и Connection.
  5. Офлайн-форма. Мы вызываем https://chat.your-domain.com/api/offline/SITE_KEY с GET. Наша конечная точка отвечает HTTP 405, потому что принимает только POST; так мы узнаём, что запрос дошёл, не создавая сообщение. Другие результаты: источник отклонён (примечание, см. устранение неполадок), проект неактивен, цель требует аутентификации (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/pki/tls/certs/ca-bundle.crt вместо /etc/ssl/certs/ca-certificates.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

Шаблон содержит только VirtualHost для порта 443. Настройте перенаправление с порта 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 МБ, поэтому шаблон задаёт client_max_body_size 32m в разделе /rc_wss/. Если перед прокси стоит балансировщик нагрузки или 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 включаются только при действительном ключе. Ответ никогда не содержит адреса.

Подробнее в документации

Подробное описание: Собственный домен и прокси первой стороны. Все главы собраны в документации.

Обновлено: