Reverse proxy and HTTPS

Putting the web port behind a certificate with Caddy or nginx, the public URL and secure cookie settings, what needs proxying and what must not be, Cloudflare and tunnels, and the apps' cross-origin calls.

Server

Anyone outside your LAN should reach the server over . It protects passwords and the in transit, and it is what browsers require before they will grant the microphone, Bluetooth for , or an installable web app. Only the web port goes behind the proxy; the voice , if you use the , is opened directly.

Caddy

play.example.com {
    reverse_proxy 127.0.0.1:3005
}

fetches and renews the certificate itself and forwards the headers the server needs.

nginx

server {
    listen 443 ssl http2;
    server_name play.example.com;
    ssl_certificate     /etc/letsencrypt/live/play.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/play.example.com/privkey.pem;

    client_max_body_size 32m;

    location / {
        proxy_pass http://127.0.0.1:3005;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 600s;
        proxy_buffering off;
    }
}

The long read timeout and buffering off matter: streams, the event stream stays open, and a Dungeon Master on a local model can take minutes. The body size covers uploads of maps and .

Tell the server its address

  • Set in Admin, Server settings, Server, or APP_PUBLIC_URL=https://play.example.com. Share links, QR codes, the invite preview and the Discord redirect are built from it. A proxy that forwards X-Forwarded-Host and X-Forwarded-Proto lets the server work it out, but setting it removes the guesswork.
  • COOKIE_SECURE=1 behind ; 0 if the server is only ever reached on plain http on a LAN.
  • Login throttling keys on the address the proxy appended, so X-Forwarded-For should be set by your proxy and not by clients.

Cloudflare and tunnels

Cloudflare's proxy or a Cloudflare works for the web port, with two cautions. Voice: the built-in 's UDP port cannot go through Cloudflare, so use the transport, or a on a DNS-only record. Pictures: Cloudflare drops the Vary: Origin header the server sets on media, which once poisoned the apps' cache; the apps from 0.12.6 work around it, so keep them current. Uploads, generated pictures and audio need a sign-in and are cached privately, so a cache in front of the server never keeps a copy for the next caller.

The apps' cross-origin calls

The desktop and Android apps draw the server's screens themselves and call its data routes from their own origin, with a bearer token. The server allows those origins by default; a self-built app or an unusual setup can add more with ODM_APP_ORIGINS.

Certificates for a LAN

For a table that is entirely inside one house, the apps do not need : they are a secure context of their own and can use voice on a plain-http address. Browsers on the LAN do need it for the microphone; a local certificate authority (mkcert) or a real certificate for a private name are the usual answers.

Checked against desktop and Android app 0.17.2 and server 0.26.2 on 2026-10-11. Something wrong or missing? Open an issue.

Download · v0.17.2
Server app · v0.26.2

Optional. Runs the table for a whole group on one machine that stays on. Everyone else just installs the app.

Free & open source · MIT licensed · served from GitHub releases