Skip to content

Going live on a domain

Loopback evaluation needs none of this. The moment your server has a real hostname, two independent things become mandatory: TLS for the web side, and a reachable address for the media side. Doing the first and assuming it covered the second is the common way to end up with working sign-in and silent calls.

The desktop refuses any non-loopback server that is not https://, so without TLS nobody can sign in. That is a loud failure, which is the good kind.

The bundle’s gateway already owns ports 80/443 and gets its own Let’s Encrypt certificate and renews it. Point an A record at the box, then in .env:

Terminal window
PUBLIC_HOST=teamhud.example.com
PUBLIC_BASE_URL=https://teamhud.example.com
APP_URL=https://teamhud.example.com
LIVEKIT_PUBLIC_URL=wss://teamhud.example.com

firstboot.sh sets all four when you give it a hostname.

2. Media: NODE_IP must be reachable by clients

Section titled “2. Media: NODE_IP must be reachable by clients”

NODE_IP is the address the media server advertises to clients. Left at 127.0.0.1, calls connect and carry no audio or video, and nothing logs an error. Set it to the box’s public IP (or its LAN IP for an office-only install).

Port Protocol Why
80 TCP Certificate issuance and the redirect to HTTPS
443 TCP Web, API, sign-in, and call signalling (wss://)
7882 UDP Call media, the normal path
7881 TCP Call media for clients whose network blocks UDP
3478 UDP TURN, for clients behind strict NAT
30000–30063 UDP TURN relay ports. Without them TURN is advertised but carries nothing

Nothing else needs to be public. The API, database and media signalling ports are bound to loopback on purpose.

TURN is on in this bundle, over UDP 3478. That covers users behind symmetric NAT (many home routers and mobile hotspots).

It does not cover networks that allow only outbound TCP 443, which some corporate firewalls enforce. There a call connects and carries nothing, unless you switch on TURN over TLS on 443. It is off by default; Corporate network requirements covers it. Before you promise it works, test from a machine inside that network. The probe needs only Node and this one file from the bundle:

Terminal window
node verify-turn.mjs teamhud.example.com 3478

5. Who can reach admin, and who can sign up

Section titled “5. Who can reach admin, and who can sign up”
  • ADMIN_IP_ALLOWLIST: /admin answers 404 to everyone else. Unset means nobody can reach it, which is safe and confusing. Comma-separated IPs or IPv4 CIDRs.
  • SIGNUP_MODE: open (default: anyone can sign up), sso_only (only through Sign in with Slack; see Slack), or closed (invite-only: a new person needs an invitation sent to their email address, or a System admin creates the account). Apply it with docker compose up -d, which restarts the auth service too.
  • Invite emails need a Resend key (RESEND_API_KEY). Without one, invitations still work as shareable links; only the email is skipped.

The media server wants a bigger UDP receive buffer than Linux’s default. Compose cannot set it, so firstboot.sh does: it raises net.core.rmem_max to 5000000 now and for later boots. Check it with sysctl net.core.rmem_max. If firstboot ran without root and said so, set it yourself:

Terminal window
echo 'net.core.rmem_max=5000000' | sudo tee /etc/sysctl.d/90-teamhud-livekit.conf
sudo sysctl --system
  1. DNS A record → the box. PUBLIC_HOST, PUBLIC_BASE_URL, APP_URL, LIVEKIT_PUBLIC_URL set.
  2. NODE_IP is the address clients reach.
  3. Firewall opened per the table above.
  4. ADMIN_IP_ALLOWLIST and SIGNUP_MODE chosen.
  5. ./verify-stack.sh green, and a test call between two machines on different networks has audio both ways.
  6. Backups installed and one restore tested.