Upgrades¶
This guide explains how to upgrade your Neo4j Enterprise clusters.
Rolling Upgrades¶
The operator supports rolling upgrades to minimize downtime. To upgrade your cluster, update the spec.image.tag field in the Neo4jEnterpriseCluster resource to a newer version:
kubectl patch neo4jenterprisecluster <name> \
--type=merge \
-p '{"spec":{"image":{"tag":"2025.01.0-enterprise"}}}'
The operator performs a safe, ordered rolling upgrade:
- Freezes all pods — sets the StatefulSet partition to block any automatic restarts.
- Restarts pods highest-ordinal-first — pods
N-1,N-2, …1are restarted in order. Kubernetes StatefulSet partition-based rolling always restarts pods from highest ordinal to lowest, so pod0is naturally the last to restart. - Verifies the server and its databases after each pod — after Kubernetes reports the pod Ready, the operator checks that the server is
Enabled/AvailableinSHOW SERVERS, and then, on that server, that every one of its databases has caught up:dbms.cluster.statusCheckmust report each clustered database replicating with the server's own copyAPPLYING, andSHOW DATABASESmust show every database on it at its requested status (which covers single-primary databases and secondaries). Only then does the next pod restart. A server is Available as soon as it rejoins, while its databases can still be starting or catching up; restarting the next server in that window would leave a database with three primaries one working copy. While the walk waits,status.upgradeStatus.messagenames the databases it is waiting for. A database that has not caught up withinupgradeTimeoutstops the upgrade (Failed, orPausedwithautoPauseOnFailure), andstatus.upgradeStatus.lastErrornames it. - Rolls pod 0 last — the operator attempts to detect which pod holds the primary role for the
systemdatabase (viaSHOW DATABASES) and logs a warning if it is not ordinal 0. Ordinal 0 is always the last pod rolled regardless, so the system-database primary is preserved as long as possible. - Waits for cluster stabilisation — after all pods are updated the operator waits for the cluster health and consensus state to be consistently stable before marking the upgrade complete.
Writes stay available throughout. As a server shuts down, Neo4j hands the leadership of each database it leads to another server, so a restart does not leave a database waiting for an election. Clients still see brief, retryable interruptions around each restart, a few failed writes and pauses of a few seconds, so use the drivers' retrying transaction functions (executeWrite and equivalents), which ride over them. Longer pauses, of about 10 seconds, came from Neo4j's 30-second cluster connect timeout meeting a restarted server's new IP address; the operator lowers that timeout to 5 seconds (see Writes while a restarted server rejoins).
If you have made databases read-only on individual servers with dbms.setConfigValue('server.databases.read_only', …), upgrade with those settings removed. Neo4j can hand a leadership to a server that is read-only for that database, and that handover is lost: the database then has no leader for about 20 seconds (dbms.cluster.raft.leader_failure_detection_window).
Watch progress with:
# StatefulSet rollout progress
kubectl rollout status statefulset/<cluster>-server -n <namespace>
# Operator upgrade status
kubectl get neo4jenterprisecluster <name> -o jsonpath='{.status.upgradeStatus}'
# Each server's state, kept current during the upgrade
kubectl get neo4jenterprisecluster <name> \
-o jsonpath='{range .status.diagnostics.servers[*]}{.name}{"\t"}{.health}{"\n"}{end}'
# Operator logs
kubectl logs -n neo4j-operator-system -l control-plane=controller-manager -f | grep -i upgrade
Upgrade phases and resumability¶
The upgrade runs as a state machine whose state is persisted in
status.upgradeStatus, so you can follow it with kubectl and it survives
operator restarts:
| Phase | What's happening |
|---|---|
Staging |
Pre-upgrade health check, version-compatibility check, StatefulSet frozen (partition = replica count), new image applied to the template |
Rolling |
Pods restart one at a time, highest ordinal first; after each pod, Kubernetes readiness, Neo4j cluster membership (SHOW SERVERS) and that pod's databases (dbms.cluster.statusCheck, SHOW DATABASES) are verified before the partition is lowered |
Stabilizing |
All pods updated; the operator waits for consistently stable cluster health and consensus |
Verifying |
Every server's reported version is checked against the target before the upgrade is declared done |
Completed / Failed / Paused |
Terminal. Paused means a health check failed with autoPauseOnFailure: true — the cluster is left as-is for you to investigate |
Two status fields drive the machine and are useful when watching a long
upgrade: currentPartition (which StatefulSet partition step the rollout is
on — counts down toward 0) and stepStartTime (when the current step began;
each per-pod step is bounded by upgradeTimeout/healthCheckTimeout).
Resumability. Because the phase and partition are persisted in status, an operator restart (or leader change) mid-upgrade resumes exactly where it left off — already-rolled pods are not rolled again.
Changing the target mid-upgrade. If you patch spec.image.tag again while
an upgrade is running, the state machine retargets to the new image: pods not
yet rolled go straight to the new version, and pods already rolled to the
intermediate version are rolled again. You don't need to wait for the first
upgrade to finish.
Version verification across SemVer/CalVer. The Verifying phase compares
each server's SHOW SERVERS version against the image tag, normalizing
Neo4j's kernel alias (the 2025.01.x CalVer releases report kernel 5.27.x —
both are accepted as the same version).
Image changes while the cluster is not Ready are deferred. Changing
spec.image while the cluster is Forming, Degraded, mid-restore, or
otherwise not Ready — or while it is Ready but a server is unavailable (its
Degraded condition is True) — does not roll pods immediately: the operator
holds the change (you'll see an UpgradeDeferred event on the cluster) and the
rolling-upgrade state machine performs it once the cluster is Ready with every
server available — a version change must never sneak in as an ungated restart
while quorum is still forming, and rolling the other servers while one is down
could cost the cluster its majority.
One deliberate exception: if no server pod has ever become ready — the
typical case being a first deploy stuck in ImagePullBackOff on a mistyped
tag — there is no formed quorum to protect, so a corrected spec.image
applies immediately. Fix the tag and the pods re-pull; you do not need to
delete and recreate the cluster.
First upgrade after operator deployment¶
If status.version is not yet set on the cluster resource (e.g. immediately after the operator is first deployed against an existing cluster), the version-compatibility check is skipped and the upgrade proceeds. Downgrade protection is applied on all subsequent upgrades.
Upgrade Strategy¶
Configure the upgrade strategy with spec.upgradeStrategy:
| Strategy | Description |
|---|---|
RollingUpgrade (default) |
Restarts one pod at a time; cluster stays available throughout |
Recreate |
Deletes and recreates all pods; faster but causes downtime |
spec:
upgradeStrategy:
strategy: RollingUpgrade
preUpgradeHealthCheck: true # validate cluster health before upgrading (default true)
autoPauseOnFailure: true # pause the upgrade for manual intervention if a check fails (default true)
upgradeTimeout: 30m # per-pod step: readiness, rejoin and databases caught up (default 30m)
healthCheckTimeout: 5m # post-upgrade version verification (Verifying) timeout (default 5m)
stabilizationTimeout: 3m # post-upgrade cluster-stabilization wait (default 3m)
postUpgradeHealthCheck: true # validate cluster health after upgrade (default true)
Servers always restart one at a time, as Neo4j's rolling upgrade requires. maxUnavailableDuringUpgrade is accepted for compatibility but has no effect; a value other than 1 raises a ValidationWarning event.
When preUpgradeHealthCheck is enabled, the operator verifies connectivity to Neo4j before changing the image. If the check fails and autoPauseOnFailure is true, the upgrade is blocked until you intervene rather than proceeding against an unhealthy deployment.
For the full field reference see the API Reference.
Supported Upgrade Paths¶
| From | To | Supported |
|---|---|---|
| SemVer 5.26.x | SemVer 5.26.y (patch only) | ✅ |
| SemVer 5.26.x | CalVer 2025.y / 2026.y | ✅ (only 5.26.x — the last SemVer LTS — may cross to CalVer) |
| CalVer 2025.x | CalVer 2025.y / 2026.y (newer minor, patch, or year) | ✅ |
| CalVer 2025.x | SemVer 5.y | ❌ (downgrade) |
| Any | earlier version | ❌ (downgrade) |