For ten years, the HorizontalPodAutoscaler had a floor it could not cross: one replica. On September 2, 2026, SIG Autoscaling changed that — HPA scale-to-zero is now beta and enabled by default in Kubernetes 1.37. Here is what that means for your platform, up front.
The verdict up front
If your idle workloads consume from a queue, you can start deleting your bespoke idle-reaping logic this quarter. If they serve HTTP, you cannot — not yet, and possibly not ever with HPA alone. The whole story fits in one table:
| Workload shape | Verdict | Why |
|---|---|---|
| Queue consumers, batch workers | Adopt native HPA now | One minReplicas: 0 HPA replaces your reaping cron plus a min-1 autoscaler |
| HTTP services with bursty traffic | Keep Knative, KEDA, or your current path | Kubernetes Services do not buffer requests, so the wake-up request dies at zero replicas without an activator-style layer |
| Latency-sensitive services | Do not scale to zero at all | Cold starts of 30–60 seconds are a property of physics, not of whichever autoscaler you pick |
The rest of this post justifies each cell: what shipped, how the mechanism actually works, and the four gaps the HPA cannot see that keep a platform-level policy layer necessary.
The news in one paragraph
Kubernetes 1.37 ("Garhwal," released August 26, 2026) graduates HPA scale-to-zero to beta and turns it on by default. The September 2 announcement from SIG Autoscaling's Johannes Würbach covers KEP-2021, a feature with real history: the first alpha shipped back in Kubernetes v1.16, v1.36 added the ScaledToZero status condition that makes the behavior safe, and v1.37 enables the HPAScaleToZero gate by default after adding integration and end-to-end coverage for scaling down to zero and back up from an external metric.
The user-visible change is exactly one line: an HPA driven by an object or external metric can now set minReplicas: 0. When the metric says idle, the last pod goes away. When demand returns, the HPA brings it back.
How it works: the ScaledToZero condition
The reason this took ten years is an ambiguity: a replica count of zero can mean "the autoscaler scaled this down" or "an operator paused this." Waking a paused workload would be a bug; leaving an autoscaled workload asleep would be an outage. The v1.36 redesign resolves it with a status condition. When the HPA scales a workload from one or more replicas to zero, it records ScaledToZero=True, telling later reconciliation loops that the controller owns the zero state and should keep evaluating metrics. After scaling back up, the condition flips to ScaledToZero=False with reason NotScaledToZero. A workload sitting at zero without that condition stays paused — inspect it with kubectl describe hpa.
Three more mechanics matter before you write the YAML:
- Object or external metrics only. CPU and memory come from running pods, and at zero replicas there is nothing left to measure. The API server rejects
minReplicas: 0on an HPA that only has resource metrics. A queue length, by contrast, exists independently of the workers consuming it, so the HPA can keep reading it while no workers run. - Start with at least one replica. Manually setting a Deployment to zero has always paused autoscaling, and the HPA preserves that behavior — it will not wake a workload it did not scale down itself.
- The five-minute stabilization window still applies. The default downscale stabilization window prevents a momentary dip in queue length from deleting every worker. Tune it via
spec.behavior.scaleDownif your workload needs different behavior.
A minimal example, adapted from the announcement, scales a queue-worker Deployment between zero and ten replicas with one replica per 30 queued tasks:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: queue-worker
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 0
maxReplicas: 10
metrics:
- type: External
external:
metric:
name: queue_consumer_lag
selector:
matchLabels:
name: worker_tasks
target:
type: Value
value: "30"Before creating the HPA, verify the metric is readable through the External Metrics API (kubectl get --raw against external.metrics.k8s.io) and fix the metrics pipeline first if it is not. An HPA cannot scale from zero when its metric is unavailable — more on that failure mode below.
Cell 1 justified: deleting the worker idle-reaper
This is the cell where a git-push PaaS saves real work. Today, a platform that wants worker-shaped tenant workloads to cost nothing at idle typically runs two control loops: a bespoke reaper (a cron or controller that watches for idleness and scales Deployments to zero) plus a standard HPA floored at one replica to handle the 1-to-N range. The reaper is custom code the platform owns forever — edge cases around deploys landing on a zeroed workload, metrics gaps, and wake-up races included.
Native scale-to-zero collapses those two loops into one HPA config. The mechanism is now the same controller, the same metrics path, and the same kubectl describe surface as the rest of autoscaling, with the pause-vs-scaled distinction handled by the ScaledToZero condition instead of by tribal knowledge in a runbook. The savings are exactly what the announcement calls out: largest when each pod reserves expensive resources, including dedicated CPUs or GPUs. For a self-hosted fleet on owned hardware, that is not a smaller cloud bill — it is freed capacity on machines you already pay for, which is the same money viewed from the other side.
The honest scope limit: this covers workloads whose demand signal lives outside the pods. Queue consumers, batch processors, webhook drainers, scheduled-job workers — anything where "is there work?" can be answered by a metric that survives at zero replicas. That is a large fraction of what idle-reaping crons do today, and it is the fraction you should migrate first.
Cells 2–3 justified: why HTTP stays put
Here is the sentence from the announcement that decides the HTTP question: "Kubernetes Services do not buffer requests while no Pods are ready, so HTTP and other request-driven workloads need a separate buffering layer."
Walk through what happens without one. A request arrives for a service at zero replicas. There is no pod to route to and nothing holding the connection, so the request fails while the HPA observes the metric, schedules a pod, pulls the image, and waits for readiness. The autoscaler did everything right and the user still got an error. This is not a tuning problem — it is a missing component.
That component is what Knative's activator is. When a Knative service sits at zero (the default after 60 seconds without requests), the activator intercepts the first request, holds the connection open, triggers the cold start, and forwards the buffered request once a pod is ready. The caller sees a slow response, not an error — as long as the client timeout exceeds the worst-case cold start. KEDA plays a related but different role for event-driven workloads: its two-phase wake jumps 0→1 on an activation threshold, then hands the 1→N range to the HPA it manages, typically within a single 15-second window.
Neither behavior exists in core HPA, and the beta does not try to add it. So the HTTP decision is unchanged by v1.37: keep Knative if you want request-driven scale-to-zero with buffering, keep KEDA if your wake signal is events, or keep your platform's current always-warm floor. Migrating HTTP services to bare minReplicas: 0 trades your reaper for dropped wake-up requests — strictly worse.
Cell 3 follows from cold-start arithmetic that no autoscaler can fix. Render's free tier, the industry's most familiar scale-to-zero experience, spins services down after 15 minutes of idleness and pays 30–60 seconds to wake them. Railway keeps paid services warm by default precisely because that penalty is unacceptable for production traffic. If your tenant's SLO cannot absorb a cold start, the correct minimum replica count is one, regardless of what the HPA now permits.
The four gaps HPA can't see
Even for worker workloads where the verdict is "adopt," four concerns stay at the platform policy layer:
1. Per-tenant cold-start budgets. The HPA knows queue depth; it does not know that tenant A's worker boots in 4 seconds while tenant B's pulls a 3 GB image and needs two minutes. A platform still needs per-tenant (or per-workload-class) policy deciding which workloads are allowed to reach zero. Beta-test the mechanism on fast-booting workers first.
2. Wake-latency SLOs. Related but distinct: even when a workload can go to zero, the queue-wait plus cold-start time must fit whatever the tenant was promised. Durable queues absorb this gracefully — the announcement explicitly scopes the feature to "work [that] can wait in a durable queue" — but somebody has to verify the queue is actually durable and the wait is actually acceptable. That somebody is your platform, not the autoscaler.
3. The metric pipeline is now load-bearing for availability. If the adapter cannot serve the configured metric, the HPA reports ScalingActive=False (reason FailedGetExternalMetric) and a workload at zero stays at zero. Before v1.37, a broken custom-metrics pipeline meant degraded scaling; now, for zeroed workloads, it means an outage that only manual intervention fixes. Do not delete the old reaper until the metrics path has proven itself — monitor ScalingActive per HPA and alert on it like any other availability signal.
4. The version-skew upgrade hazard. In v1.37 the gate is enabled by default on both kube-apiserver and kube-controller-manager, and both must support it: a controller-manager with the feature disabled treats replicas: 0 as a manual pause and may leave a workload at zero. During a skew window, wait until both components are upgraded before creating minReplicas: 0 HPAs. And before any downgrade or gate-off, move affected HPAs back to minReplicas: 1 and manually scale zeroed workloads up first.
What a git-push PaaS should do this quarter
Concretely: adopt native HPA scale-to-zero for worker-shaped tenant workloads now, keep HTTP on whatever path it uses today, and treat the migration as a policy project with a mechanism at its center rather than a config flip. Migrate fast-booting queue consumers first, keep the old reaper running in shadow or as a fallback until ScalingActive history earns your trust, and write the per-tenant cold-start policy before you need it. Then do what beta is for: run it, watch the ScaledToZero conditions, and feed what you learn back to SIG Autoscaling on the #sig-autoscaling Slack channel. GA arrives faster when operators show up with data.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



