DevTools Hub

Search tools

Search for a developer tool

Kubernetes Rate Limiting Explained

Part of the Kubernetes Toolkit

"Kubernetes rate limiting" means three genuinely different things depending on which direction the traffic is flowing, and mixing them up is the single most common source of confusion on this topic. There's the client throttling itself before it even sends a request, the control plane protecting itself from too many requests, and an Ingress controller protecting your application from too many requests. Kubernetes gives you the first two out of the box. The third — the one most people searching this phrase actually want — it gives you nothing for, by design.

Layer 1: the client throttles itself before it even asks

Every kubectl command and every controller talking to the API server goes through client-go, which ships with its own built-in rate limiter — separate from anything the server does. The default is small enough to surprise people at cluster scale:

DefaultQPS   = 5   // steady-state requests per second
DefaultBurst = 10  // allowed burst above that

That's the default for a single client instance — a controller doing real reconciliation work against a large or highly dynamic cluster can hit this ceiling and throttle itself long before the API server itself is under any real pressure. It's configurable per-client (--kube-api-qps / --kube-api-burst on many controllers, or directly on a custom rest.Config), and it's the reason a slow-feeling controller isn't automatically a server-side problem.

Layer 2: the API server protects itself

Every kubectl get, every controller's reconcile loop, every kubelet heartbeat is an API server request. With no ceiling, a runaway controller or a misbehaving CI pipeline can starve out the traffic that actually keeps the cluster healthy — so the API server enforces its own concurrency limits, independent of whatever any individual client does.

The blunt version — still the underlying bound today — is two flat flags: --max-requests-inflight (default 400, for reads) and --max-mutating-requests-inflight (default 200, for writes). Since Kubernetes 1.20, API Priority and Fairness (APF) sits on top of that same combined budget and is enabled by default: instead of one shared pool where a noisy client can starve everyone else, APF divides that total concurrency into named priority levels (system traffic, leader-election, workload traffic, and more) with fair queuing within each — so a workload flooding the API server can only ever exhaust its own slice, not the concurrency leader-election or core system controllers depend on.

APF measures concurrency in seats — one seat per unit of work, inspired by a fixed number of seats on a train. Most requests take one; a list request the server estimates will return a large number of objects takes proportionally more; a watch holds its seat only for its initial notification burst, not for its entire (potentially long-lived) connection.

When a request can't get a seat, the API server rejects it with HTTP 429 and a Retry-After header — client-go retries this automatically for most callers, which is why APF throttling is often invisible unless you're specifically watching for it:

kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total

A nonzero, climbing value there means real requests are being rejected at the control plane — the concrete signal that this layer, not the one below, is where to look first.

Layer 3: nothing protects your application — you have to add it

This is the layer most "kubernetes rate limiting" searches are actually about, and it's the one Kubernetes itself has zero opinion on. A Service or Ingress object has no rate-limit field anywhere in its spec — if you want to cap how many requests an external client can send to your app, that has to come from whatever sits in front of it: the Ingress controller, or a service mesh.

For the widely-used ingress-nginx controller, that means annotations on the Ingress resource itself:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"
    nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/24"
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80

limit-rps caps requests per second per source IP; the effective burst allowed above that is the rate multiplied by limit-burst-multiplier (default 5×, so limit-rps: "10" here allows bursts up to 50); limit-connections caps concurrent connections per IP instead of a rate; and limit-whitelist exempts specific CIDR ranges entirely.

Two details that catch people specifically because they're not obvious from the annotation names:

  • The limit is per controller replica, not per cluster. If ingress-nginx runs 3 replicas — or scales via HPA — a limit-rps: "10" annotation permits roughly 30 requests per second in practice, not 10. Autoscaling the ingress controller for availability silently raises the effective rate limit at the same time, which is easy to miss during capacity planning.
  • Exceeding it returns HTTP 503, not 429. The exact opposite of the control-plane layer above — the status code alone tells you which layer actually rejected a request. (The code itself is set at the ingress-nginx ConfigMap level via limit-req-status-code, not per-Ingress, so it's consistent across every Ingress the controller serves.)

A service mesh (Istio, Linkerd, or a standalone Envoy with its rate-limit service) can enforce the same kind of limit at the sidecar level instead of — or in addition to — the Ingress edge, with finer-grained routing rules than IP-based annotations allow. The mechanism differs by mesh, but the role is the same one this layer plays: something has to sit in front of your application and say no, because Kubernetes itself never will.

Three layers, side by side

Client-side (client-go)Control plane (APF)Edge (Ingress)
ProtectsThe client from itselfThe API serverYour application
Default5 QPS / 10 burst400 read + 200 write seatsNone — must be added
Enabled by default?YesYes (since 1.20)No
Rejection statusN/A — throttles before sendingHTTP 429 + Retry-AfterHTTP 503 (ingress-nginx default)

Which one do you actually need?

If kubectl or a controller feels slow against a large cluster, check the client's own QPS/Burst settings first — it may be throttling itself, not waiting on the server. If you're seeing real 429s from the API server itself, that's APF, and apiserver_flowcontrol_rejected_requests_total confirms it. If external users are hammering an HTTP endpoint your application exposes, none of the above apply at all — that's purely an Ingress/mesh configuration decision, and by default there is no limit until you add one.

Try it yourself

Kubernetes YAML Validator checks a manifest against real API rules before you apply it, and Kubernetes Resource Calculator covers the other Kubernetes "limits" — CPU and memory resource limits per container, a completely different concept from anything on this page. See Kubernetes Resource Requests vs Limits for that one specifically.

FAQ

Is API Priority and Fairness the same thing as client-go's QPS/Burst limiting?

No, and mixing them up is common enough to have its own tracked GitHub issue. QPS/Burst is the client deciding not to send a request yet; APF is the server deciding whether to accept one that already arrived. A client can throttle itself into feeling slow on a cluster whose API server has plenty of spare APF capacity, and vice versa.

Does Kubernetes rate-limit traffic between Pods?

No — there's no default east-west (pod-to-pod) rate limiting any more than there is north-south (Ingress) rate limiting. It requires the same answer as external traffic: a service mesh sidecar or application-level logic, not anything Kubernetes enforces on its own.

Is this the same as Kubernetes resource requests and limits?

No — different word, different concept, different layer entirely. Resource requests/limits (covered in Kubernetes Resource Requests vs Limits) govern how much CPU and memory a container is guaranteed and capped at. Rate limiting, as covered on this page, governs how many requests per second something is allowed to send or receive. Nothing about a generous resource limit protects an application from a traffic spike, and nothing about a strict rate limit affects how much CPU a container can use.

Related tools