Kubernetes HPA Not Scaling? The 7 Causes, in Order
The HorizontalPodAutoscaler is up, traffic is spiking, and replicas won't move. Seven causes in the order we check them, each with the command that confirms it.
Key takeaways
- When a Kubernetes HPA won't scale, run kubectl describe hpa and read its targets and conditions.
- The usual causes are a missing metrics-server, Pods without CPU requests, the 10% tolerance, the 300-second scale-down window, maxReplicas already reached, or new Pods that are not yet ready.
On this page
When a HorizontalPodAutoscaler won't scale, the HPA usually tells you why. Most people look at kubectl get hpa, see <unknown> or a target that looks fine, and start guessing. The answer is in kubectl describe hpa: its targets and its conditions name the cause in most cases.
$ kubectl describe hpa web -n shop
...
Metrics: ( current / target )
resource cpu on pods: <unknown> / 70%
Min replicas: 2
Max replicas: 10
Conditions:
Type Status Reason Message
AbleToScale True SucceededGetScale the HPA controller was able to get the target's current scale
ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count: failed to get cpu utilization
ScalingActive False means the HPA can't even compute a number. ScalingLimited True means it computed one but a bound stopped it. Those two conditions sort the seven causes into two groups.
| # | Cause | What you see | Fix |
|---|---|---|---|
| 1 | No metrics API | <unknown>, kubectl top fails | Install metrics-server |
| 2 | No CPU or memory request | <unknown> for that resource | Set requests on every container |
| 3 | Wrong target | AbleToScale False | Fix scaleTargetRef |
| 4 | Within tolerance | Target shows 75% / 70%, nothing happens | Expected; lower the target if needed |
| 5 | Scale-down window | High replicas stay for 5 minutes | Expected; tune behavior |
| 6 | Bound reached | ScalingLimited True, TooManyReplicas | Raise maxReplicas, check capacity |
| 7 | Pods not ready | Scale-up smaller than expected | Fix readiness, startup time |
When the HPA can't compute anything
1. The metrics API isn't there. Resource metrics come from the metrics.k8s.io API, which the Kubernetes docs note is usually provided by metrics-server, an add-on you launch separately. Many local and some managed clusters don't have it.
$ kubectl top pods -n shop # fails if the metrics API is missing
$ kubectl get apiservice v1beta1.metrics.k8s.io
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/shop/pods | head -c 300
2. Containers have no requests. Utilization targets are a percentage of the request. From the Kubernetes documentation: if some of a Pod's containers don't have the relevant request set, CPU utilization for the Pod isn't defined and the autoscaler takes no action for that metric. A sidecar without a request counts too, so check every container, not just the app. Getting requests right helps far beyond autoscaling, as we covered in rightsizing resource requests.
3. The target reference is wrong. A typo in scaleTargetRef, the wrong apiVersion, or an HPA pointing at a Deployment that was renamed shows up as AbleToScale False. Two HPAs targeting the same workload also fight each other; keep one per workload.
When it computes a number but doesn't act
4. You're inside the tolerance. The controller computes desiredReplicas = ceil(currentReplicas × currentMetric / desiredMetric) and skips any change when that ratio is within 0.1 of 1.0 by default. At a 70% target, 75% utilization is a ratio of about 1.07, so nothing happens. That's intended, to prevent flapping.
5. You're waiting on the scale-down window. Scale-up reacts within about 15 seconds by default, but scale-down uses a 300-second stabilization window: the HPA picks the highest recommendation from the last five minutes. If replicas "won't come down", wait five minutes before debugging. You can tune both directions:
# hpa-web.yaml (autoscaling/v2)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: shop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 120
6. You hit a bound. ScalingLimited True with reason TooManyReplicas means maxReplicas is reached. If replicas did increase but new Pods sit Pending, the HPA did its job and the cluster is out of capacity; that's a node autoscaler or quota problem, not an HPA problem.
7. New Pods aren't ready yet. For CPU, the controller sets aside Pods that aren't ready or are still initializing and, when scaling up, conservatively assumes they use 0% of the target. Slow-starting apps, such as JVM services, therefore scale up in smaller steps than you'd expect. Fix readiness probes and startup time before tuning the HPA.
Scaling on custom or external metrics
CPU and memory come from metrics-server, but requests per second, queue depth or latency need a metrics adapter that serves the custom.metrics.k8s.io or external.metrics.k8s.io APIs, such as Prometheus Adapter or KEDA. Without one, the HPA reports ScalingActive False with a reason like FailedGetPodsMetric or FailedGetExternalMetric.
$ kubectl get apiservices | grep -E 'custom.metrics|external.metrics'
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | head -c 300
If the API is registered but the metric name returns nothing, the adapter's query or naming rules don't match what your Pods export. Check the metric in Prometheus first, then the adapter configuration, before suspecting the HPA.
Questions people ask
Why does my HPA show unknown for CPU?
<unknown> means the HPA can't read the metric. Either the metrics API is missing (install metrics-server and confirm kubectl top pods works), or a container in the Pod has no CPU request, which leaves utilization undefined. Check every container, including sidecars, for resources.requests.cpu.
Why isn't my HPA scaling down?
By default, scale-down uses a 300-second stabilization window, so the HPA keeps the highest replica count recommended in the last five minutes. Also check minReplicas, and whether utilization is within the default 10% tolerance of the target, in which case the HPA intentionally does nothing.
How fast does a Kubernetes HPA scale up?
With default behavior, it can add up to 100% of current replicas or 4 Pods, whichever is more, every 15 seconds, with no stabilization window for scale-up. In practice, metrics collection and Pod startup time add delay, so slow-starting apps scale more slowly than the policy allows.
The decision, concretely
<unknown>target? Check the metrics API first, then requests on every container.- Number computed but no change? Compare current and target; within 10% is normal.
- Replicas stuck high? Wait out the 300-second window, then tune
behavior.scaleDown. - Replicas went up but Pods are Pending? Look at cluster capacity, not the HPA.
The call we'd make
Make kubectl describe hpa your first move and read the conditions before touching configuration. Then fix the boring prerequisites, metrics-server and requests on every container, which resolve most HPA tickets we see. Tune behavior only after those are right, and only with real traffic graphs in front of you.
Sources
More from DevOps
Explore more articles in this category
Argo CD App Always OutOfSync? Find the Field, Then Fix It
The sync succeeds, the app goes green for a second, then OutOfSync again. Almost always a controller is changing a field Git also sets. How to find it and the three fixes, in order of preference.
GitHub Actions Security: 7 Fixes After the Trivy Compromise
In March 2026 an attacker force-pushed 76 trivy-action tags to an infostealer that read secrets straight out of runner memory. Seven fixes that would have contained it, in the order we'd apply them.
Do You Need Kubernetes in 2026? A Decision Framework
Kubernetes is the right answer for fewer teams than run it. Five questions that tell you whether you need it, and what to run instead when you don't.
You might have missed
Evergreen posts worth revisiting.