Kif nistgħu ngħinuk?

Għidilna fil-qosor x'ġara — inwieġbuk bl-email.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Mur fil-paġna ewlenija

Eigen domein (first-party)

Widget, chatverbinding en offlineformulier aanbieden via een subdomein van uw website: vereisten, inrichting, sjablonen voor nginx, Apache en Caddy, problemen oplossen.

Met een eigen domein loopt de chat via een subdomein van uw website, bijvoorbeeld chat.your-domain.com. Uw server ontvangt de verzoeken van de widget en geeft ze als reverse proxy door aan reactive.chat. Deze pagina is bedoeld voor degene die die server beheert.

Wat een eigen domein doet

Via uw subdomein lopen:

Het resultaat:

Wat het niet is

Vereisten

Achter een loadbalancer of CDN ziet uw proxy eerst het adres van dat apparaat in plaats van de bezoeker. Stel dan het overnemen van het echte bezoekersadres in: bij nginx de real_ip-module (set_real_ip_from, real_ip_header), bij Apache mod_remoteip (RemoteIPHeader, RemoteIPTrustedProxy), bij Caddy de optie trusted_proxies en de tijdelijke aanduiding {client_ip} in plaats van {remote_host}. Onze controle kan dit geval niet herkennen: ze ziet alleen dat een geldig adres is ingesteld, niet welk adres.

Stap voor stap instellen

  1. De pagina openen. Open het project in uw account. In het gedeelte “Insluiten” vindt u de link Eigen domein instellen.
  2. De host invoeren. Voer onder “1. Host” de naam in, bijvoorbeeld chat.your-domain.com, en sla op.
  3. De sleutel aanmaken. Klik onder “2. Proxysleutel” op Sleutel aanmaken. De sleutel begint met rcpx_ en wordt precies één keer getoond; wij bewaren alleen een hash. Kopieer hem meteen. Hij is geheim en hoort alleen in uw proxyconfiguratie — nooit op een webpagina, in een browserscript of in een repository.
  4. Het sjabloon invoegen. Onder “3. Serverconfiguratie” staan kant-en-klare sjablonen voor nginx, Apache en Caddy. Direct na het aanmaken bevatten ze de sleutel al; later tonen ze __RC_PROXY_KEY__, dat u door de sleutel vervangt. Pas zo nodig de certificaatpaden aan, test de configuratie (nginx -t of apachectl configtest) en herlaad de server.
  5. Controleren. Klik onder “5. Controleren” op Nu controleren. Wij benaderen uw subdomein van buitenaf en controleren vijf routes afzonderlijk (zie hieronder). Er wordt geen bericht aangemaakt. Een nieuwe controle kan op zijn vroegst na 10 seconden.
  6. Het insluitfragment vervangen. Vervang pas wanneer alle vijf stappen “OK” tonen het huidige insluitfragment op uw website door dat uit “4. Insluitcode”.

Het nieuwe fragment ziet er zo uit — beide adressen verwijzen naar uw subdomein:

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

De widget haalt de locatie van zijn bestanden uit src en de chatverbinding uit data-rc-ws-url — en leidt daarvan het adres voor het offlineformulier af. Andere attributen, zoals data-rc-locale, blijven ongewijzigd; ze staan beschreven onder De widget insluiten. Het fragment op de accountpagina bevat ook uw merkkleur.

De vijf controlestappen

Elke stap toont “OK”, “Fout”, “Opmerking” of “niet gecontroleerd”. De controle is pas geslaagd als alle vijf “OK” tonen; een opmerking telt niet als geslaagd. Er wordt alleen via HTTPS op poort 443 gecontroleerd, omleidingen worden niet gevolgd en elke stap krijgt 5 seconden.

  1. Naamomzetting. De naam moet een DNS-record hebben en alle bijbehorende adressen moeten openbaar zijn. Er komt een melding als er geen A- of AAAA-record wordt gevonden of als minstens één adres privé, lokaal of gereserveerd is — zulke adressen controleren wij niet. Alle volgende stappen verbinden met precies het hier gecontroleerde adres.
  2. loader.js via uw domein. Wij laden https://chat.your-domain.com/loader.js en vergelijken het bestand met het onze. Als er geen verbinding, antwoord of geldig certificaat tot stand komt, worden de overige stappen overgeslagen (“niet gecontroleerd”). Als de proxy met een omleiding antwoordt in plaats van met het bestand, is dat een fout. Als het bestand afwijkt, ligt dat meestal aan een cache in de proxy of een verkeerd doel voor /.
  3. Sleutel en bezoekersadres. Wij benaderen een controleadres onder /rc_wss/. Onze server antwoordt of er een geldige sleutel en een geldig bezoekersadres zijn ontvangen en bij welk project de sleutel hoort. Mogelijke fouten: de header X-RC-Proxy-Key ontbreekt; de sleutel is onbekend (typefout, verouderd of verwijderd — direct na aanmaken kan het maximaal 30 seconden duren); de header X-RC-Client-IP ontbreekt of bevat geen geldig adres; de sleutel hoort bij een ander project. Er verschijnt een opmerking als de sleutel geldig is maar voor een andere host geregistreerd staat.
  4. Chatverbinding (WebSocket). Wij openen een WebSocket-verbinding via wss://chat.your-domain.com/rc_wss/ en verwachten HTTP 101 met protocol rc.v1. “Geen WebSocket-upgrade” betekent: de proxy geeft de headers Upgrade en Connection niet door.
  5. Offlineformulier. Wij benaderen https://chat.your-domain.com/api/offline/SITE_KEY met GET. Ons endpoint antwoordt met HTTP 405 omdat het alleen POST accepteert — zo weten wij dat het verzoek ons bereikt, zonder een bericht aan te maken. Andere resultaten: origin afgewezen (een opmerking, zie problemen oplossen), project niet actief, doel vereist authenticatie (HTTP 401).

Stappen 2 tot en met 5 kunnen ook HTTP 421 melden (Host-header, zie problemen oplossen). Als stap 3 of 5 meldt “Het antwoord komt niet van reactive.chat”, wijst dit pad in uw proxy naar een verkeerd doel.

Wat de proxy moet doorsturen

Voor alle drie de routes:

Voor /rc_wss/ en /api/offline/ stelt de proxy ook bij elk verzoek twee headers in:

“Instellen” betekent: alles wat een browser onder deze namen verstuurt, wordt overschreven en bereikt ons nooit. Op de route naar het distributienetwerk worden beide headers verwijderd; de sleutel gaat alleen naar de chatserver.

HTTP-fallback via uw domein

Als het netwerk van een bezoeker geen WebSocket doorlaat, schakelt de widget automatisch over op de HTTP-fallback. Deze gebruikt hetzelfde pad /rc_wss/ en heeft geen extra vermelding in de proxy nodig. Twee zaken zijn vereist:

De sjablonen

De onderstaande sjablonen komen overeen met die op de accountpagina. De accountpagina vult uw host, de voor uw account geldende doeladressen en — direct na het aanmaken — uw sleutel al in. Kopieer het sjabloon daarom het beste daarvandaan; de versies hier dienen als referentie. Tijdelijke aanduidingen zijn chat.your-domain.com, SITE_KEY en __RC_PROXY_KEY__.

nginx

Hoort in de http-context, bijvoorbeeld als /etc/nginx/conf.d/chat.your-domain.com.conf. Op RHEL en CentOS staat de CA-bundel in /etc/pki/tls/certs/ca-bundle.crt in plaats van /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

Het sjabloon bevat alleen de VirtualHost voor poort 443. Stel de omleiding vanaf poort 80 en het certificaat in zoals voor uw andere hosts. SSLProxyVerifyDepth 5 is vereist: zonder die instelling controleert Apache de certificaatketen van het doel slechts één niveau diep en mislukt de verbinding met “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 haalt zelf het certificaat op; WebSockets hebben geen extra instelling nodig. header_up met een waarde vervangt alles wat de browser onder die naam heeft verstuurd.

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

De proxysleutel

Problemen oplossen

HTTP 421 (Misdirected Request)

Onze server wijst de Host-header af: uw proxy stuurt uw eigen naam in plaats van de doelnaam. Host-header en SNI moeten de doelnaam zijn — bij nginx proxy_set_header Host plus proxy_ssl_server_name on met proxy_ssl_name, bij Apache ProxyPreserveHost Off, bij Caddy header_up Host en tls_server_name.

HTTP 502 of SSL-fouten richting het doel

De proxy kan de TLS-verbinding met ons niet opbouwen of vertrouwt het certificaat niet.

HTTP 413 bij het uploaden van bijlagen

Uploads gaan via /rc_wss/. Zonder eigen instelling accepteert nginx slechts 1 MB; het sjabloon stelt daarom client_max_body_size 32m in het gedeelte voor /rc_wss/ in. Als er een loadbalancer of CDN vóór uw proxy staat, moet die minstens dezelfde limiet hebben.

Chatverbinding zonder upgrade

De controle meldt “Geen WebSocket-upgrade” met de HTTP-status die in plaats daarvan is ontvangen. De chat werkt dan verder via de HTTP-fallback, maar maakt pas na twee mislukte WebSocket-pogingen verbinding.

HTTP 401 of 403 bij het offlineformulier

DNS verwijst naar een privéadres

De controle meldt een niet-openbaar adres als minstens één adres van de naam privé, lokaal of gereserveerd is (bijvoorbeeld 10.0.0.0/8, 192.168.0.0/16, 100.64.0.0/10 of fc00::/7). Veelvoorkomende oorzaken zijn een record uit interne DNS of een interne loadbalancer. Uw bezoekers kunnen zulke adressen evenmin bereiken; het openbare record moet naar een openbaar adres verwijzen.

Handmatig controleren

U kunt het controleadres uit stap 3 zelf benaderen:

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

Bij een juiste inrichting is het antwoord:

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

De waarden zijn Duitse woorden: schluessel (sleutel) is gueltig (geldig), unbekannt (onbekend) of fehlt (ontbreekt); client_ip is gesetzt (ingesteld), fehlt (ontbreekt) of ungueltig (ongeldig). site en host staan er alleen bij een geldige sleutel in. Het antwoord bevat nooit een adres.

Verder lezen in de documentatie

Uitgebreid: Eigen domein en first-party proxy. Alle hoofdstukken staan in de documentatie.

Aġġornat l-aħħar: