Scheduling Control Plane Updates with Gardener Maintenance Windows
Gardener Shoot maintenance windows let you schedule automated Kubernetes control plane updates. Learn how the timeWindow field works, its limits, and how to verify an upgrade occurred.
14 Dec 2025, 18:11 UTC

The 10 a.m. API server restart problem
You have a Gardener-managed Shoot cluster running production workloads. Overnight, the platform decides to upgrade the Kubernetes control plane, and suddenly your CI pipelines and kubectl commands fail for a minute. You could disable automatic upgrades and patch by hand, but that trades surprise downtime for forgotten security updates and drift across clusters. The useful takeaway is that Gardener lets you declare a maintenance.timeWindow in the Shoot spec so disruptive control plane work happens on your schedule, not the platform's.
What a maintenance window actually controls
Gardener manages Shoot clusters—your workload Kubernetes clusters—by running their control planes on Seed clusters. Controllers in the Garden cluster continuously reconcile the actual state toward the desired state expressed in the Shoot resource. A maintenance window is a declarative schedule inside that resource. It tells the reconcilers when they may perform automatic Kubernetes version upgrades and control plane restarts.
Two things are worth separating in your mind. First, the window governs when the platform may act, not what it will do. Second, it does not force an upgrade; it only opens a gate. If no compatible Kubernetes version is available or the Seed is unhealthy, the window passes without action.
Why not just upgrade manually?
Manual updates are easy to postpone and hard to standardize across dozens of Shoots. Always-on automation removes the human step but can restart the API server at inconvenient times. A maintenance window sits between those extremes: you keep automated patching, but you align it with a predictable low-traffic period.
A worked example
The relevant configuration lives under spec.maintenance.timeWindow. The exact field names and timestamp format vary by Gardener release, so treat the following as a conceptual illustration and validate it against your installation's API reference before applying.
spec:
maintenance:
timeWindow:
begin: "220000-0100"
end: "230000-0100"
timezone: "America/New_York"In this pattern, begin and end define a one-hour slot with a UTC offset, and timezone anchors the recurrence to local time. During that hour, Gardener may upgrade the control plane or restart it if the new version requires it. Outside the window, automatic disruptive operations wait.
Trade-offs and limitations
- Brief API server unavailability. Even if worker node workloads keep running, a control plane restart can make the Kubernetes API server unreachable for a short time. Anything that depends on the API during that minute—CI runners, admission webhooks, kubectl commands—will see errors.
- Timezone and recurrence mistakes are expensive. If the offset or timezone is wrong, a critical security patch might sit unapplied for days while you believe automation is handling it.
- Dependency on the Seed and available versions. Automatic upgrades only occur when the Seed cluster is healthy and your Gardener installation offers a compatible Kubernetes version. A window does not override platform constraints.
How to verify the window is working
Run these commands against the Garden cluster where the Shoot resource is stored, not against the Shoot itself. You need read access to the project namespace.
First, confirm the schedule is declared as expected:
kubectl get shoot <shoot-name> -n <project-namespace> -o yaml | grep -A 10 maintenance
Expected check: the output shows timeWindow with the begin, end, and timezone values you intended. If the block is missing, the Shoot will not use a scheduled window.
Next, capture the current control plane version before the window opens:
kubectl get shoot <shoot-name> -n <project-namespace> -o jsonpath='{.status.kubernetesVersion}'After the window closes, run the same command again. If the value changed, an automatic upgrade occurred. If it did not, inspect the Gardener controller logs for reconciliation events around the scheduled time:
kubectl logs -n garden <gardener-controller-pod> --since=2h
These commands are read-only and do not change cluster state. Applying the YAML snippet above, however, modifies the Shoot resource and triggers reconciliation.
Start narrow, then expand
Pick a short window in a non-production Shoot first, confirm that status.kubernetesVersion moves as expected, and check that your workloads tolerate the brief API interruption. Once you trust the behavior for your specific Gardener version, apply the same pattern more broadly. Maintenance windows will not eliminate every surprise, but they move the ones that remain to hours you chose.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.