The “default backend - 404” page is the response an ingress controller (typically ingress-nginx) returns when it has no rule that matches the incoming request’s host and path. The fix is almost always one of: the Ingress resource references a Service that does not exist, the host in the Ingress does not match the Host header the client sent, or the Ingress has been created in a namespace the controller is not watching. The page itself is harmless — it is the controller’s 404 — but the underlying configuration error is the real problem.
The reason this is a top search is that the error looks generic. It looks like a routing problem, but it is almost always a configuration problem, and the configuration problem is in a YAML file the developer wrote three months ago and has not looked at since.
This post is the diagnostic sequence I run when a “default backend - 404” shows up. It covers the five real causes, the order to check them, and the one that catches people who already know the answer.
Table of contents
- What the page actually is
- Cause 1: the Service does not exist
- Cause 2: the host header does not match
- Cause 3: the namespace is not watched
- Cause 4: the path is wrong
- Cause 5: the Ingress class is wrong
- The diagnostic command that catches all five
- FAQ
What the page actually is
ingress-nginx, by default, deploys a “default backend” Pod whose only job is to return 404 for any request that does not match an Ingress rule. The Pod is a small Nginx instance with a static 404 page; it is not your application.
When a request arrives at the ingress controller:
- The controller looks for an Ingress resource that matches the
Hostheader and path. - If a match is found, the request is proxied to the backend Service.
- If no match is found, the request is routed to the default backend Pod.
- The default backend returns
404 Not Foundwith the bodydefault backend - 404.
The “default backend - 404” page is the visible signal of step 4. The cause is somewhere in steps 1-3 — the controller is not finding a match, even though the YAML on disk says it should.
Cause 1: the Service does not exist
The most common cause. The Ingress references a Service that has not been created, has been deleted, or is in a different namespace.
The diagnostic:
kubectl get ingress -A
kubectl get svc -A
Compare the BACKENDS column in the ingress output against the actual services. A backend that shows as 404 in the ingress’s status is a Service that the controller could not resolve.
The fix depends on the gap:
- The Service does not exist at all: create it. A Deployment without a Service is a Pod that the ingress cannot reach.
- The Service is in a different namespace: either move it, or update the Ingress’s
backend.service.nameto use the FQDN (my-svc.my-namespace.svc.cluster.local). - The Service was just created: wait a few seconds. ingress-nginx polls the API; it is not instant.
A more common variant: the Service exists, but the Pods behind it are not ready. The Service is in the list, but kubectl get pods shows 0/1 Running or CrashLoopBackOff. The ingress correctly routes the request, the Service correctly forwards it, and the Pod correctly fails. The 404 is the wrong error code (it should be a 503), but the root cause is the Pod, not the Ingress.
Cause 2: the host header does not match
The second most common cause. The Ingress has a host: rule, and the client is not sending that host. The most common reason: the client is using an IP address instead of a hostname, or the local /etc/hosts / DNS does not have the right entry.
The diagnostic:
# See what the Ingress expects
kubectl get ingress my-ingress -o jsonpath='{.spec.rules[*].host}'
# See what the client is sending
curl -v https://my-app.example.com 2>&1 | grep -i '> Host:'
If the two do not match, the controller has no rule to apply, and the request goes to the default backend.
The fix is on the client side: configure DNS, update /etc/hosts, or pass the right Host header in the test (curl -H 'Host: my-app.example.com'). The Ingress is correct; the request is wrong.
A more subtle variant: TLS is configured for one host, but the client is hitting a different host on the same IP. The TLS handshake fails before the HTTP request even gets to the controller. The browser shows a certificate warning; curl shows a TLS error. The fix is the same — the client is hitting the wrong host.
Cause 3: the namespace is not watched
ingress-nginx watches all namespaces by default, but a misconfigured cluster-admin may have set --watch-namespace=foo on the controller, in which case Ingresses outside that namespace are invisible.
The diagnostic:
kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx \
-o jsonpath='{.items[*].spec.containers[*].args}' | tr ' ' '\n' | grep watch
If --watch-namespace=foo is set, every Ingress not in foo is ignored. The fix is either to move the Ingress into the watched namespace, or to set the controller to watch all namespaces (the default, and usually the right answer).
A subtle variant: the Ingress is in the right namespace, but the Service is in a different namespace, and the Ingress’s backend.service.name is the short name. ingress-nginx will resolve the short name in the Ingress’s own namespace, miss, and 404. The fix is the FQDN as in Cause 1.
Cause 4: the path is wrong
The Ingress has a path rule that does not match the path the client is sending. Common cases:
- The Ingress has
path: /apiand the client is hitting/api/v1/users. The match is exact, not prefix, so the request 404s. - The Ingress has
path: /api/(with a trailing slash) and the client is hitting/api. The match is prefix, but the trailing slash changes the behavior in subtle ways depending on the controller.
The diagnostic:
kubectl get ingress my-ingress -o yaml | grep -A2 'path:'
Then curl the actual path the client is sending.
The fix is usually to switch the path type from Exact to Prefix (or ImplementationSpecific, which is the default and usually does what you want). The Kubernetes docs on path types are a useful reference for the differences.
Cause 5: the Ingress class is wrong
A cluster with multiple ingress controllers (ingress-nginx, Traefik, HAProxy, an AWS ALB controller) can have an Ingress that is being ignored by every controller in the cluster. The Ingress has no ingressClassName field, the cluster has a default IngressClass, but the default is the wrong one.
The diagnostic:
kubectl get ingressclass
kubectl get ingress my-ingress -o jsonpath='{.spec.ingressClassName}'
If the Ingress has no class and the cluster default is not the controller you are hitting, the Ingress is orphaned. The fix is to set ingressClassName: nginx (or whichever controller you want).
The diagnostic command that catches all five
When the “default backend - 404” is unexplained, the one-liner that catches most of the above is:
kubectl describe ingress my-ingress
The output shows:
- The Ingress class.
- The default backend.
- Each rule, the path type, the host, and the backend Service.
- The events at the bottom, which include “Service not found” warnings and other reconciliation errors.
kubectl describe is also the place to confirm the controller actually picked up the Ingress. If the Ingress does not appear in the controller’s logs (kubectl logs -n ingress-nginx deploy/ingress-nginx-controller), the controller is not watching the namespace, the class is wrong, or the Ingress is invalid.
For a real production cluster, the same diagnostic against a managed Kubernetes (EKS, GKE, AKS) is the same shape, but the ingress controller is usually the cloud’s load balancer, and the YAML uses annotations instead of spec fields. The principle is the same: the controller is the source of truth, and kubectl describe (or the cloud equivalent) is the first place to look. For a hosted alternative where the routing layer is not your problem, the RunxBuild deploy path exposes the same routing via a managed service, with the YAML replaced by a one-line config and the default-backend 404 problem replaced by a clear error in the dashboard.
How this fits the rest of the stack
The default-backend 404 is also a hidden cost signal — the routing layer is sending traffic somewhere it should not be sending traffic, and every request that hits the default backend is a request that is not hitting the real service. The team should know what that wasted traffic costs in bandwidth and in customer experience before the 404 becomes the customer’s first impression. The RunxBuild hosting calculator is the quick way to model that — pick the bandwidth, the traffic volume, and the service size, and the calculator shows what the wasted traffic costs and what the right-sized deploy would cost.
Useful related references:
FAQ
What does “default backend - 404” mean?
It is the response from an ingress controller’s default backend Pod when the controller cannot find an Ingress rule that matches the request’s host and path. The page is harmless; the underlying configuration error is the real problem.
How do I find which Ingress is supposed to handle a request?
Run kubectl get ingress -A to list all Ingresses, and kubectl get ingress -A -o wide to see the hosts and addresses. The Ingress that should match is the one whose host field matches the request’s Host header.
Why does the default backend return 404 instead of 503?
Because, from the controller’s perspective, the request is correctly routed — to the default backend. The default backend’s job is to return 404. A 503 would be more accurate (the service is down), but the controller does not have the visibility to know that.
Can I customize the default backend - 404 page?
Yes. Most controllers accept a custom default backend in the Helm values (controller.defaultBackend.service for ingress-nginx). You can also set a custom error page at the application layer by handling 404s in your app.
What is the difference between a 404 from the controller and a 404 from my app?
A controller 404 means the request never reached your app. An app 404 means the request reached your app, the app processed it, and the app returned 404 (because the resource does not exist). The “default backend - 404” body always comes from the controller. Your app’s 404 has a different body.
Why is the Ingress controller ignoring my Ingress?
Most often, the ingressClassName is wrong, the controller is not watching the Ingress’s namespace, or the Ingress is invalid (a typo in the Service name, a missing port). kubectl describe ingress shows the events that explain why the controller is not picking it up.