Skip to content
All docs

Docs

Auth & security

Organizations, roles, capabilities, 2FA, SSO, audit, and encrypted secrets.

Accounts

  • Email/password via better-auth; 12-character minimum for new passwords with a live strength hint
  • First user via /setup — public /register is disabled afterward, and the installer generates a one-time setup token so a scanner cannot win the race on a fresh host
  • Invitations are shareable links that disclose only a masked email; the sign-up must carry the invitation id and match that exact address
  • Optional TOTP 2FA, with an org-wide require-2FA gate that blocks every org-scoped call until the member enrols
  • Passkeys (WebAuthn) — Touch ID, Windows Hello, a phone or a security key, instead of a password. They need the panel to have a domain name, because a credential is bound to one; an instance reached by IP is told so rather than shown a button that cannot work
  • Password reset by email, plus a break-glass host command when no mail is configured
  • Optional OIDC single sign-on (NIXPLOY_OIDC_ISSUER / CLIENT_ID / CLIENT_SECRET, plus an optional default org slug for JIT membership) — Authentik, Keycloak, any OpenID provider
  • Per-account sign-in lockout on top of the per-IP limit: 10 failures in 15 minutes

Roles & capabilities

  • Roles: viewer < member < deployer < admin < owner
  • 27 capability overlays (projects, services, secrets, domains, backups, schedules, gitops, AI, infrastructure, organization)
  • Invite members; set per-member capability grants on top of the role baseline
  • Rank-bound capabilities (servers, docker, org settings, members) stay admin-only — an overlay cannot delegate them downward
  • Anything that reaches the shared host or cluster additionally requires the instance admin, whatever the org role: Swarm joins and the manager role, cluster-wide Docker, host-privileged compose, wildcard domains, layer-4 entrypoints, instance backups, self-update, network and container-hardening overrides

API keys

  • Scoped: read, deploy, write or full access — the effective set is the scope intersected with the owner's own capabilities, never more
  • Bound to one organization; a bound key refuses every other tenant, including a contradicting header
  • 90-day expiry by default (1 year maximum; Never is instance-admin only), nxp_ prefix so secret scanners catch a leak, and a Last used column
  • The same scope gates REST, MCP, the Prometheus endpoint and the deploy webhook
  • Rate limited per key (120 requests/minute); a throttled request answers 429 with Retry-After
  • Keys created before scopes existed are labelled Legacy — full access. Rotate them

Audit log

  • Every meaningful mutation appends a row: actor, dot-namespaced action, target, client IP and user agent, and a free-form metadata blob that never holds secrets
  • Auth events are in the same trail — sign-in, failed sign-in, lockout, 2FA changes, API-key create/delete, impersonation, and instance-admin user management
  • The IP is resolved through the trusted-proxy policy and a socket-peer check, so a forged X-Forwarded-For never lands in the trail
  • Monitoring → Audit log filters by action, target and a since/until window, and exports the current filter as CSV with every column
  • Retention is NIXPLOY_AUDIT_RETENTION_DAYS (default 365, 0 keeps rows forever)
  • NIXPLOY_AUDIT_FORWARD=1 mirrors every new row to the instance-admin notification channels, batched once a minute — the panel can delete its own table, it cannot delete a Slack message

Secrets at rest

  • Env vars, database passwords, registry and git credentials, S3 keys and notification configs live in AES-256-GCM columns keyed by ENCRYPTION_KEY
  • The panel refuses to boot on the placeholder key from the example env file
  • Secrets are redacted on read for callers without the secrets.read capability
  • Build variables never travel on argv — a 0600 env file, --env-file, or a BuildKit secret depending on the builder — and every one of those files is deleted when the build ends
  • Rotating the key is a supported, online operation: see Key rotation

Network isolation

Each environment gets its own private overlay network. Your services resolve each other by name inside it; another organization's containers cannot see them at all.

  • The panel and its Postgres sit on a separate overlay no tenant container ever joins, so a compromised container cannot even resolve them
  • A service joins the shared, Traefik-facing network only while it has a domain
  • Managed databases publish no host port unless you opt in, and are reached by the panel with docker exec rather than over the network
  • Compose stacks keep their own per-stack network on top
  • Cross-environment and cross-organization DNS is gone by design

Container defaults

  • All Linux capabilities dropped, with seven added back (no NET_RAW, so ping and ICMP monitors do not work inside tenant containers; no SYS_*)
  • no-new-privileges on every tenant container
  • Process (1024) and file-descriptor (65536) ceilings
  • Rotating JSON logs (10 MB × 3) so one service cannot fill the disk
  • Org quota CPU/memory applied as per-service limits when the service sets none — 1024 shares is one CPU
  • These keys are written explicitly on every deploy, so a spec that grew privileges from a manual docker service update is reset rather than inherited
  • Relaxing the baseline, or overriding the Swarm network list, is instance-admin only

Compose safety

Compose files are rendered before they are validated, so a variable reference cannot smuggle a value past the checks, and the checks run on both the raw and the rendered file. Privileged mode, host namespaces, the Docker socket, host binds, published ports, dangerous capabilities, foreign log drivers, unbounded tmpfs, global mode and manager-targeting placement are all refused, and the hardening defaults are injected where the file does not set them.

Outbound requests

Everything the panel fetches on your behalf — webhooks, SMTP, self-hosted Git, S3, uptime probes, template sources — goes through one guard. Cloud metadata and link-local ranges are never reachable; private LAN targets need an instance-admin toggle. See Outbound requests.

Also see the repository guides under docs/ · REST API reference

Rent the box. Keep the rest.

One command, three services, and the first account is yours.