# Kubernetes HPA Not Scaling? The 7 Causes, in Order

URL: https://www.devopsness.com/blog/kubernetes-hpa-not-scaling
Author: Kiril Urbonas
Published: 2026-10-29
Updated: 2026-10-30
Categories: DevOps
Tags: Kubernetes, Performance, Autoscaling, Kubectl, Troubleshooting

Summary: 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.

---
# Kubernetes HPA Not Scaling? The 7 Causes, in Order

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.

```bash
$ 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.

```bash
$ 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](/blog/kubernetes-resource-requests-rightsizing).

**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:

```yaml
# 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`.

```bash
$ 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

- [Kubernetes documentation: Horizontal Pod Autoscaling](https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/)
- [Kubernetes documentation: HorizontalPodAutoscaler walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)
- [kubernetes-sigs/metrics-server](https://github.com/kubernetes-sigs/metrics-server)

