## Introduction

Ingress routes external HTTP(S) traffic to Services inside your cluster, but the Ingress resource alone does not expose anything. A working request travels through multiple layers that each must be healthy. Most Kubernetes Ingress errors are not caused by a single YAML field; they are request-path failures across DNS, load balancers, controllers, Services, endpoints, and application Pods.

Troubleshoot by testing every hop in order, not by randomly editing manifests.

Client
 |
 DNS
 |
External IP / Load Balancer
 |
Ingress Controller Service
 |
Ingress Controller Pod
 |
Ingress Rule
 |
Application Service
 |
EndpointSlice
 |
Application Pod 
 What you will learn:

- How to verify each layer with concrete commands.

- How to diagnose Kubernetes Ingress 404/502/503/504 and TLS errors.

- How to separate the Ingress API from the controller implementation.

Primary concept: an Ingress is configuration consumed by an Ingress controller. Creating an Ingress resource does not install or configure a controller.

## Build a minimal working example

Save the following as example.yaml. It creates a namespace, a simple HTTP echo Deployment, a ClusterIP Service, and an Ingress with host-based routing at path "/" using pathType: Prefix. Adjust <namespace> and <host> .

apiVersion: v1
kind: Namespace
metadata:
 name: <namespace>
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: echo
 namespace: <namespace>
spec:
 replicas: 2
 selector:
 matchLabels:
 app: echo
 template:
 metadata:
 labels:
 app: echo
 spec:
 containers:
 - name: echo
 image: hashicorp/http-echo:0.2.3
 args: ["-text=ok"]
 ports:
 - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
 name: echo-svc
 namespace: <namespace>
spec:
 selector:
 app: echo
 ports:
 - name: http
 port: 80
 targetPort: 5678
 type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: echo
 namespace: <namespace>
spec:
 ingressClassName: nginx
 rules:
 - host: <host>
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: echo-svc
 port:
 number: 80 
 Apply and inspect:

kubectl apply -f example.yaml
kubectl get pods,svc,ingress -n <namespace>
kubectl describe ingress echo -n <namespace> 
 Healthy signs:

- Pods Ready=1/1 and STATUS=Running.

- Service has a stable ClusterIP and a port 80 -> 5678 mapping.

- Ingress shows an ADDRESS (external IP or hostname) after your controller provisions it.

## Start with the request path

Use this quick table to test each layer deliberately.

<div style="overflow-x:auto;margin:1.5rem 0;"><table style="width:100%;border-collapse:collapse;font-size:0.95rem;line-height:1.5;"><thead><tr><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Layer</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Typical symptom</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Diagnostic command</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Success looks like</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Likely fix</th></tr></thead><tbody><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">DNS</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Host does not resolve</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;dig +short &lt;host&gt;&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Returns IP or LB hostname</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Create/adjust DNS A/AAAA/CNAME</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">External LB</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Connection refused/timeout</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -v http://&lt;host&gt;/&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TCP connect succeeds</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Provision LB, open firewall/SG, wait for provisioning</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Controller Service</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Empty Ingress ADDRESS</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get svc -A&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Controller svc has EXTERNAL-IP or NodePort</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Configure Service type, fix cloud/LB setup</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Controller Pods</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">503/timeout</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get pods -A&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Controller Pods Running/Ready</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Install/repair controller</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">IngressClass</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Ingress ignored</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get ingressclass&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Matching class exists and is default or named in spec</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Set &lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;spec.ingressClassName&lt;/code&gt; correctly</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Ingress resource</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">404 default backend</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl describe ingress&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Accepted, rules listed, no errors</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix host/path, annotations, class</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Backend Service</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">502/503</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get svc &lt;svc&gt; -o yaml&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Port/selector correct</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Correct name/port/selector</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">EndpointSlice</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">503/504</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get endpointslice -l kubernetes.io/service-name=&lt;svc&gt; -n &lt;ns&gt;&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Addresses with Ready=true</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix selector, Pod readiness</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Application Pods</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">502/504</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl describe pod &lt;pod&gt;&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Ready, listening on 0.0.0.0</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix probes, bind address, container args</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TLS</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Browser warnings</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;openssl s_client -connect &lt;host&gt;:443 -servername &lt;host&gt;&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Certificate matches host, not expired</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix Secret, SANs, chain</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App response</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App 404</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -v -H "Host: &lt;host&gt;" http://&lt;ingress-ip&gt;/&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App headers/body as expected</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix app routes or rewrites</td></tr></tbody></table></div>

## Error 1: No Ingress controller is installed

An Ingress object is only configuration. Without a controller, nothing listens.

kubectl get pods -A | grep -E "ingress|traefik|haproxy|alb|gateway"
kubectl get deployments -A | grep -E "ingress|traefik|haproxy|alb|gateway"
kubectl get ingressclass 
 Look for ingress-nginx, Traefik, HAProxy, or your cloud vendor controller. Installation differs across bare metal, minikube/kind, managed Kubernetes, and VMs. Use your platform’s documented method.

## Error 2: IngressClass is missing or incorrect

The controller handling your Ingress is chosen by spec.ingressClassName or the cluster’s default IngressClass.

kubectl get ingressclass
kubectl describe ingressclass <class-name>
kubectl get ingress echo -n <namespace> -o yaml 
 Example spec:

spec:
 ingressClassName: nginx 
 Use the class name your controller advertises; names are implementation-specific.

## Error 3: Ingress has no external address

kubectl get ingress -A shows an empty ADDRESS when:

- No controller handles the resource.

- The controller Service has no EXTERNAL-IP ( ).

- Load balancer is still provisioning or not supported on your infra.

- Bare-metal LoadBalancer integration (e.g., MetalLB) is missing/misconfigured.

- The controller is exposed via NodePort instead.

 Check:

kubectl get svc -A
kubectl describe svc <controller-service> -n <controller-namespace>
kubectl get events -A --sort-by=.metadata.creationTimestamp 
 If EXTERNAL-IP is <pending> , solve the Service or infrastructure issue first.

## Error 4: DNS does not point to the Ingress endpoint

Validate DNS and host-based routing:

dig +short <host>
nslookup <host>
curl -v http://<host>/ 
 To bypass DNS while preserving the Host header:

curl -v --resolve <host>:80:<ingress-ip> http://<host>/
curl -vk --resolve <host>:443:<ingress-ip> https://<host>/ 
 --resolve proves the Ingress and backend work even if public DNS is wrong.

## Error 5: Default backend returns 404

A controller-generated 404 typically means host or path did not match any rule, or the wrong controller handled the request.

curl -v -H "Host: <host>" http://<ingress-ip>/
kubectl describe ingress <name> -n <namespace>
kubectl get ingress <name> -n <namespace> -o yaml 
 Differentiate:

- Ingress 404: headers often include controller-specific identifiers (e.g., nginx). Fix host/path, class, or annotations.

- Application 404: request reached your app; adjust app routing.

## Error 6: Path matching and rewrites

- pathType: Prefix matches /api and /api/v1 .

- pathType: Exact matches the path literally.

- ImplementationSpecific is controller-defined and not portable.

Standard example without rewrite:

paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: echo-svc
 port:
 number: 80 
 Ingress-nginx-specific rewrite example (controller-specific, not portable):

metadata:
 annotations:
 nginx.ingress.kubernetes.io/rewrite-target: / 
 Do not copy nginx annotations to Traefik, HAProxy, or cloud controllers.

## Error 7: Wrong Service name or port

The Ingress backend must reference the Service port, not the Pod’s containerPort.

backend:
 service:
 name: api-service
 port:
 number: 8080 
 Check mappings and names:

kubectl get svc <service-name> -n <namespace> -o yaml
kubectl describe svc <service-name> -n <namespace>
kubectl get deployment <deployment-name> -n <namespace> -o yaml 
 Service.port maps to Service.targetPort, which maps to the containerPort (or a named port) inside the Pod.

## Error 8: Service has no endpoints

A Service can exist with zero ready endpoints. Confirm:

kubectl get endpoints <service-name> -n <namespace>
kubectl get endpointslice -n <namespace> -l kubernetes.io/service-name=<service-name>
kubectl describe svc <service-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels 
 Common causes:

- Selector does not match Pod labels.

- Pods are not Ready (failing probes).

- Pods are terminating or in another namespace.

Fix selectors to match labels; Ingress cannot reference Services in other namespaces via the standard backend.

## Error 9: Pods running but not Ready

Running does not mean routable. Inspect probes and logs:

kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> 
 Example readiness probe:

readinessProbe:
 httpGet:
 path: /healthz
 port: 5678
 initialDelaySeconds: 2
 periodSeconds: 5 
 Ensure the app listens on 0.0.0.0, not 127.0.0.1.

## Error 10–12: 502, 503, 504

- 502 Bad Gateway: often wrong targetPort, backend refusing connections, HTTP vs HTTPS mismatch.

- 503 Service Unavailable: no ready endpoints, backend Service missing, or controller reload errors.

- 504 Gateway Timeout: slow app, dependency latency, NetworkPolicy blocking, or controller timeout settings.

Test the Service from inside the cluster to isolate Ingress vs backend:

kubectl run curl-test --rm -it --restart=Never \
 --image=curlimages/curl -n <namespace> -- \
 curl -v http://<service-name>:<port>/ 
 Success here but failure through Ingress points to controller, routing rules, or protocol/TLS configuration.

## TLS errors: Secrets and certificates

TLS requires a Secret of type kubernetes.io/tls in the same namespace as the Ingress. Hosts must match the certificate SANs.

spec:
 tls:
 - hosts:
 - app.example.com
 secretName: app-example-tls 
 Validate:

kubectl get secret app-example-tls -n <namespace>
kubectl describe ingress <name> -n <namespace>
openssl s_client -connect <host>:443 -servername <host> | openssl x509 -noout -subject -issuer -dates -ext subjectAltName 
 Check for expiration, chain completeness, and correct hostname.

## Works with port-forward but not through Ingress

kubectl port-forward deployment/<deployment> 8080:<container-port> proves the Pod listens and responds. kubectl port-forward service/<service> 8080:<service-port> proves the Service routes to endpoints. If both work but the public route fails, focus on controller, DNS, TLS, or external networking.

## Controller logs and generated configuration

Identify controller Pods generically:

kubectl get pods -A -l app.kubernetes.io/component=controller 
 Then inspect logs in the correct namespace. Example (ingress-nginx only):

kubectl -n ingress-nginx logs deploy/ingress-nginx-controller 
 Look for admission errors, config reloads, and upstream health messages.

## A disciplined troubleshooting workflow

- Confirm DNS: dig +short <host> .

- Confirm external reachability: curl -v http(s)://<host>/ .

- Controller installed and Ready: kubectl get deployments,pods -A .

- Correct IngressClass: kubectl get ingressclass and Ingress spec.ingressClassName .

- Inspect Ingress and events: kubectl describe ingress .

- Test host/path: curl -v -H "Host: <host>" http://<ingress-ip>/ .

- Verify Service name and port: kubectl describe svc .

- EndpointSlices Ready: kubectl get endpointslice .

- Test inside cluster: kubectl run ... curl http://<svc>:/ .

- Check controller logs.

- Check application logs and probes.

- Validate TLS Secret and certificate hostname.

- Review NetworkPolicies/firewalls.

- Re-test externally.

## Practical end-to-end incident

 Symptom (example output):

- curl -v https://app.example.com/ returns 503.

Evidence:

kubectl get ingress echo -n demo
# ADDRESS set; rules OK
kubectl get svc echo-svc -n demo
# Port 80 -> 5678
kubectl get endpointslice -n demo -l kubernetes.io/service-name=echo-svc
# No endpoints listed
kubectl get deploy echo -n demo -o yaml | grep labels -A2
# Deployment pods labeled app=echo-app (mismatch!) 
 Root cause: Service selector app=echo does not match Pod label app=echo-app , so there are zero ready endpoints.

Fix (align labels):

spec:
 selector:
 matchLabels:
 app: echo
 template:
 metadata:
 labels:
 app: echo 
 Validate:

kubectl apply -f deployment.yaml
kubectl get endpointslice -n demo -l kubernetes.io/service-name=echo-svc
# Endpoints now present
curl -v https://app.example.com/
# 200 OK 
 Prevention: alert on Services with no ready endpoints; lint selectors in CI; test routes after deploy.

## Quick decision table

<div style="overflow-x:auto;margin:1.5rem 0;"><table style="width:100%;border-collapse:collapse;font-size:0.95rem;line-height:1.5;"><thead><tr><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Symptom</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Most likely layer</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">First command</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Common cause</th><th style="border:1px solid rgba(15,23,42,.18);padding:.75rem;text-align:left;background:#f8fafc;font-weight:700;">Next action</th></tr></thead><tbody><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">DNS failure</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">DNS</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;dig +short &lt;host&gt;&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Missing/old record</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix DNS A/AAAA/CNAME</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Connection refused</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">LB/Service</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -v http://&lt;host&gt;/&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">LB not ready, firewall</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Verify LB, open ports</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Timeout</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">LB/Controller/App</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -v&lt;/code&gt; + controller logs</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">NetworkPolicy, slow app</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Test Service in-cluster</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Controller 404</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Ingress rule</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl describe ingress&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Host/path mismatch</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Correct rule/pathType</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Application 404</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -v&lt;/code&gt; headers</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App route missing</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix app routes</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">502</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Backend</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">In-cluster curl to Service</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Wrong targetPort/protocol</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Align ports/protocols</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">503</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Endpoints</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get endpointslice&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">No ready endpoints</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix selector/probes</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">504</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App/deps</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">App logs/metrics</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Slow backend</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Optimize or adjust timeouts</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TLS warning</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TLS</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;openssl s_client ...&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Name/expiry/chain</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Fix Secret/cert</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Wrong certificate</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TLS/SNI</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;openssl s_client -servername&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Host mismatch</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Provide per-host cert</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Redirect loop</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Proxy/app</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl -IL&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Proto/header issues</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Trust X-Forwarded-Proto, fix redirects</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Empty external address</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Infra</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;kubectl get svc -A&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Pending LB</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Configure LB/NodePort</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Works by IP only</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Ingress rule</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&lt;code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded"&gt;curl --resolve&lt;/code&gt;</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Host mismatch</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Correct host rules</td></tr></tbody></table></div>

## Ingress versus Gateway API

Ingress remains widely used. Gateway API offers richer routing and role separation, but still requires a compatible controller and the same disciplined troubleshooting across DNS, Services, EndpointSlices, Pods, and TLS. Migrating does not eliminate these fundamentals.

## Final troubleshooting checklist

- Controller installed and Pods Ready

- Controller Service reachable (EXTERNAL-IP/NodePort)

- Correct spec.ingressClassName

- DNS resolves to the Ingress endpoint

- Host and path rules correct, proper pathType

- Backend Service name and port correct

- Service selector matches Pod labels

- EndpointSlices contain Ready addresses

- Application listens on 0.0.0.0

- TLS Secret exists in same namespace; cert matches hostname and is valid

- NetworkPolicy/firewalls allow controller <-> Pods and external <-> controller

- Controller and application logs inspected

## Conclusion

Most Kubernetes Ingress errors are not YAML typos; they are broken links in the end-to-end request path. Start at DNS and the external endpoint, move through the controller and Ingress rules, and finish at the Service, EndpointSlices, and Pods. Distinguish controller 404s from application 404s, and validate TLS without guessing. Reliable fixes come from isolating the failing layer, proving each hop with commands, and only then changing configuration.