Proxy

Put Caddy or nginx in front of the server to hold the TLS certificate.

The daemon terminates no TLS. A team that runs a server already runs a proxy, and a certificate lifecycle inside the daemon is a product of its own. The proxy holds the certificate, speaks TLS to the browser, and speaks plain HTTP to the daemon on the private interface.

The proxy does three things:

  1. Forward the WebSocket upgrade of /api/v1/ws, /api/v1/channels/{id}/dictate and /api/v1/calls/{id}/listen. Without it the Product App loads and then shows nothing live.
  2. Set X-Forwarded-Proto to the scheme the browser used. The Session cookie carries Secure only where the daemon is told the browser spoke TLS.
  3. Set X-Forwarded-For to the browser's address. The sign-in rate limit counts against it, so without it one person who forgets a password locks out everybody behind the proxy.

A browser can write both headers, so the daemon reads them from the Trusted Proxy and from no other address. It takes the last entry of X-Forwarded-For, which is the entry the proxy wrote, so a proxy that appends and a proxy that replaces read the same. In the compose deployment the Trusted Proxy is 127.0.0.1, where the proxy is and where no browser can be.

Caddy

Caddyfile
pagis.example.net {
	reverse_proxy 10.0.1.7:4400
}

Caddy gets and renews the certificate, sets X-Forwarded-Proto, appends to X-Forwarded-For, and passes a WebSocket upgrade through with no configuration. deploy/Caddyfile is the same block for the compose deployment, with the admin API of Caddy off (The shape).

nginx

server {
    listen 443 ssl;
    server_name pagis.example.net;

    ssl_certificate     /etc/letsencrypt/live/pagis.example.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pagis.example.net/privkey.pem;

    location / {
        proxy_pass http://10.0.1.7:4400;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # The WebSocket upgrade of /api/v1/ws and of the two media sockets.
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # A run streams for minutes and a socket stays open for hours.
        proxy_read_timeout  1h;
        proxy_send_timeout  1h;
        proxy_buffering     off;
    }

    client_max_body_size 50m;  # the artifact upload cap
}

# Connection: upgrade only where the request asked for one.
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
Edit on GitHub

On this page