Status & Conditions
NetBoxEnterprise status fields, condition types, and component health reference
The operator maintains a detailed status on each NetBoxEnterprise resource. Use this to monitor cluster health, troubleshoot issues, and integrate with monitoring systems.
Viewing Status
# Summary view
kubectl -n netbox get netboxenterprises -o wide
# Detailed status
kubectl -n netbox describe netboxenterprise netbox
# JSON output for scripting
kubectl -n netbox get netboxenterprise netbox -o jsonpath='{.status}'Top-Level Status Fields
| Field | Type | Description |
|---|---|---|
components | object | Health of each component. See status.components. |
conditions | []Condition | Cluster conditions. See Conditions. |
convergedGeneration | int64 | Highest spec generation that has reached all-components-ready. When it equals metadata.generation, the current config rollout has converged and the progress sentinel reports Ready. |
customPythonConfigRefDigest | string | Short SHA-256 digest of the ConfigMap key named by spec.netbox.config.customPythonConfigRef, as the operator last read it. A change to it makes the operator run database migrations again and restart the NetBox pods. When the ConfigMap or the key is missing, the operator keeps the last value, so a delete-and-reapply (a GitOps prune, for example) does not stop a migration that is running. |
dependencyWaitStartedAt | string | RFC 3339 timestamp of when the operator first saw a dependency-wait timeout for the current generation. It measures how long the wait has lasted, so a brief wait is distinguishable from a hanging one. Cleared on the first successful reconcile. |
lastReconcileTime | string | Timestamp of the last successful reconciliation (RFC 3339). |
license | object | License details. See status.license. |
licenseStatus | string | Computed display string for the LICENSE column of kubectl get. Holds the license type (Dev, Trial, Paid) when the license is healthy, or the LicenseHealth condition reason when it is not. See License Information. Unset until the first reconcile completes. |
message | string | Human-readable summary of the current state. |
migrationHash | string | SHA-256 of migration-relevant inputs (image tag, wheelhouse, and similar). Detects when a migration must re-run. |
migrationPhase1Hash | string | SHA-256 of Phase 1 migration inputs. |
observedGeneration | int64 | The spec generation last processed by the operator. |
phase | enum | Coarse startup phase. See Startup Phase. Values: WaitingForDependencies, Migrating, InstallingPlugins, CollectingStatic, Booting, Serving, Degraded. |
ready | bool | Overall cluster readiness - true only when all components are ready. |
secretChecksum | string | Aggregate hash of external secret resourceVersion values. Drives the debounced restart logic. |
secretChecksumObservedAt | string | RFC 3339 timestamp recording when secretChecksum was last observed. |
version | string | Operator version managing this cluster. |
wheelhouseFingerprint | string | Last-seen custom-plugin wheelhouse fingerprint from the watcher sidecar, as <mtime>_<size> or absent. Stored so the migration hash is a function of stored state only. |
Example:
status:
ready: true
phase: Serving
message: "All components ready"
version: "2.4.0"
licenseStatus: "Paid"
observedGeneration: 3
convergedGeneration: 3
lastReconcileTime: "2026-06-08T10:30:00Z"Startup Phase
status.phase reports where an install has reached. Without it, a multi-minute install shows only an opaque 503. The Admin Console and the kubectl printer columns both read this field.
| Phase | Meaning |
|---|---|
WaitingForDependencies | PostgreSQL, Redis, or object storage is not serving yet. |
Migrating | The database migration Job is running. |
InstallingPlugins | Custom plugins are being installed from the wheelhouse. |
CollectingStatic | NetBox is collecting its static files. |
Booting | The NetBox pods are starting but not yet serving. |
Serving | NetBox is serving traffic. |
Degraded | A component failed. Read the conditions for the cause. |
The variants are ordered along the install timeline. The operator observes WaitingForDependencies, Migrating, Serving, and Degraded directly, from the dependency-wait timer, the migration Job status, component readiness, and the existing conditions. The three in-pod phases -- InstallingPlugins, CollectingStatic, and Booting -- happen inside the NetBox entrypoint, which reports them back through a startup table the operator reads.
kubectl -n netbox get netboxenterprise netbox -o jsonpath='{.status.phase}{"\n"}'Conditions
Standard Kubernetes conditions on the NetBoxEnterprise resource:
| Condition | Status | Reason | Description |
|---|---|---|---|
Ready | True | AllComponentsReady | All components are healthy and serving traffic |
Ready | False | Reconciling | A reconcile is in progress; readiness not yet confirmed |
Ready | False | ComponentsNotReady | One or more components still initializing |
Ready | False | ReconcileFailed | Reconciliation encountered an error |
Progressing | True | ApplyingResources | Operator is actively applying resources |
Progressing | True | WaitingForComponents | Resources applied, waiting for pods to become ready |
Progressing | False | ReconcileComplete | Reconciliation finished successfully |
Degraded | True | ReconcileError | Reconciliation failed - check operator logs |
RoutingHealth | True | GatewayActive | North/south traffic is served by the Gateway API |
RoutingHealth | Unknown | GatewayProgramming | The Gateway was created; waiting for its controller to report Programmed=True (not yet serving) |
RoutingHealth | False | GatewayNotProgrammed | The Gateway controller reports Programmed=False; its listeners are not up |
RoutingHealth | True | IngressActive | North/south traffic is served by operator-emitted Ingress |
RoutingHealth | True | IngressFallback | spec.gateway is enabled but the Gateway API is unavailable (CRDs absent or RBAC missing); serving via Ingress |
RoutingHealth | False | RoutingModeMismatch | The resolved routing path is disabled (e.g. mode: gateway with spec.gateway.enabled: false); nothing is emitted |
PluginsReady | True | PluginsImportable | All enabled plugins import successfully |
PluginsReady | False | PluginUnimportable | An enabled plugin failed to import; the message names it |
PluginsReady | False | SettingsLoadFailed | NetBox settings failed to load before any single plugin could be blamed (an enabled plugin broke settings import, or an invalid Python config override) |
LeaderElected | True | LeaderElectionAcquired | The operator pod named in message holds the leader lease. Present only on HA (multi-replica) operator deployments; only the leader writes status. |
HANotEnabled | True | HANotEnabled | Gates Ready. The cluster has 3 or more nodes but operator high availability is not enabled. Present only in that state. Run netbox-enterprise enable-ha to complete the setup. Embedded Cluster only — a Helm install has no such signal, so this condition does not appear. |
HAWithoutExternalData | True | HAWithoutExternalData | Gates Ready. Operator HA is enabled but a data tier cannot survive node loss. The message names which of PostgreSQL, Redis, or object storage is at fault. Requires a confirmed Embedded Cluster HA signal, so this condition never appears on a Helm install — see the note below. |
SharedStorageNotMultiNode | True | SharedStorageNotMultiNode | Advisory: netbox.replicas is above 1 with ReadWriteOnce/ReadWriteOncePod media and scripts volumes and object storage off, so the extra replicas will hang on volume multi-attach. Suppressed when object storage is in use. Never gates Ready, because the combination is legitimate on a single node or with pinned scheduling. |
InClusterStorageBelowNodeGate | True | InClusterStorageBelowNodeGate | Advisory: storageBackend is in-cluster but the cluster has fewer than 3 nodes, so all three Garage replicas can share a node and the store holds no real redundancy. Derived from the live node count, so it does apply to Helm installs. Does not gate Ready. |
MultiNodeNotEntitled | True | MultiNodeNotEntitled | Advisory: the cluster has 3 or more nodes but the license lacks the multi-node entitlement, so in-cluster PostgreSQL and Redis scaling is capped at a single instance. Does not gate Ready — a capped tier still runs correctly. |
AssetLifecycle | False | RequiresPremiumLicense | Asset Lifecycle was requested but the license is below Premium, so it was not enabled |
AssetLifecycle | False | ConflictsWithNetboxInventory | Asset Lifecycle was requested but netbox_inventory is present, so it was not enabled |
AssetLifecycle | False | ConflictsWithNetboxLifecycle | Asset Lifecycle was requested but netbox_lifecycle is present, so it was not enabled |
NdxReady | False | MissingToken | NDX was requested (spec.plugins.ndx.enabled, which defaults to true) but the license carries no paid NDX token, so NDX was not enabled. NetBox keeps running. Present only in that state. See NetBox Data Exchange. |
RedisSpreadHeld | True | RedisSpreadHeld | Advisory: two or more in-cluster Redis pods share a node and their ReadWriteOnce volumes pin them there, so the hard one-pod-per-node spread is held as a soft preference. Redis serves normally but is not protected against losing that node, and the state is permanent until an operator acts. Does not gate Ready. See Recovery steps. |
TlsCaTrust | False | CaSecretMissing | A referenced CA secret is absent, empty, or holds no PEM certificate. Covers both the keychainCaCertificates references of a TLS-verifying service (Redis, PostgreSQL, S3) and the global spec.extraCaCertificates entries. Surfaced here so the failure is visible before the bundle init container fails fast at runtime. |
TlsCaTrust | True | CaTrustResolved | The cluster references at least one CA secret and every one of them resolves. |
WheelhouseSourceProbe | False | SourceUnreachable | The configured custom-plugin wheelhouse source could not be reached. See Custom Plugins. |
MigrationSucceeded | True / False | — | Reports the outcome of the most recent database migration. |
SuperuserSecret | False | SuperuserSecretKeyMissing | Gates the NetBox pod, not Ready. A bring-your-own superuser Secret exists but does not carry a key named against it. The NetBox pod then never starts, and nothing else operator-side says why. See spec.netbox.config.superuser. |
SuperuserSecret | False | SuperuserSecretMissing | A named superuser Secret is absent. Self-correcting: a resource may be applied ahead of its Secret, and the condition flips True once the Secret exists. |
SuperuserSecret | True | SuperuserSecretResolved | Every superuser Secret key resolves. |
SpecManuallyEdited | True | ManualEditDetected | Advisory: kubectl edit or kubectl patch changed fields in the resource's spec. On Embedded Cluster 2.15.0 and later, an admin console deploy that sets a different value for one of those fields fails with a field-manager conflict. The message names the fields and the field manager. To recover, delete that field manager's entry from metadata.managedFields, then deploy again. Does not gate Ready. |
SpecManuallyEdited | False | NoManualEdits | No kubectl edit or kubectl patch field manager owns a field in spec. |
VeleroRestoreConfigInvalid | True | five reasons, below | Advisory: the velero namespace does not hold exactly one usable pod-volume-restore plugin config, so a future restore cannot return your volume data. See Velero restore plugin config. Does not gate Ready -- nothing is wrong with the running cluster. |
New in 2.3.0, the four PostgreSQL restore conditions:
| Condition | Status | Gates Ready? | Description |
|---|---|---|---|
PostgresRestorePending | True | No | A restored cluster whose PostgreSQL is wedged. Patroni failed to bootstrap over the empty data volume that a Velero restore leaves behind, and the instance pod parks with its database container not ready. The dependency wait already holds Ready=Unknown, so this condition only names the cause. See PostgreSQL Backups and Disaster Recovery. |
PostgresRecoveryRequired | True | Yes | A restored cluster whose PostgreSQL bootstrapped cleanly over an empty data volume. Every other surface reports success, and the cluster serves a database with none of your data in it. Derived live from Velero's velero.io/restore-name label and PGO's own PostgresDataInitialized condition, so it clears itself once the database is back. |
PostgresRestoreFailed | True | Yes | The pgBackRest restore Job is failing. It shows after several failed attempts: on the tested installs, after two to five attempts, up to five minutes. PostgreSQL is shut down for the whole retry cycle, so Ready is Unknown with WaitingForDependencies while it shows. It clears once PGO reports a database. See Restore fails in a loop. |
PostgresReconciliationPaused | True | No | PGO paused reconciliation of the PostgresCluster because PostgreSQL backups were disabled on a live cluster. The pause is total: no spec change, no upgrade, and no resource change reaches the database, while the database keeps serving. Set spec.postgresql.backups.enabled back to true to resume. |
PostgresRestoreFailed is a useful signal when it is present, and its absence proves nothing. On a cluster with no primary pod at all, the condition can be absent while the restore is still failing. Read status.pgbackrest.restore on the PostgresCluster directly in that case.
New in 2.4.0, PostgresBackupRepoPressure reports how full the pgBackRest backup repository is. An hourly probe CronJob measures it. The condition is advisory and does not gate Ready. It is absent when PostgreSQL is external and when backups are off. Before the first probe run, it is Unknown with NoProbeHistory. The operator records an event each time the condition changes.
It does not measure the PostgreSQL data volume, where WAL waits when archiving fails. See What fills the PostgreSQL data volume.
| Status | Reason | Description |
|---|---|---|
True | FilesystemNearlyFull | The filesystem that holds the repository is 70% full or more. On Embedded Cluster the repository shares the node's disk, so the message also says when most of the used space is outside the repository. |
True | RepoOverRequest | The repository is larger than spec.postgresql.backups.repoStorageSize. On Embedded Cluster that size is not a limit, so the repository keeps growing. |
False | WithinLimits | The latest reading is below both limits. |
Unknown | ReadingStale | The latest reading was below both limits, but it is more than 3 hours old. |
Unknown | ProbeFailing | The probe has never produced a reading. The message gives the error from its latest run. |
Unknown | ProbeNotInstalled | The probe CronJob does not exist. The RepoUsageProbeFailed events on the NetBoxEnterprise give the reason. |
Unknown | ProbeUnreadable | The operator could not read the probe's Jobs and pods. |
Unknown | NoProbeHistory | No probe run is left to read. This is the state of a new cluster before the first hourly run, and of a cluster whose probe runs are gone after an earlier reading. The next hourly run replaces it. |
A probe failure does not clear a True condition. The condition stays True, and its message adds the failure, so an alert on it does not clear while the probe is down.
When the condition is True, lower spec.postgresql.backups.retentionFull, or take full backups more often, and then expire the old backups. Do not raise repoStorageSize on Embedded Cluster: its storage class cannot expand a volume, so the reconcile fails. See PostgreSQL Backups and Disaster Recovery.
The AssetLifecycle condition is only present when Asset Lifecycle was requested and gated off. It is absent when Asset Lifecycle is disabled, and absent when it is enabled and healthy. See Asset Lifecycle Management.
TlsCaTrust is absent entirely when the cluster configures no custom CA trust, so an install that never asked for one carries no condition about it.
SuperuserSecret is absent when the operator generates the superuser credentials, because it then writes the very keys it reads.
Velero restore plugin config
Velero restores a filesystem-backed volume through a restore-wait init container, and it reads that container's security context from a ConfigMap in the velero namespace. It finds the ConfigMap by label selector, and it needs exactly one match.
The operator checks that on every reconcile and reports VeleroRestoreConfigInvalid when the check fails. The reason names which of five faults applies:
| Reason | What is wrong |
|---|---|
VeleroRestoreConfigMissing | No ConfigMap matches the selector. |
VeleroRestoreConfigDuplicated | More than one matches, and Velero needs one. |
VeleroRestoreConfigUnparseable | The secCtx key is not valid YAML. |
VeleroRestoreSecCtxIncomplete | The secCtx omits a required key. |
VeleroRestoreSecCtxUnsafe | A key holds a value the restore cannot survive. |
Read the reason first for the kind of fault, then read the message for the detail you act on. Only the message says which ConfigMaps matched, or which keys are absent.
The condition is absent when Velero is not installed. It is also absent under rbac.scope: namespace, because the reads are cross-namespace: the operator emits nothing rather than a false alarm. A namespace-scoped install has to run the count itself:
kubectl get configmap -n velero \
-l 'velero.io/plugin-config,velero.io/pod-volume-restore=RestoreItemAction'See The pod-volume-restore plugin config for what to do about each fault.
Ready is a weaker signal on Helm than on Embedded Cluster
HANotEnabled and HAWithoutExternalData are the two conditions that gate Ready, and both depend on an Embedded Cluster signal that a Helm install does not have. On Helm, a data tier that cannot survive node loss therefore produces no condition and no effect on Ready. Verify replica counts directly — see High Availability.
Each condition includes:
| Field | Type | Description |
|---|---|---|
type | string | Condition type |
status | string | True, False, or Unknown |
reason | string | Machine-readable reason code |
message | string | Human-readable detail |
lastTransitionTime | string | When the condition last changed |
observedGeneration | int64 | Spec generation when set |
Inspecting Conditions
# Get conditions as a table
kubectl -n netbox get netboxenterprise netbox -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'GarageCluster Conditions
When in-cluster object storage is enabled, the object store is a separate GarageCluster resource with its own conditions. These do not appear on the NetBoxEnterprise resource, so kubectl describe netboxenterprise will not show them:
kubectl -n netbox describe garagecluster| Type | Status | Reason | Description |
|---|---|---|---|
LayoutApplied | True | LayoutApplied | Garage has applied its storage layout |
LayoutApplied | False | LayoutPending | The storage layout has not been applied yet |
Serving | True | ClusterServing | Garage reports a write quorum for every partition and answers an S3 round-trip |
Serving | False | ClusterNotServing | Garage does not yet report a write quorum for every partition |
Ready | True | GarageServing | Garage is laid out and serving S3 requests |
Ready | False | LayoutPending | Garage is not yet both laid out and serving |
Ready | False | WaitingForPods | Too few StatefulSet pods are Ready to reach the admin API — ordinary first-provision startup |
Ready | False | QuorumLost | Too few pods are Ready and a layout already exists, so the cluster has lost quorum and genuinely cannot serve |
Ready | False | ReconcileFailed | Reconciliation encountered an error |
GarageSpreadHeld | True | GarageSpreadHeld | Advisory: two or more Garage pods share a node and their ReadWriteOnce volumes pin them there, so the hard one-pod-per-node spread is held as a soft preference. Object storage serves normally but is not protected against losing that node. See A held Garage spread. |
Ready is the conjunction of LayoutApplied and Serving, so a held spread cannot gate it — the store is serving, it just is not protected.
Component Status
Each component reports its own health under status.components:
| Field | Type | Description |
|---|---|---|
copilot | object | Copilot backend (present when Copilot is enabled). |
diode | object | Data ingestion pipeline (if enabled). |
netbox | object | Web application deployment. |
postgresql | object | Database (internal or external). |
redis | object | Cache/queue. |
worker | object | Background task worker. |
Each component has:
| Field | Type | Description |
|---|---|---|
conditions | []Condition | Component-specific conditions. |
message | string | Human-readable status (e.g., "2/2 replicas ready"). |
ready | bool | Whether the component is ready. |
Component conditions carry their own reason codes:
| Reason | Condition / Status | Description |
|---|---|---|
ComponentReady | Ready / True | The component is healthy and serving |
Progressing | Progressing / True | The component is starting or rolling out |
NotReady | Ready / False | The component is not yet ready |
Unknown | Ready / False | Component health could not be determined (for example, PostgreSQL or Redis status was unavailable) |
A component that fails also sets Degraded / True with a component-specific reason and the failure detail in message.
Inspecting Component Health
# All component statuses (components is an object keyed by component name)
kubectl -n netbox get netboxenterprise netbox \
-o jsonpath='{.status.components}' | jq# Specific component
kubectl -n netbox get netboxenterprise netbox \
-o jsonpath='{.status.components.postgresql}'License Information
License details are surfaced in status.license:
| Field | Type | Description |
|---|---|---|
channelName | string | Channel (stable, beta, dev). |
customerEmail | string | Customer email. |
customerName | string | Customer name. |
edition | enum | Dead field. The operator no longer writes it. It stays in the schema so a status written by an older operator still parses. Do not read it. Values: Community, Enterprise. |
entitlements.<name>.title | string | Display name of the entitlement. The map is the raw entitlements map from the license, keyed by entitlement name. |
entitlements.<name>.value | any | Value of the entitlement, of any JSON type. |
expiresAt | string | Expiration date (ISO 8601). |
isAssuranceEntitled | bool | Whether NetBox Assurance features are available. |
isCopilotEntitled | bool | Whether Private Copilot is entitled by the license (defaults false). |
isNdxEntitled | bool | Whether NetBox Data Exchange is entitled, meaning the license carries a paid NDX token. New in 2.3.0. See NetBox Data Exchange. |
isVisualExplorerEntitled | bool | Whether Visual Explorer, the first UI Platform app, is entitled. New in 2.3.0. |
licenseId | string | Unique license identifier. |
licenseType | string | License type (dev, trial, prod, paid). |
platformOrgId | string | Platform organization ID from the license. |
tier | enum | The tier your license carries. Community is the base tier of every license; Professional and Premium add features. Values: Community, Professional, Premium. |
kubectl -n netbox get netboxenterprise netbox -o jsonpath='{.status.license}'The operator also reports a LicenseHealth condition. status.licenseStatus carries the same verdict as one string, which is what the LICENSE column of kubectl get netboxenterprise prints.
| Condition | Status | Reason | Description |
|---|---|---|---|
LicenseHealth | True | LicenseValid | The license is present and current. |
LicenseHealth | False | LicenseExpired | The license is past its expiry date. Enterprise features may be unavailable. Renew it in the Replicated vendor portal. |
LicenseHealth | False | LicenseUnavailable | No license could be fetched and no cached license exists. Check that the in-cluster Replicated SDK is reachable. Reconciliation is blocked until a license is available. |
LicenseHealth | False | LicenseUnrecognized | The SDK answered with a license type this release does not recognize. Distinct from LicenseUnavailable, which means no answer at all. |
LicenseHealth | False | LicenseInvalid | The license failed a tamper check. Reconciliation is blocked while that stands. |
The LICENSE column's own help text, which kubectl explain prints, names a LicenseMismatch reason. The operator does not emit that reason. Read the five rows above as the real set.
Generation Tracking
The operator tracks observedGeneration to detect configuration drift:
metadata.generation: Incremented by Kubernetes whenspecchangesstatus.observedGeneration: Set by the operator after processing a generation
If observedGeneration < metadata.generation, the operator has not yet processed the latest spec change.
# Check for pending changes
kubectl -n netbox get netboxenterprise netbox \
-o jsonpath='gen={.metadata.generation} observed={.status.observedGeneration}'Reconciliation Events
The operator publishes Kubernetes events for each reconciliation:
# All operator events
kubectl -n netbox get events --field-selector source=nbe-operator
# Watch events in real time
kubectl -n netbox get events -w --field-selector source=nbe-operatorEvent types include:
- Normal: Resource applied successfully, reconciliation complete
- Warning: Readiness check failed, reconciliation error
See Also
- Troubleshooting - Using status to diagnose issues
- NetBoxEnterprise CRD - Full spec reference