Introduction

Kubernetes Ingress routes external HTTP/S traffic to Services within the cluster. When a client requests a path mapped by the Ingress, the Ingress controller forwards the request to the Service, which load balances across its ready endpoint Pods. This chain—Ingress to Service to Pod—requires each link to function correctly.

When an Ingress returns HTTP 503 Service Unavailable, the Ingress controller itself is typically healthy; the problem lies downstream. The Service has no ready endpoints to receive traffic. This can happen even when Pods are running and the Service exists. The root cause is usually a mismatch between Service selectors and Pod labels, Pods failing readiness probes, or namespace misconfiguration.

Understanding how Services select Pods through label selectors, how readiness gates endpoint inclusion, and how Ingress references Services is essential for debugging 503 errors in Kubernetes Ingress deployments.

Symptoms

When Kubernetes Ingress returns 503 due to missing endpoints, you will observe these symptoms:

  • HTTP requests to Ingress paths return 503 Service Unavailable
  • Ingress controller logs show "no healthy upstream" or similar
  • kubectl get endpoints shows empty addresses for the Service
  • Pods are running but not listed as Service endpoints
  • Recent rollout, label change, or probe modification preceded the outage
  • Some paths work while others fail (different Services)
  • Nginx/HAProxy Ingress controller logs show "no live upstreams"

Common error responses:

``` HTTP/1.1 503 Service Unavailable Content-Type: text/html

<html> <head><title>503 Service Temporarily Unavailable</title></head> <body> <center><h1>503 Service Temporarily Unavailable</h1></center> <hr><center>nginx</center> </body> </html> ```

Nginx Ingress controller logs:

bash
2026/01/15 10:30:45 [error] 1234#1234: *5678 upstream temporarily disabled while connecting to upstream
2026/01/15 10:30:45 [warn] 1234#1234: *5678 no live upstreams while connecting to upstream

kubectl endpoints showing empty:

bash
kubectl get endpoints my-service
NAME        ENDPOINTS
my-service  <none>

Common Causes

Several factors cause Kubernetes Services to have no endpoints:

  1. 1.Service selector doesn't match Pod labels: The Service's label selector doesn't match any Pod labels. This commonly happens after Deployment label changes without updating the Service.
  2. 2.Pods not in Ready state: Pods may be running but not Ready due to failing readiness probes. Non-Ready Pods are excluded from Service endpoints.
  3. 3.Namespace mismatch: Ingress references a Service in the wrong namespace, or Pods and Service are in different namespaces.
  4. 4.Service name typo in Ingress: The Ingress backend references a Service name that doesn't exist or has a typo.
  5. 5.Port mismatch: Ingress backend port doesn't match Service port, causing connection failures.
  6. 6.Pod selector empty: Service was created without selector (valid for external Services), but no Endpoints were manually created.
  7. 7.All Pods terminated: Deployment scaled to 0 replicas or all Pods are in failed/completed state.
  8. 8.Readiness probe too strict: Readiness probe conditions are too aggressive, preventing any Pod from becoming Ready.

Step-by-Step Fix

Follow these steps to diagnose and resolve Ingress 503 endpoint issues:

Step 1: Check Service endpoints

Verify the Service has endpoints:

```bash # Check endpoints for the Service kubectl get endpoints my-service -n my-namespace

# Detailed endpoint information kubectl describe endpoints my-service -n my-namespace

# Check via endpointslices (newer Kubernetes) kubectl get endpointslices -l kubernetes.io/service-name=my-service -n my-namespace

# Expected output with endpoints: NAME ENDPOINTS AGE my-service 10.244.0.5:8080,10.244.0.6:8080 5m

# Empty endpoints indicates the problem: NAME ENDPOINTS AGE my-service <none> 5m ```

Step 2: Compare Service selector with Pod labels

Check for selector/label mismatch:

```bash # Get Service selector kubectl get service my-service -n my-namespace -o jsonpath='{.spec.selector}' # Output: {"app":"myapp","environment":"production"}

# Get Pod labels kubectl get pods -n my-namespace --show-labels

# Output: NAME READY STATUS LABELS myapp-abc1 1/1 Running app=myapp,environment=staging,pod-template-hash=abc1 myapp-abc2 1/1 Running app=myapp,environment=staging,pod-template-hash=abc2

# Mismatch: Service wants environment=production, Pods have environment=staging ```

Step 3: Check Pod readiness status

Verify Pods are Ready:

```bash # List Pods with readiness status kubectl get pods -n my-namespace -o wide

# Pods not Ready (0/1 or similar): NAME READY STATUS RESTARTS myapp-abc1 0/1 Running 0

# Describe Pod for probe status kubectl describe pod myapp-abc1 -n my-namespace

# Look for Readiness probe events: Events: Warning Unhealthy 30s kubelet Readiness probe failed: HTTP probe failed with statuscode: 500 ```

Step 4: Check Deployment and Service alignment

Verify Deployment labels match Service selector:

```bash # Get Deployment labels kubectl get deployment myapp -n my-namespace -o jsonpath='{.spec.template.metadata.labels}'

# Get Service selector kubectl get service my-service -n my-namespace -o jsonpath='{.spec.selector}'

# They must match for Pods to be selected ```

Step 5: Fix selector/label mismatch

Update Service selector or Pod labels:

```bash # Option 1: Update Service selector to match Pod labels kubectl patch service my-service -n my-namespace -p '{"spec":{"selector":{"app":"myapp","environment":"staging"}}}'

# Option 2: Update Deployment labels (requires rollout) kubectl set env deployment/myapp ENVIRONMENT=production -n my-namespace # Or edit Deployment kubectl edit deployment myapp -n my-namespace # Update spec.template.metadata.labels

# After Deployment update, verify new Pods have correct labels kubectl get pods -n my-namespace --show-labels ```

Step 6: Fix readiness probe issues

If Pods aren't becoming Ready:

```bash # Check probe configuration kubectl get deployment myapp -n my-namespace -o yaml | grep -A 20 readinessProbe

# Example failing probe: readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 10

# Test the health endpoint manually kubectl exec -it myapp-abc1 -n my-namespace -- curl -f http://localhost:8080/health

# If endpoint returns errors, fix application or adjust probe: kubectl patch deployment myapp -n my-namespace --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/path", "value":"/api/health"}]' ```

Step 7: Verify Ingress references correct Service

Check Ingress backend configuration:

```bash # Get Ingress configuration kubectl get ingress my-ingress -n my-namespace -o yaml

# Check backend specification: spec: rules: - host: example.com http: paths: - path: /api backend: service: name: my-service # Must exist port: number: 80 # Must match Service port

# Verify Service exists and port matches kubectl get service my-service -n my-namespace ```

Step 8: Check namespace consistency

Verify all resources are in expected namespaces:

```bash # Check Ingress namespace kubectl get ingress --all-namespaces | grep my-ingress

# Check Service namespace kubectl get service my-service --all-namespaces

# Check Pod namespace kubectl get pods -l app=myapp --all-namespaces

# Ingress can only reference Services in same namespace # For cross-namespace, use ExternalName Service ```

Step 9: Verify endpoint recovery

After fixes, confirm endpoints are populated:

```bash # Check endpoints are now populated kubectl get endpoints my-service -n my-namespace

# Expected output: NAME ENDPOINTS AGE my-service 10.244.0.5:8080,10.244.0.6:8080 10m

# Test Ingress curl -H "Host: example.com" http://ingress-ip/api

# Should return 200 instead of 503 ```

Verification

After fixing the issue, verify the complete chain:

```bash # Verify Pods are Ready kubectl get pods -n my-namespace # All should show 1/1 or n/n Ready

# Verify Service has endpoints kubectl get endpoints my-service -n my-namespace # Should show IP:port addresses

# Verify Ingress routes correctly curl -v http://example.com/api # Should return 200 OK

# Check Ingress controller logs are clean kubectl logs -n ingress-nginx deployment/ingress-nginx-controller | grep -i error ```

Complete verification script:

```bash #!/bin/bash NS="my-namespace" SVC="my-service"

echo "Checking Service endpoints..." EP=$(kubectl get endpoints $SVC -n $NS -o jsonpath='{.subsets[0].addresses}') if [ -z "$EP" ]; then echo "FAIL: No endpoints for Service $SVC" exit 1 else echo "OK: Service has endpoints" fi

echo "Testing Ingress..." HTTP=$(curl -s -o /dev/null -w "%{http_code}" http://example.com/api) if [ "$HTTP" == "200" ]; then echo "OK: Ingress returns 200" else echo "FAIL: Ingress returns $HTTP" exit 1 fi ```

Prevention

To prevent Kubernetes Ingress 503 endpoint issues:

  1. 1.Keep Deployment labels and Service selectors synchronized: Treat them as a unit.

```yaml # Deployment spec: template: metadata: labels: app: myapp version: v1

# Service (selector must match) spec: selector: app: myapp version: v1 ```

  1. 1.Monitor endpoint counts: Set up alerts for Services with zero endpoints.
yaml
# Prometheus alert
- alert: ServiceNoEndpoints
  expr: kube_endpoint_address_available == 0
  for: 5m
  labels:
    severity: critical
  1. 1.Use meaningful readiness probes: Probes should verify actual request-handling capability.
yaml
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3
  1. 1.Validate before applying changes: Use dry-run to check impact.
bash
kubectl apply -f service.yaml --dry-run=client
  1. 1.Use GitOps for coordination: Manage Deployment and Service together in same commit.
  2. 2.Document Service dependencies: Document which Services are critical for which Ingress paths.
markdown
## Service Dependencies
- /api -> api-service (requires 2+ endpoints)
- /web -> web-service (requires 1+ endpoints)
  1. 1.Implement health checks for Services: Periodically verify Service endpoints exist.
bash
# CronJob to check endpoints
kubectl get endpoints -A -o json | jq '.items[] | select(.subsets | length == 0) | .metadata.name'

Related Articles

  • [Fix Envoy Rate Limit Configuration with envoyproxy/ratelimit](envoyproxy-ratelimit-configuration-guide)
  • [Fix Fix Argocd App Not Syncing Issue in Kubernetes](fix-argocd-app-not-syncing)
  • [Fix Fix Argocd Sync Conflict Issue in Kubernetes](fix-argocd-sync-conflict)
  • [Fix ArgoCD Sync Timeout](fix-argocd-sync-timeout)
  • [How to Fix Cilium Identity Exhaustion and Endpoint Allocation Failed](fix-cilium-identity-exhaustion)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Kubernetes Ingress Returning 503 Because the Backend Service Has No Endpoints", "description": "Resolve Kubernetes Ingress 503 Service Unavailable responses by checking Service selectors, Pod readiness, and whether the backend actually exposes ready endpoints.", "url": "https://www.fixwikihub.com/kubernetes-ingress-503-service-backend-no-endpoints", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T21:09:23.304Z", "dateModified": "2026-01-22T21:09:23.304Z" } </script>