Kubernetes Error Guide: '503 Service Temporarily Unavailable' From ingress-nginx
Fix ingress-nginx 503 Service Temporarily Unavailable: no healthy upstream endpoints, service selector mismatch, and pods that never reach Ready.
- #kubernetes-helm
- #troubleshooting
- #errors
- #ingress
Exact Error Message
A request that reaches ingress-nginx but has no backend to forward to returns an HTTP 503 with the controller’s default body:
HTTP/1.1 503 Service Temporarily Unavailable
Date: Sun, 28 Jun 2026 14:02:11 GMT
Content-Type: text/html
Content-Length: 197
Connection: keep-alive
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx</center>
</body>
</html>
The corresponding controller log line confirms the upstream had nothing to route to:
2026/06/28 14:02:11 [error] 412#412: *9381 no live upstreams while connecting to upstream, client: 10.0.3.7, server: app.example.com, request: "GET /api HTTP/1.1", upstream: "http://upstream-default-backend/api"
What the Error Means
ingress-nginx terminates the client connection and then proxies the request to an upstream — the set of pod IPs behind your Service. A 503 from nginx itself (note the nginx footer, not your application) means the controller has zero healthy endpoints in that upstream. The Ingress object resolved correctly and TLS terminated, but when nginx looked up the backend Service it found an empty or unreachable endpoint list, so there was no pod to forward the request to.
This is distinct from a 502 (backend returned a malformed response) or a 504 (backend timed out). A 503 means nginx never got a connection because the upstream pool is empty.
Common Causes
- Service selector does not match pod labels — the Service’s
selectortargets labels no running pod carries, so its Endpoints object is empty. - Pods are not Ready — readiness probes are failing, so endpoints are removed from the EndpointSlice even though pods are Running.
- Wrong
servicePortin the Ingress — the Ingress references a port the Service does not expose. - All backend pods crashed or scaled to zero — a Deployment at
replicas: 0or in CrashLoopBackOff leaves no targets. - Service in a different namespace — the Ingress
backend.service.nameonly resolves Services in the same namespace as the Ingress. - NetworkPolicy blocks the controller — a policy denies traffic from the ingress-nginx namespace to the pods, so health checks and proxying fail.
How to Reproduce the Error
Create a Service whose selector matches nothing, point an Ingress at it, and curl the host:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector: { app: web-typo } # no pod has this label
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations: { kubernetes.io/ingress.class: nginx }
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port: { number: 80 }
kubectl apply -f web.yaml
curl -H 'Host: app.example.com' http://<INGRESS_IP>/
# -> 503 Service Temporarily Unavailable
The Endpoints object for web will be empty, and every request returns 503.
Diagnostic Commands
# The single most important check: does the Service have endpoints?
kubectl get endpointslices -l kubernetes.io/service-name=<SERVICE>
kubectl get endpoints <SERVICE> -o wide
# Compare the Service selector to actual pod labels
kubectl get service <SERVICE> -o jsonpath='{.spec.selector}'; echo
kubectl get pods --show-labels
# Are the backing pods Ready (not just Running)?
kubectl get pods -l <SELECTOR> -o wide
# Read the controller log for "no live upstreams" / "no healthy upstream"
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50
# Confirm the Ingress port matches a Service port
kubectl get ingress <NAME> -o jsonpath='{.spec.rules[*].http.paths[*].backend.service}'; echo
kubectl get service <SERVICE> -o jsonpath='{.spec.ports}'; echo
The kubectl get endpoints output is decisive: <none> in the ENDPOINTS column means nginx has no upstream, which is the direct cause of the 503.
Step-by-Step Resolution
1. Confirm the upstream is empty. Run kubectl get endpoints <SERVICE>. If ENDPOINTS shows <none>, the rest of the fix is about populating it.
2. Reconcile selector and labels. Print the Service selector and the pod labels side by side. A single typo (app: web vs app: web-typo) breaks the match:
kubectl get service web -o jsonpath='{.spec.selector}'; echo
kubectl get pods -l app=web
Edit either the Service selector or the pod template labels so they agree, then re-check endpoints.
3. Make pods Ready. If the selector matches but endpoints are still empty, the pods are failing readiness. Describe a pod and look at the Ready condition and probe events:
kubectl describe pod <POD> | grep -A3 'Readiness\|Conditions'
Fix the readiness probe (wrong path, port, or too aggressive initialDelaySeconds) so the pod passes and is added to the EndpointSlice.
4. Verify the port chain. The Ingress port must equal a Service port, and the Service targetPort must equal the container’s listening port. A mismatch anywhere yields an empty or dead upstream.
5. Check replica count and pod health. Ensure the Deployment is not scaled to zero and pods are not in CrashLoopBackOff:
kubectl get deploy <NAME>
6. Rule out NetworkPolicy and namespace. Confirm the Ingress and Service share a namespace, and that no NetworkPolicy denies traffic from the ingress-nginx namespace to the backend pods.
Prevention and Best Practices
- Always define readiness probes so endpoints only appear when a pod can truly serve traffic, and alert when a Service’s endpoint count drops to zero.
- Treat an empty Endpoints object as a first-class signal — a Prometheus alert on
kube_endpoint_address_available == 0catches 503s before users do. - Keep Service selectors and Deployment pod labels in a single templated source (Helm/Kustomize) so they cannot drift apart.
- Configure a
PodDisruptionBudgetso rolling updates never drain every endpoint at once. - Validate the full Ingress → Service → targetPort → containerPort chain in CI before merging manifest changes. See more in our Kubernetes & Helm guides.
Related Errors
- No endpoints available for service — the underlying empty-upstream condition.
- default backend - 404 — when the Ingress host/path matches nothing at all.
- upstream connect error or disconnect/reset before headers — backend reachable but rejecting connections.
Frequently Asked Questions
How do I know the 503 is from nginx and not my app? Look at the response body footer. ingress-nginx’s default 503 page ends with <center>nginx</center>. If your application emits its own 503 (for example a maintenance page), the body and headers differ, and the controller log will show a real upstream IP instead of “no live upstreams”.
The Service has endpoints but I still get 503 intermittently. Intermittent 503s usually mean some endpoints are unhealthy — pods flapping in and out of Ready, or a rolling update draining endpoints faster than new ones arrive. Check kubectl get endpoints repeatedly and add a PodDisruptionBudget.
Why does it say “Service Temporarily Unavailable” — will it self-heal? It self-heals only when a healthy endpoint appears: a pod passes its readiness probe, the Deployment scales up, or the selector is corrected. nginx re-resolves the upstream continuously, so the moment an endpoint is added the 503 stops.
My pods are Running but endpoints are still empty. Running is not Ready. The EndpointSlice controller only adds pod IPs once the pod’s Ready condition is true. A failing readiness probe keeps a Running pod out of the upstream, producing exactly this 503.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.