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 |
|---|---|---|
ready | bool | Overall cluster readiness - true only when all components are ready |
message | string | Human-readable summary of the current state |
version | string | Operator version managing this cluster |
observedGeneration | int64 | The spec generation last processed by the operator |
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. |
lastReconcileTime | string | Timestamp of the last successful reconciliation (RFC 3339) |
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 |
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 |
phase | enum | Coarse startup phase. See Startup Phase. |
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. |
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. |
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.3.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 |
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. |
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 fires on the first failed attempt, because PostgreSQL is shut down for the whole retry cycle. 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.
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 PostgreSQL Backups and Disaster Recovery 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:
| Component | Key | Description |
|---|---|---|
| NetBox app | components.netbox | Web application deployment |
| NetBox worker | components.worker | Background task worker |
| PostgreSQL | components.postgresql | Database (internal or external) |
| Redis | components.redis | Cache/queue |
| Diode | components.diode | Data ingestion pipeline (if enabled) |
| Copilot | components.copilot | Copilot backend (present when Copilot is enabled) |
Each component has:
| Field | Type | Description |
|---|---|---|
ready | bool | Whether the component is ready |
message | string | Human-readable status (e.g., "2/2 replicas ready") |
conditions | []Condition | Component-specific conditions |
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 |
|---|---|---|
tier | enum | The tier your license carries: Professional or Premium. The schema accepts further values that no available license uses. |
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. |
licenseId | string | Unique license identifier |
licenseType | string | License type (dev, trial, prod, paid) |
customerName | string | Customer name |
customerEmail | string | Customer email |
channelName | string | Channel (stable, beta, dev) |
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. |
platformOrgId | string | Platform organization ID from the license |
entitlements | map | Raw entitlements map keyed by entitlement name. Each entry has a title (string) and a value (any JSON type). Used for additional checks. |
expiresAt | string | Expiration date (ISO 8601) |
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