Nakama

Deployment hardening

Nakama holds provider keys, account sessions, message history, files, and tools that can act on other systems. Treat the dashboard as an administrative console, not as a public website.

The safest starting point is a private network such as Tailscale. If people must reach Nakama from the public internet, put it behind an HTTPS reverse proxy and keep port 4310 private.

Before people sign in

AreaMinimum control
Inbound networkExpose HTTPS on 443; do not expose 4310 directly
TLSTerminate TLS at Caddy, Nginx, Traefik, or an equivalent managed proxy
Public originSet NAKAMA_WEB_PUBLIC_URL to the exact HTTPS origin
Data rootPersist it, restrict it to the Nakama service account, and include it in encrypted backups
SecretsKeep provider keys and channel tokens out of images, Compose files, shell history, and source control
EgressPermit only the destinations required by the features you enabled
RecoveryTest a restore before relying on a backup schedule
UpdatesPin a tested image tag or digest; stage upgrades and keep a rollback copy

Keep the application port private

Nakama has no built-in TLS. A source install binds to loopback by default, while the Docker image listens on 0.0.0.0:4310 inside the container. Publishing 4310 on every host interface bypasses the protection your reverse proxy would otherwise provide.

When the proxy runs on the same Docker host, bind the published port to loopback:

Replace VERSION with a release tag you have tested:

docker run -d \
  -p 127.0.0.1:4310:4310 \
  -v nakama-data:/nakama/data \
  -e NAKAMA_WEB_PUBLIC_URL=https://nakama.example.com \
  --name nakama \
  ghcr.io/ahmadrosid/nakama:VERSION

When the proxy and Nakama are services in the same Compose project, place them on a private Docker network and omit ports from the Nakama service. Use expose: ["4310"] only as documentation; Compose services on the same network can already reach that port.

Allow inbound traffic to the proxy on 443. Redirect 80 to HTTPS if you need certificate issuance or an HTTP redirect. Deny direct public access to 4310.

For a deployment that does not need public callbacks or public artifact links, Tailscale avoids a public listener entirely.

Terminate TLS at a reverse proxy

Caddy

Caddy obtains and renews certificates automatically when DNS points at the host and ports 80 and 443 reach it:

nakama.example.com {
  @metrics path /metrics
  respond @metrics 404

  reverse_proxy 127.0.0.1:4310 {
    flush_interval -1
  }
}

Nginx

The certificate paths below are examples. Keep response buffering off so chat streaming through Server-Sent Events is not delayed:

server {
    listen 443 ssl;
    server_name nakama.example.com;

    ssl_certificate     /etc/letsencrypt/live/nakama.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nakama.example.com/privkey.pem;

    location = /metrics { return 404; }

    location / {
        proxy_pass http://127.0.0.1:4310;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

If Prometheus needs /metrics, replace the public 404 with an IP allowlist or a private listener. The metrics endpoint has no application login when NAKAMA_METRICS=true.

Behind a reverse proxy, Nakama's rate limiter sees the proxy address by default. Set NAKAMA_TRUST_PROXY=true only when the backend port is private and the proxy replaces untrusted X-Forwarded-For values. A client that can reach the backend or supply the first forwarded address can otherwise choose its own rate-limit identity.

After the proxy is live, set the exact external origin:

NAKAMA_WEB_PUBLIC_URL=https://nakama.example.com

Nakama uses this value for OAuth callbacks and public artifact links. Do not point it at an internal HTTP address when users enter through HTTPS.

Limit outbound traffic

A fresh server does not send telemetry. Network calls start when an operator configures a provider or integration, or when a user or agent invokes a network tool. The packaged desktop app is the exception: it checks its GitHub update feed at startup and every six hours.

Use the outbound connections inventory to build an egress policy. Keep these two cases in mind:

  • Fixed services such as a selected LLM provider or Telegram can be allowlisted by destination.
  • web_fetch, browser automation, HTTP MCP servers, custom tools, plugins, and coding agents can target operator- or user-selected hosts. Either isolate those capabilities in a controlled network or leave them unassigned.

DNS-only allowlists do not control a tool after it starts another process. Enforce egress at the host, container network, or firewall boundary.

Protect the data root

The data root contains more than the SQLite database. It can contain provider keys, channel credentials, OAuth state, profile workspaces, memory, attachments, plugin data, and public-share snapshots. An exported backup can contain the same material.

Nakama does not currently provide full encryption at rest for this data root. Use an encrypted host filesystem or volume in addition to restrictive file permissions. Native secret-manager and encrypted-config support is tracked in #364.

The container runs as non-root UID 1000. For a bind mount, create a dedicated directory and make that account its owner before starting Nakama:

sudo install -d -m 700 -o 1000 -g 1000 /srv/nakama-data

Then mount /srv/nakama-data:/nakama/data. Do not mount a home directory, the Docker socket, or unrelated application data into the container. Host-backed bash, custom tools, browser automation, coding agents, and plugins can reach whatever the Nakama process can reach.

The stock image already runs as UID 1000; do not replace that with root or add --privileged. Add /dev/kvm only when you deliberately operate the MicroSandbox backend and understand the host dependency.

Handle secrets deliberately

  • For settings that Nakama accepts from environment variables, inject them at runtime from your deployment platform or a protected environment file. Keep that file readable only by the service account. Settings saved through the dashboard still require protection of the data root described above.
  • Do not bake keys into an image or commit them in Compose files.
  • Give each deployment its own provider, channel, mailbox, Composio, MCP, and error-tracking credentials so one leak can be rotated without affecting every environment.
  • Remove stale integrations and revoke their credentials at the provider. A disabled worker stops using a token; it does not revoke the token upstream.
  • Keep debug logs local and short-lived. HTTP request logs omit bodies and credentials, but local process and tool logs are not a general-purpose secret scrubber.

In Control center → Workspace → Settings → Multi-factor authentication, register a passkey or authenticator app for privileged accounts and store backup codes offline. A platform admin can enable and enforce MFA for selected roles. Once enforcement is on, platform admins must use MFA whichever roles are selected. Test a recovery login before making enforcement mandatory.

Back up for recovery, not just retention

Use Control center → Workspace → Settings → Export ZIP or snapshot the persistent volume. Prefer the application export while Nakama is running. If you copy the SQLite files or the whole volume directly, stop the container first so the snapshot is not taken mid-write.

For each environment:

  1. Choose a recovery point objective and schedule backups more frequently than that interval.
  2. Encrypt backups before they leave the host and restrict who can download them.
  3. Keep at least one copy outside the Nakama host and apply a retention limit.
  4. Restore into an isolated instance and verify login, org membership, profile files, attachments, and one non-production provider connection.
  5. Record the Nakama image version used by the restore test.

See Backup and restore for the supported export and import flows.

Reduce runtime impact

  • Assign bash, browser automation, coding agents, skill scripts, custom tools, MCP servers, plugins, and external providers only to profiles that need them.
  • Prefer MicroSandbox for untrusted shell work when the host supports it. The default host backend runs inside the Nakama process environment and is not a tenant sandbox.
  • Set CPU and memory limits with enough headroom for enabled channel workers. WhatsApp and browser workloads use more memory than an offline server.
  • Leave /metrics off unless a private collector needs it. Use /healthz for liveness and /readyz for database readiness; neither verifies external providers or worker connectivity.
  • Use NAKAMA_LOG_FORMAT=json and request IDs when shipping logs. Treat the log destination as sensitive because tool and worker logs can include operational data even though HTTP request logging omits bodies.
  • Configure error tracking only when the destination and its retention policy are acceptable for the deployment.

Verify the boundary

Run these checks from a machine outside the host network:

# Must succeed through TLS.
curl --fail --show-error https://nakama.example.com/healthz

# Must fail or time out; 4310 must not be public.
curl --connect-timeout 5 http://nakama.example.com:4310/healthz

# Must be 404 or blocked unless this client is an approved metrics collector.
curl --fail-with-body https://nakama.example.com/metrics

Then verify operational controls rather than assuming them:

  • Sign in as a viewer and confirm mutation and agent actions are unavailable.
  • Confirm an org member cannot select another organization by changing the URL, cookie, or X-Org-Id header.
  • Restore the latest backup into an isolated instance.
  • Trigger one configured integration at a time and compare observed egress with the inventory.
  • Re-run these checks after proxy, firewall, provider, plugin, MCP, or channel changes.

On this page