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
- Put TLS in front. Keep the default listen address,
127.0.0.1:8080, and terminate TLS at a reverse proxy. Usehttps://as the base URL. The session cookie is markedSecureonly for anhttps://base URL. Do not pass--listen 0.0.0.0:8080to the installer unless something else guards that port. - Trust only your proxy. Set
NEBULA_TRUSTED_PROXIESin.env, below the marker line, to the exact address or CIDR your proxy has from the container's side. Addcloudflarewhen Cloudflare sits in front. A trusted peer may setX-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. - Restrict who can read
.env. It holds the master key, the database password, the OIDC client secret and the updater token. Keep mode600. The installer's.env.bak-TIMESTAMPcopies hold the same secrets, so remove the ones you no longer need. - Limit host access. Anyone who can run
dockeror passwordlesssudoon the host can read.envand the database. Thenebula-updaterservice runs as the user that installed NebulaCtrl and drives Docker. Install as a dedicated user, or use--no-updaterif you do not want console-triggered updates. - Leave diagnostics closed. Metrics bind to the container's loopback.
--pprofis off by default; do not turn it on in production.
Protect the database and the master key
- Keep PostgreSQL private. The bundled database is not published. For an external server, require TLS (for example
sslmode=verify-fullin the URL), allow only the control plane's address and encrypt the storage. - Separate the key from the dump. Store
.envor the master key apart from database dumps. A dump alone holds only sealed values. - Rotate on a schedule. Follow Rotate the master key.
Lock down sign-in
- Use an
https://issuer. NebulaCtrl refuses a plainhttp://issuer unlessNEBULA_OIDC_INSECURE_HTTPis true. Never set that in production. - 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. - Keep invitation-only. People without an invitation or a SCIM grant are refused at sign-in. Revoke unused invitations; they expire after 7 days.
- Provision and deprovision over SCIM if your provider supports it. Deprovisioning revokes the tokens that person created.
- 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
- 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.
- Keep self-approval off. Approvers can't approve their own requests is on by default. Leave it on.
- Keep rollbacks reviewed. Rollbacks skip approval is off by default. Turn it on only with a reason.
- Freeze deploys when nobody is watching. Use Weekend deploy freeze. See Approvals and freezes.
Manage API tokens and integrations
- 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.
- Revoke what is unused. Any admin can revoke any token.
- 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.
- Rotate registry credentials by deleting and adding them. They cannot be edited.
Secure the clusters
- Install keys before you install NebulaCtrl. The cluster install disables SSH password login only if
rootor thesudouser already has anauthorized_keysfile. Otherwise it leaves password login on and says so. - 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.
- Keep agents current. The control plane never updates an agent on its own. See Update a cluster agent.
- 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. - 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 -ltnState 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/.env600 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
Security model
What NebulaCtrl secures, what you secure when you self-host it, and how the control plane, the agents, your identity provider and your Git providers trust each other.
Secrets and sealing
How NebulaCtrl encrypts stored secrets with envelope encryption, what the master key protects, how rotation and resealing work, and which values are never logged.