Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Same app, two results
The same app returns 200 inside the cluster and 503 from the internet. Notice the Server header on the failing response. It says nginx, so the request did get in and reached the Ingress controller. The failure happens somewhere after that, not at the front door.
# from a pod inside the cluster
$ curl -i http://shop-svc.default.svc.cluster.local
HTTP/1.1 200 OK
# from a laptop on the internet
$ curl -i https://shop.example.com
HTTP/1.1 503 Service Temporarily Unavailable
Server: nginxInternal test covers only Service and Pod
A public request to shop.example.com crosses five hops. The client resolves DNS, which returns the load balancer address. The load balancer forwards to the Ingress, the Ingress applies its host rule to the Service, and the Service sends port 80 to the Pod's 8080. Our in-cluster curl started at the Service, so the outer hops were never tested.
The cause: Ingress asks for a port the Service lacks
The Service listens on port 80 and forwards to targetPort 8080 on the Pods. But the Ingress backend names port 8080, and that's the Service's port, not the container's. Ingress talks to the Service, so it should say 80. That's the classic port versus targetPort mix-up.
# Service
metadata:
name: shop-svc
spec:
selector: {app: shop}
ports:
- port: 80
targetPort: 8080
---
# Ingress backend
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-svc
port:
number: 8080 # Service exposes 80Outer hops: DNS and load balancer succeed
Now we follow the public request from the outside. The client asks DNS for shop.example.com, and DNS answers with the load balancer's IP. The HTTPS request reaches the Cloud LB, which forwards it to the Ingress controller, and health checks pass. So the outer hops are fine. If they broke, you'd see no resolution or a timeout, not an HTTP error.
Inner hop: the Ingress rule breaks
The request reaches the Ingress, and the host rule for shop.example.com matches. Then the controller looks up shop-svc on port 8080. But the Service only exposes port 80, so there's no such port. The Ingress gives up and returns the 503 itself. The Pod is healthy, yet it never sees the request.
If the public URL instead returned 404 from nginx, which layer would you check first?
If the public URL instead returned 404 from nginx, which layer would you check first?
- The DNS record for the host
- The Ingress host, path, or class
- The Pod's container port
If the public URL instead returned 404 from nginx, which layer would you check first?
The Ingress host, path, or class. A 404 from nginx means the request already reached the Ingress controller, so DNS and the load balancer worked. The controller found no matching rule, often due to a wrong host, path, or ingress class. A Pod port problem would surface later, usually as a 502.
- The DNS record for the host
- The Ingress host, path, or class
- The Pod's container port
Fix the backend port, then verify outside-in
The fix is one line: the Ingress backend port becomes 80, the Service port. Now kubectl describe ingress lists real pod endpoints on 8080, and the public curl returns 200 OK. One tip: reference the port by name instead of number, so the two can't drift apart again.
backend:
service:
name: shop-svc
port:
number: 80
$ kubectl describe ingress shop # backend shows pod IP:8080
$ curl -i https://shop.example.com
HTTP/1.1 200 OKThe symptom points to the layer
Each symptom points to a hop. A host that won't resolve is DNS. A timeout means the load balancer. A 404 usually means the Ingress didn't match your host or class. A 503 means no backend, which was our case. A 502 means the Pod refused. Codes vary by controller.
Test outside-in, one hop at a time
Start at the outside and move in. Run dig to confirm the hostname returns the load balancer address. Then curl that IP with a Host header, which rules DNS out. Finally, check that the Ingress backend port matches the Service port, and that endpoints exist.
- dig host: returns the LB address?
- curl LB IP with Host header
- Ingress backend matches Service port?
Every hop is separate config
A public request crosses five hops: DNS, load balancer, Ingress, Service, and Pod. Each has its own config, and an internal curl only tests the last two. So read the error's source and status code, then test outside-in, one hop at a time.



