Skip to content
NebulaCtrldocs

Harden a production install

A checklist for locking down a self-hosted NebulaCtrl control plane, its identity and access settings, its integrations and the clusters it manages.

A fresh install is safe by default: the control plane listens on loopback, the database is not published and .env is mode 600. This checklist covers what you add for production. Each item names the setting and how to check it.

Before you begin

  • You have the owner role and shell access to the control plane host.
  • Read the security model to see which parts are yours.
  • Take a backup before you change anything on the host.

Secure the host and the edge

  1. Put TLS in front. Keep the default listen address, 127.0.0.1:8080, and terminate TLS at a reverse proxy. Use https:// as the base URL. The session cookie is marked Secure only for an https:// base URL. Do not pass --listen 0.0.0.0:8080 to the installer unless something else guards that port.
  2. Trust only your proxy. Set NEBULA_TRUSTED_PROXIES in .env, below the marker line, to the exact address or CIDR your proxy has from the container's side. Add cloudflare when Cloudflare sits in front. A trusted peer may set X-Forwarded-For, so restrict the origin to the proxy. Rate limits work per client address, so a wrong value either shares one limit across everyone or lets a client choose its own address.
  3. Restrict who can read .env. It holds the master key, the database password, the OIDC client secret and the updater token. Keep mode 600. The installer's .env.bak-TIMESTAMP copies hold the same secrets, so remove the ones you no longer need.
  4. Limit host access. Anyone who can run docker or passwordless sudo on the host can read .env and the database. The nebula-updater service runs as the user that installed NebulaCtrl and drives Docker. Install as a dedicated user, or use --no-updater if you do not want console-triggered updates.
  5. Leave diagnostics closed. Metrics bind to the container's loopback. --pprof is off by default; do not turn it on in production.

Protect the database and the master key

  1. Keep PostgreSQL private. The bundled database is not published. For an external server, require TLS (for example sslmode=verify-full in the URL), allow only the control plane's address and encrypt the storage.
  2. Separate the key from the dump. Store .env or the master key apart from database dumps. A dump alone holds only sealed values.
  3. Rotate on a schedule. Follow Rotate the master key.

Lock down sign-in

  1. Use an https:// issuer. NebulaCtrl refuses a plain http:// issuer unless NEBULA_OIDC_INSECURE_HTTP is true. Never set that in production.
  2. Require two-factor. Enforce multi-factor authentication at your provider and make it report amr. Then switch on Require two-factor for everyone under Settings > Security & policy > Authentication. See Manage members and teams.
  3. Keep invitation-only. People without an invitation or a SCIM grant are refused at sign-in. Revoke unused invitations; they expire after 7 days.
  4. Provision and deprovision over SCIM if your provider supports it. Deprovisioning revokes the tokens that person created.
  5. Review members. In Settings > Members, keep few owners and admins. The 2FA column shows who signed in without a second factor.

Constrain what changes can do

  1. Mark production environments as production when you create them. Changes to a production environment wait for an approval from an admin. Settings > Security & policy > Protected environments lists them.
  2. Keep self-approval off. Approvers can't approve their own requests is on by default. Leave it on.
  3. Keep rollbacks reviewed. Rollbacks skip approval is off by default. Turn it on only with a reason.
  4. Freeze deploys when nobody is watching. Use Weekend deploy freeze. See Approvals and freezes.

Manage API tokens and integrations

  1. Give each token the lowest role that works, limit it to the projects it needs, and pick an expiry. The table in Settings > API tokens tints tokens that never expire.
  2. Revoke what is unused. Any admin can revoke any token.
  3. Scope provider credentials. Use the GitHub App where you can. For GitLab and Forgejo, give the token only the scopes listed in Connect Git providers and registries. Give Cloudflare and object store credentials access only to the zones and buckets NebulaCtrl uses.
  4. Rotate registry credentials by deleting and adding them. They cannot be edited.

Secure the clusters

  1. Install keys before you install NebulaCtrl. The cluster install disables SSH password login only if root or the sudo user already has an authorized_keys file. Otherwise it leaves password login on and says so.
  2. Shorten join tokens. Under Settings > Security & policy > Server join tokens, set the default lifetime. The range is 5 minutes to 24 hours and the default is one hour.
  3. Keep agents current. The control plane never updates an agent on its own. See Update a cluster agent.
  4. Remove clusters you no longer use. Removing a cluster stops its agent and broker tokens working. K3s stays installed until you run /usr/local/bin/k3s-uninstall.sh.
  5. Keep nodes patched. Unattended upgrades are installed on every node. You own reboots and the rest of the operating system.

Keep the control plane current

Update when a release ships. Read the changelog first. See Upgrade the control plane.

Verify

Check the listener. Only loopback may appear for the control plane:

ss -ltn
State   Recv-Q  Send-Q   Local Address:Port   Peer Address:Port
LISTEN  0       4096         127.0.0.1:8080        0.0.0.0:*

Your proxy's port is listed too. Port 8080 must not show 0.0.0.0 or *, and 5432 must not appear.

Check the file mode:

stat -c '%a %U' /opt/nebula/.env
600 deploy
  • Settings > Security & policy > Authentication shows Require two-factor for everyone on.
  • Settings > API tokens lists no token you do not recognize.
  • Settings > Members lists only people who need access.

Next steps

On this page