Tailscale
Nakama has no built-in TLS and binds 127.0.0.1:4310 by default, or 0.0.0.0:4310 in Docker. Publishing that port to the internet exposes an agent console with tool access, so the safer route for a self-hosted install is a private network. Tailscale gives you one without a reverse proxy, a certificate, or a DNS record.
Tailscale stays an operator concern. Nakama does not embed it and has no Tailscale settings of its own.
Host install
A host install binds to 127.0.0.1, so tailscale up alone doesn't expose it to
your tailnet. To reach Nakama through the machine's Tailscale IPv4 address,
install Tailscale, join your tailnet, and start Nakama on that address:
tailscale up
NAKAMA_HOST="$(tailscale ip -4)" bun run dev:serverLeave Nakama on its default port. With MagicDNS on, the dashboard is reachable from any device in the tailnet:
http://<machine-name>:4310Binding to the Tailscale address keeps Nakama off public network interfaces. If
you set NAKAMA_HOST=0.0.0.0 instead, use a host firewall to block untrusted
interfaces.
If Nakama runs in Docker, keep the published port bound to the host only, not to every interface:
docker run -d -p 127.0.0.1:4310:4310 -v nakama-data:/nakama/data \
--name nakama ghcr.io/ahmadrosid/nakama:latest-p 4310:4310 binds 0.0.0.0 and is what accidentally exposes the console on a machine with a public IP.
HTTPS with Tailscale Serve
MagicDNS over plain HTTP works, but Serve gives you an HTTPS origin with a real certificate, which the browser and any OAuth flow prefer. Serve proxies the loopback port, so you don't need to change Nakama's default binding:
tailscale serve --bg 4310Nakama is then at https://<machine-name>.<tailnet>.ts.net. Tell Nakama that this is its public origin, otherwise callbacks and shared links keep pointing at localhost:
NAKAMA_WEB_PUBLIC_URL=https://<machine-name>.<tailnet>.ts.netThe same value can be set under Control center → Workspace → Settings → Public web URL instead of the environment.
Keep Funnel off. tailscale funnel publishes the same service to the whole internet, which is exactly what this setup avoids.
Docker Compose with a sidecar
If you would rather not install Tailscale on the host, run it beside Nakama and put Nakama in the sidecar's network namespace:
services:
tailscale:
image: tailscale/tailscale:latest
hostname: nakama
environment:
TS_AUTHKEY: ${TS_AUTHKEY}
TS_STATE_DIR: /var/lib/tailscale
TS_SERVE_CONFIG: /config/serve.json
volumes:
- tailscale-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
restart: unless-stopped
nakama:
image: ghcr.io/ahmadrosid/nakama:latest
network_mode: service:tailscale
environment:
NAKAMA_WEB_PUBLIC_URL: https://nakama.<tailnet>.ts.net
volumes:
- nakama-data:/nakama/data
depends_on:
- tailscale
restart: unless-stopped
volumes:
tailscale-state:
nakama-data:serve.json describes the HTTPS front end:
{
"TCP": { "443": { "HTTPS": true } },
"Web": {
"${TS_CERT_DOMAIN}:443": {
"Handlers": { "/": { "Proxy": "http://127.0.0.1:4310" } }
}
},
"AllowFunnel": {}
}An empty AllowFunnel keeps the service tailnet-only. Use a tagged auth key (tag:nakama) so the node is owned by the tag rather than by whoever generated the key, which keeps it from expiring with that user.
network_mode: service:tailscale means Nakama publishes no ports of its own. Reaching it from outside the tailnet is then not a matter of configuration, it is simply not routed.
What Tailscale does not solve
Serve is tailnet-only, so anything that has to call Nakama from the public internet still needs a public URL:
- Composio OAuth callbacks, see Composio
- Third-party webhooks pointed at your instance
Outbound traffic is unaffected. Channel workers for Telegram, WhatsApp and Discord connect out, so they keep working behind a tailnet with no inbound exposure at all.
Headscale
Headscale is a self-hosted control plane that speaks the same protocol. Everything above applies once your nodes point at it. It is an operator choice about who runs the coordination server, and Nakama neither knows nor cares which one you use.
Next steps
- Deployment hardening — protect the data root, secrets, backups, and local runtime
- Outbound connections — review traffic that still leaves through the tailnet host
- Backup and restore — prepare and test recovery