One request, five places it can fail
When a user reports "the site is down", the controller has already decided who failed. Each status code points at a different layer, and the controller's log has one line that says why.
What you need to know already: the access log fields and [upstream] (16.19), the 404/502/503 incidents of 16.24-16.25, EndpointSlices and readiness (16.1, 15.14), NetworkPolicy (16.29), curl -v, -w, --resolve (9.27).
client ─> node port / LB ─> controller ─(rule?)─> endpoints? ─> connect ─> answer in time?
unreachable: 404 503 502 504
curl (7)/(28)
404: no rule matched
The controller has no server/location for this host + path, so its default server answers with its built-in 404 page. The access log names the upstream [upstream-default-backend]:
10.244.1.1 - - [07/Oct/2026:10:00:01 +0000] "GET /blog HTTP/1.1" 404 146 "-" "curl/8.14.1" 82 0.001 [upstream-default-backend] [] 127.0.0.1:8181 146 0.001 404 4f1c...
Check in this order: the Host header (-H 'Host: ...' or --resolve), the Ingress class (an Ingress the controller ignores is the same as no Ingress: ADDRESS empty), pathType (Exact vs Prefix, 16.19), and a regex annotation on another Ingress of the same host that changed how all its paths match.
A 404 can also come from the app: the upstream field then names a real Service ([shop-web-80]) and the last status column is the pod's own 404.
Teams often replace the plain 404 page with a branded one: --default-backend-service=ns/name in the controller args sends every unmatched request to that Service. The controller still logs [upstream-default-backend], so the diagnosis is the same.
503: matched, but nowhere to send it
W1007 10:00:05.101221 7 controller.go:1128] Service "shop/api" does not have any active Endpoint.
No ready pods behind the Service (selector, readiness probe, a rollout that never became ready), the Service does not exist, or the Ingress names a port the Service does not have. kubectl get endpointslice -l kubernetes.io/service-name=api answers it in one command.
502: reached a pod, the connection broke
2026/10/07 10:00:07 [error] 38#38: *1004 connect() failed (111: Connection refused) while connecting to upstream, client: 10.244.1.1, server: shop.lab, request: "GET /api/orders HTTP/1.1", upstream: "http://10.244.2.15:8080/api/orders", host: "shop.lab"
The pod IP is right but nothing listens on that port: the Service's targetPort is wrong, or the app crashed between the readiness check and now. Two other 502 lines you will see:
recv() failed (104: Connection reset by peer) while reading response header from upstream
SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking to upstream
The first: the pod accepted and then dropped the connection (an app that crashes per request, or a mesh sidecar in STRICT mTLS refusing plaintext - later in this chapter). The second: backend-protocol: HTTPS on an Ingress whose pods speak plain HTTP.
504: reached it, gave up waiting
2026/10/07 10:00:20 [error] 38#38: *1009 upstream timed out (110: Operation timed out) while reading response header from upstream, client: 10.244.1.1, server: shop.lab, request: "GET /reports/export HTTP/1.1", upstream: "http://10.244.1.22:8080/reports/export", host: "shop.lab"
"while reading response header" = connected fine, the pod was too slow (longer than proxy-read-timeout). "while connecting to upstream" with a timeout = the connection never opened: a NetworkPolicy dropping the controller's packets (it is a pod like any other, 16.29) or a dead node.
Two more classics
The redirect loop. A cloud load balancer terminates TLS and sends plain HTTP to the controller; the controller sees http, the host has tls, so it answers 308 to https; the browser follows, the LB sends http again... Fix: use-forwarded-headers: "true" (trust X-Forwarded-Proto from the LB) or ssl-redirect: "false" on that Ingress.
"It works with curl from my laptop but not from the pod." Inside the cluster, call the Service directly (http://web.shop) - going out to the node port and back through the controller adds every layer above for nothing.
first command kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=20
then, by code 404 -> rules/class 503 -> endpoints 502 -> targetPort/backend 504 -> timeout/policy
What you can now do:
- name the layer that failed from the status code alone
- find the controller's log line for each of 404, 502, 503 and 504
- tell a slow pod (504 reading header) from a blocked connection (504 connecting)