Kubernetes Ingress

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

Route external HTTP traffic to Services with Ingress. Controllers, host and path rules, pathType, TLS with cert-manager, and why your Ingress returns 404.

An Ingress is a set of HTTP routing rules: send api.example.com here, send /admin there, terminate TLS for these hostnames. One entry point, many backend Services.

It exists because the alternative is a cloud load balancer per Service, which gets expensive and unmanageable at anything past a handful of applications.

An Ingress on its own does nothing

This is the single most important thing to understand, and the cause of most "my Ingress isn't working" confusion.

An Ingress object is just configuration. It has no behaviour. Something has to read it and act on it, and that something is an Ingress controller. A separate component you install yourself. Kubernetes does not ship one.

# Is a controller installed?
kubectl get pods -A | grep -i ingress

# What classes are available?
kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       9d

If kubectl get ingressclass returns nothing, no controller is installed, and your Ingress will sit there with an empty ADDRESS field forever.

Common choices: ingress-nginx (the usual default), Traefik, and cloud-native controllers such as the AWS Load Balancer Controller, which provisions a real ALB rather than running a proxy in the cluster.

A working Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: acme-public
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /payments
            pathType: Prefix
            backend:
              service:
                name: payments-api
                port:
                  number: 80
          - path: /users
            pathType: Prefix
            backend:
              service:
                name: users-api
                port:
                  number: 80
kubectl get ingress
NAME          CLASS   HOSTS              ADDRESS                   PORTS   AGE
acme-public   nginx   api.example.com    34.122.18.4               80      3m

An empty ADDRESS after a few minutes means the controller has not picked it up, usually a missing or wrong ingressClassName.

The backend Service must be in the same namespace as the Ingress. There is no cross-namespace backend reference; an Ingress per namespace is the normal pattern.

pathType matters more than it looks

pathTypeBehaviour
PrefixMatches on whole path segments: /api matches /api and /api/v1, but not /apifoo
ExactMatches only that exact path, case-sensitive
ImplementationSpecificWhatever the controller decides (often regex)

Prefix is what you want almost always. Exact on /api means /api/users gets no match and falls through to a 404, which is a surprisingly common self-inflicted outage.

Rules are evaluated longest-path-first, so a specific /api/admin rule wins over a general /api one regardless of the order you write them.

TLS

Ingress terminates TLS using a Secret of type kubernetes.io/tls:

spec:
  tls:
    - hosts:
        - api.example.com
      secretName: api-example-com-tls
  rules:
    - host: api.example.com
      # ...

The Secret must live in the same namespace as the Ingress, which catches people running one shared certificate across namespaces. It has to be copied, or issued per namespace.

In practice, don't manage certificates by hand. cert-manager issues and renews them from Let's Encrypt automatically:

metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [api.example.com]
      secretName: api-example-com-tls   # cert-manager creates this

cert-manager sees the annotation, solves the ACME challenge, writes the Secret, and renews before expiry. A certificate stuck in False usually means the HTTP-01 challenge cannot reach the cluster. Check that DNS points at the Ingress address and that the challenge path is not being intercepted by another rule.

Annotations are controller-specific

This is where portability breaks down. The Ingress spec covers hosts, paths and TLS. Everything else, rewrites, timeouts, body size limits, rate limiting, auth, is done through annotations that each controller defines itself.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"

Those are ingress-nginx annotations. On Traefik or the AWS Load Balancer Controller they do nothing at all. Silently, with no warning. Copying a snippet from a blog post written for a different controller is a very common way to lose an afternoon.

Two defaults worth knowing on ingress-nginx: the request body limit is 1 MB, which breaks file uploads with a 413, and the read timeout is 60 seconds, which cuts off long-running requests.

Debugging

Work from the outside in.

# 1. Does the Ingress have an address?
kubectl get ingress

# 2. Did the controller accept it? Events name the problem.
kubectl describe ingress acme-public

# 3. Does the backend Service actually have endpoints?
kubectl get endpointslices -l kubernetes.io/service-name=payments-api

# 4. What is the controller saying?
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50

The failures you will actually meet:

404 from the default backend: no rule matched. Either the Host header does not match your host: value (testing by IP instead of hostname does this), the path does not match given its pathType, or the Ingress is in a different namespace from the Service.

502 Bad Gateway: a rule matched but the backend is unreachable. Nearly always the Service has no endpoints, or the port in the Ingress does not match the Service's port. Note it must match the Service port, not the pod's targetPort.

503 Service Unavailable: the controller has no healthy endpoint to send to.

Empty ADDRESS: wrong or missing ingressClassName, or no controller installed.

Ingress and the Gateway API

The Gateway API is the successor to Ingress. It splits responsibilities properly. Cluster operators define Gateway resources, application teams define HTTPRoute resources, and moves the things that currently require annotations into the actual specification, including header matching, traffic splitting and cross-namespace routing.

It is worth knowing about and worth choosing for a greenfield platform. Ingress is not going away, still has far broader ecosystem support, and remains the right choice for most existing clusters.

Quick reference

TaskCommand
List Ingresseskubectl get ingress -A
Detail and eventskubectl describe ingress <name>
Available classeskubectl get ingressclass
Controller logskubectl logs -n ingress-nginx deploy/ingress-nginx-controller
Check backend endpointskubectl get endpointslices -l kubernetes.io/service-name=<svc>
Test with a Host headercurl -H "Host: api.example.com" http://<ingress-ip>/payments
Certificate statuskubectl get certificate

Key takeaways

  • An Ingress does nothing without a controller. Kubernetes ships none. Check kubectl get ingressclass first.
  • ingressClassName is what binds your Ingress to a controller. Missing it leaves ADDRESS empty forever.
  • Use pathType: Prefix. Exact on /api will 404 on /api/users.
  • The backend Service and TLS Secret must be in the Ingress's namespace. There is no cross-namespace reference.
  • Annotations are controller-specific and fail silently on the wrong controller.
  • 404 means no rule matched; 502 means the backend is unreachable. That split tells you where to look.
  • Use cert-manager rather than rotating certificates by hand.

Check your understanding

Exercise

An Ingress with no address

An Ingress has been applied successfully but kubectl get ingress shows an empty ADDRESS column after 15 minutes. What is the most likely cause?

Exercise

Routing that half works

An Ingress rule uses path /api with pathType: Exact. Requests to /api succeed but /api/users returns the default backend 404. Why?


Frequently Asked Questions

What is the difference between an Ingress and a Service?

A Service provides stable networking to a set of pods and routes on port alone. It has no understanding of HTTP. An Ingress sits in front of Services and routes HTTP traffic based on hostname and URL path, terminates TLS, and lets many applications share one entry point. They work together: the Ingress decides which Service should receive a request, and the Service decides which pod. You still need a Service for every backend an Ingress references.

Why is my Ingress returning 404?

No rule matched the request. Check three things in order. First, the Host header, if you are testing against the Ingress IP directly, no hostname is sent and a rule with host: api.example.com cannot match; use curl -H "Host: api.example.com". Second, the path and its pathType, Exact on /api will not match /api/users. Third, the namespace, because the backend Service must be in the same namespace as the Ingress. kubectl describe ingress and the controller logs usually name the problem outright.

Do I need an Ingress controller, and which should I choose?

Yes. An Ingress resource is inert configuration and Kubernetes ships no controller. ingress-nginx is the most widely used and the safest default, with the largest body of documentation and examples. Traefik is popular where dynamic configuration and a built-in dashboard matter. On AWS, the AWS Load Balancer Controller is often the better fit because it provisions a real ALB instead of running a proxy inside the cluster, which moves TLS termination and scaling to managed infrastructure.

How do I add HTTPS to an Ingress?

Reference a Secret of type kubernetes.io/tls in the spec.tls block, in the same namespace as the Ingress. Rather than creating and rotating that Secret yourself, install cert-manager and add a cert-manager.io/cluster-issuer annotation; it will obtain a certificate from Let's Encrypt, write the Secret, and renew it automatically before expiry. If a certificate never becomes ready, the HTTP-01 challenge usually cannot reach the cluster. Verify DNS points at the Ingress address and no rule is intercepting the challenge path.

Why do the annotations from a tutorial have no effect?

They were almost certainly written for a different Ingress controller. Only hosts, paths and TLS are part of the Ingress specification itself; rewrites, timeouts, body size limits, rate limiting and authentication are all controller-specific annotations. An nginx.ingress.kubernetes.io/* annotation is ignored entirely by Traefik or the AWS Load Balancer Controller, with no error to tell you. Check which controller you are running with kubectl get ingressclass and use its own documentation.

Should I use the Gateway API instead of Ingress?

For a new platform, it is worth serious consideration. The Gateway API separates the cluster operator's concerns from the application team's, and brings header-based matching, traffic splitting and cross-namespace routing into the specification rather than leaving them to vendor annotations. For existing clusters, Ingress remains well supported, has far more ecosystem tooling and examples, and is not being removed. Migrating is a project rather than a quick win, so most teams adopt the Gateway API for new workloads first.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources