How can we help?

Tell us briefly what this is about - we will get back to you by email.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
To the start page

Own domain (first-party)

Serve widget, chat connection and offline form from a subdomain of your website – requirements, setup, templates for nginx, Apache and Caddy, troubleshooting.

With your own domain, the chat runs through a subdomain of your website, for example chat.your-domain.com. Your server accepts the widget's requests and passes them on to reactive.chat as a reverse proxy. This page is written for whoever runs that server.

What your own domain does

These run through your subdomain:

As a result:

What it is not

Requirements

Behind a load balancer or CDN, your proxy first sees that device's address instead of the visitor's. In that case, set up taking over the real visitor address: in nginx the real_ip module (set_real_ip_from, real_ip_header), in Apache mod_remoteip (RemoteIPHeader, RemoteIPTrustedProxy), in Caddy the trusted_proxies option and the {client_ip} placeholder instead of {remote_host}. Our check cannot detect this case: it only sees that a valid address is set, not which one.

Setup step by step

  1. Open the page. Open the project in your account. In the “Embed” section you will find the link Set up your own domain.
  2. Enter the host. Under “1. Host”, enter the name, for example chat.your-domain.com, and save.
  3. Create the key. Under “2. Proxy key”, click Create key. The key starts with rcpx_ and is shown exactly once; we only store a hash of it. Copy it right away. It is secret and belongs only in your proxy configuration – never in a web page, a browser script or a repository.
  4. Insert the template. Under “3. Server configuration” you will find ready-made templates for nginx, Apache and Caddy. Right after creating the key they already contain it; later they show __RC_PROXY_KEY__, which you replace with the key. Adjust the certificate paths if needed, test the configuration (nginx -t or apachectl configtest) and reload the server.
  5. Check. Under “5. Check”, click Check now. We call your subdomain from outside and check five routes separately (see below). No message is created. A new check is possible after 10 seconds at the earliest.
  6. Replace the snippet. Only when all five steps show “OK”, replace the current embed snippet on your website with the one from “4. Embed snippet”.

The new snippet looks like this – both addresses point to your subdomain:

<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>

The widget takes the location of its files from src, and the chat connection from data-rc-ws-url – and derives the offline form address from that. Other attributes such as data-rc-locale stay as they are; they are described under Embedding the widget. The snippet on the account page also carries your brand colour.

The five check steps

Each step shows “OK”, “Error”, “Note” or “not checked”. The check has passed only when all five show “OK”; a note does not count as passed. Checks use HTTPS on port 443 only, redirects are not followed, and each step has 5 seconds.

  1. Name resolution. The name must have a DNS record, and all its addresses must be public. It is reported if neither an A nor an AAAA record is found, or if at least one address is private, local or reserved – we do not check such addresses. All following steps connect to exactly the address checked here.
  2. loader.js via your domain. We load https://chat.your-domain.com/loader.js and compare the file with ours. If no connection, no answer or no valid certificate comes about, the remaining steps are skipped (“not checked”). If the proxy answers with a redirect instead of the file, that is an error. If the file differs from ours, the cause is usually a cache in the proxy or a wrong target for /.
  3. Key and visitor address. We call a check address under /rc_wss/. Our server answers whether it received a valid key and a valid visitor address, and which project the key belongs to. Possible errors: the X-RC-Proxy-Key header is missing; the key is unknown (mistyped, outdated or removed – right after creating it, it can take up to 30 seconds); the X-RC-Client-IP header is missing or contains no valid address; the key belongs to another project. A note appears if the key is valid but registered for a different host.
  4. Chat connection (WebSocket). We open a WebSocket connection via wss://chat.your-domain.com/rc_wss/ and expect HTTP 101 with the rc.v1 protocol. “No WebSocket upgrade” means: the proxy does not pass on the Upgrade and Connection headers.
  5. Offline form. We call https://chat.your-domain.com/api/offline/SITE_KEY with GET. Our endpoint answers with HTTP 405 because it only accepts POST – that tells us the request reaches us, without creating a message. Other results: origin rejected (a note, see troubleshooting), project not active, target requires authentication (HTTP 401).

Steps 2 to 5 can also report HTTP 421 (Host header, see troubleshooting). If step 3 or 5 reports “The answer does not come from reactive.chat”, this path points to a wrong target in your proxy.

What the proxy must forward

For all three routes:

For /rc_wss/ and /api/offline/ the proxy also sets two headers on every request:

“Sets” means: whatever a browser sends under these names is overwritten and never reaches us. On the route to the delivery network both headers are cleared; the key only goes to the chat server.

HTTP fallback through your domain

If a visitor's network does not let a WebSocket through, the widget automatically switches to the HTTP fallback. It uses the same path /rc_wss/ and needs no additional entry in the proxy. Two things are required:

The templates

The templates below match those on the account page. The account page already fills in your host, the target addresses valid for your account and – right after creating it – your key. So it is best to copy the template from there; the versions here are for reference. Placeholders are chat.your-domain.com, SITE_KEY and __RC_PROXY_KEY__.

nginx

Belongs in the http context, for example as /etc/nginx/conf.d/chat.your-domain.com.conf. On RHEL and CentOS the CA bundle is at /etc/pki/tls/certs/ca-bundle.crt instead of /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://dev.reactive.chat/rc_wss/h/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host dev.reactive.chat;
        proxy_ssl_server_name on;
        proxy_ssl_name dev.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://dev.reactive.chat/rc_wss/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host dev.reactive.chat;
        proxy_ssl_server_name on;
        proxy_ssl_name dev.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://dev.reactive.chat/api/offline/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host dev.reactive.chat;
        proxy_ssl_server_name on;
        proxy_ssl_name dev.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://dev.reactive.chat/api/suche/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host dev.reactive.chat;
        proxy_ssl_server_name on;
        proxy_ssl_name dev.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

The template contains only the VirtualHost for port 443. Set up the redirect from port 80 and the certificate as for your other hosts. SSLProxyVerifyDepth 5 is required: without it Apache checks the target's certificate chain only one level deep, and the connection fails with “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://dev.reactive.chat/rc_wss/" upgrade=websocket timeout=3600 flushpackets=on
    ProxyPass "/api/offline/" "https://dev.reactive.chat/api/offline/" timeout=30
    ProxyPass "/api/suche/"   "https://dev.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://dev.reactive.chat/rc_wss/$1" [P,L]
#   ProxyPass "/rc_wss/" "https://dev.reactive.chat/rc_wss/" timeout=3600 flushpackets=on

Caddy

Caddy obtains the certificate itself; WebSockets need no extra setting. header_up with a value replaces whatever the browser sent under that name.

# 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://dev.reactive.chat {
            header_up Host dev.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 dev.reactive.chat
            }
            flush_interval -1
        }
    }
    handle /api/offline/* {
        reverse_proxy https://dev.reactive.chat {
            header_up Host dev.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 dev.reactive.chat
            }
        }
    }
    handle /api/suche/* {
        reverse_proxy https://dev.reactive.chat {
            header_up Host dev.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 dev.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
            }
        }
    }
}

The proxy key

Troubleshooting

HTTP 421 (Misdirected Request)

Our server rejects the Host header: your proxy sends your own name instead of the target name. Host header and SNI must be the target's name – in nginx proxy_set_header Host plus proxy_ssl_server_name on with proxy_ssl_name, in Apache ProxyPreserveHost Off, in Caddy header_up Host and tls_server_name.

HTTP 502 or SSL errors towards the target

The proxy cannot establish the TLS connection to us or does not trust the certificate.

HTTP 413 when uploading attachments

Uploads go through /rc_wss/. Without a setting of its own nginx accepts only 1 MB; the template therefore sets client_max_body_size 32m in the /rc_wss/ section. If a load balancer or CDN sits in front of your proxy, it needs at least the same limit.

Chat connection without upgrade

The check reports “No WebSocket upgrade” with the HTTP status it received instead. The chat then continues over the HTTP fallback, but only connects after two failed WebSocket attempts.

HTTP 401 or 403 on the offline form

DNS points to a private address

The check reports a non-public address if at least one address of the name is private, local or reserved (for example 10.0.0.0/8, 192.168.0.0/16, 100.64.0.0/10 or fc00::/7). Common causes are a record from internal DNS or an internal load balancer. Your visitors cannot reach such addresses either; the public record must point to a public address.

Checking by hand

You can call the check address from step 3 yourself:

curl -s https://chat.your-domain.com/rc_wss/eigene-domain-probe

With a correct setup the answer is:

{"rc":"eigene-domain-probe","v":1,"schluessel":"gueltig","client_ip":"gesetzt","site":"SITE_KEY","host":"chat.your-domain.com"}

The values are German words: schluessel (key) is gueltig (valid), unbekannt (unknown) or fehlt (missing); client_ip is gesetzt (set), fehlt (missing) or ungueltig (invalid). site and host are only included with a valid key. The answer never contains an address.

Last updated: