Skip to main content

Ingress: HTTP Routing

Key Takeaways for AI & Readers
  • Centralized Routing: Ingress allows multiple services to share a single IP and Load Balancer by routing traffic based on hostnames or URL paths, eliminating the need for one LoadBalancer per service.
  • Resource vs. Controller: An Ingress resource is just a set of routing rules stored in the API server. An Ingress Controller (like NGINX or Traefik) is the actual workload that reads those rules and routes traffic accordingly. The resource does nothing without a controller.
  • Path Matching: Kubernetes supports "Exact" and "Prefix" path types. Use "Rewrite Target" annotations to strip path prefixes before they reach your backend application.
  • TLS Termination: Ingress is the standard layer for managing SSL certificates, often automated with cert-manager for automatic Let's Encrypt certificate provisioning and renewal.
  • Ingress Classes: When multiple controllers exist in a cluster (e.g., NGINX for internal, ALB for public), the ingressClassName field determines which controller handles each Ingress resource.
  • Gateway API: The successor to Ingress, Gateway API provides a more expressive, role-oriented model for traffic routing. It is production-ready and worth evaluating for new projects.

Services (ClusterIP) are great for internal traffic. LoadBalancers are great for exposing a single app. But what if you have 50 microservices? You do not want to pay for 50 Cloud Load Balancers.

Ingress solves this. It exposes multiple services under a single IP address, using Host-based and Path-based routing.

Interactive Routing Simulator

Test how an Ingress routes traffic based on the URL path (/ vs /api).

Loading...

1. The Architecture

Ingress is unique because creating the resource (kind: Ingress) does nothing by itself. You need a Controller running in the cluster to act on those rules.

How It Works

  1. You deploy an Ingress Controller (a pod running a reverse proxy like NGINX).
  2. The controller watches the Kubernetes API for Ingress resources.
  3. When it detects a new or updated Ingress, it dynamically regenerates its configuration (e.g., nginx.conf) and reloads.
  4. External traffic hits the controller (via a LoadBalancer Service or NodePort), and the controller routes it to the correct backend pods based on the Ingress rules.

Analogy: The Ingress Resource is a "routing table" and the Controller is the "router" that enforces it.

Not all controllers are equal. Your choice depends on your environment, performance needs, and feature requirements.

ControllerMaintained ByBest ForKey Features
NGINX Ingress ControllerKubernetes communityGeneral purpose, most widely usedExtensive annotation support, Lua customization
TraefikTraefik LabsDynamic environments, auto-discoveryNative Let's Encrypt, middleware chains, dashboard
HAProxy IngressHAProxy TechnologiesHigh-performance, low-latencyAdvanced load balancing algorithms, connection draining
AWS ALB Ingress ControllerAWSAWS-native deploymentsMaps directly to Application Load Balancers, native WAF
Istio GatewayIstio projectService mesh environmentsFull mesh integration, advanced traffic management
ContourVMwareEnvoy-based routingHTTPProxy CRD, delegation model for multi-team

To install the NGINX Ingress Controller (the most common choice):

# Using Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace

2. Routing Rules

Path-Based Routing

Route different URL paths to different backend services:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # Strip the path prefix
spec:
ingressClassName: nginx # Which controller handles this
rules:
- host: myapp.example.com
http:
paths:
# Requests to /api/* go to the API service
- path: /api
pathType: Prefix # Matches /api, /api/users, /api/v2/items
backend:
service:
name: api-service
port:
number: 80
# All other requests go to the frontend
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80

Path Types

  • Exact: The URL must match the path exactly. /foo matches only /foo, not /foo/ or /foo/bar.
  • Prefix: Matches the URL prefix, split on /. /api matches /api, /api/, /api/users, and /api/v2/items. It does not match /apiVersion (the boundary is always a /).

Host-Based Routing

Route different domains to different services, all sharing the same IP address:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-domain-ingress
spec:
ingressClassName: nginx
rules:
# API traffic
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
# Dashboard traffic
- host: dashboard.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard-service
port:
number: 3000
# Documentation site
- host: docs.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: docs-service
port:
number: 8080

All three domains resolve to the same IP (the Ingress Controller's external IP). The controller inspects the Host header to determine where to route each request.


3. TLS & Security

Ingress is the standard place to terminate SSL/TLS. The controller handles encryption/decryption so your backend services can communicate over plain HTTP internally.

Manual TLS Configuration

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
- dashboard.example.com
secretName: example-com-tls # Kubernetes Secret with tls.crt and tls.key
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80

The Secret must contain tls.crt (the certificate chain) and tls.key (the private key):

kubectl create secret tls example-com-tls \
--cert=fullchain.pem \
--key=privkey.pem \
--namespace=production

Automated TLS with cert-manager

Managing certificates manually is error-prone. cert-manager automates the entire lifecycle -- it requests certificates from Let's Encrypt (or other CAs), stores them in Kubernetes Secrets, and renews them before expiration.

# First, create a ClusterIssuer for Let's Encrypt
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
---
# Then annotate your Ingress to use cert-manager
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auto-tls-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod" # Triggers cert-manager
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-example-com-tls # cert-manager creates and manages this Secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80

cert-manager detects the annotation, requests a certificate from Let's Encrypt, completes the HTTP-01 challenge through the Ingress controller, and stores the resulting certificate in the specified Secret. It renews the certificate automatically before it expires.


4. Ingress Classes

What if you have multiple controllers in the same cluster? For example, NGINX for internal tools and AWS ALB for public-facing APIs.

Every Ingress Controller registers an IngressClass:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx-internal
annotations:
ingressclass.kubernetes.io/is-default-class: "true" # Used when no class is specified
spec:
controller: k8s.io/ingress-nginx

Then each Ingress resource specifies which class it belongs to:

spec:
ingressClassName: nginx-internal # Handled by the internal NGINX controller

If you do not specify ingressClassName and a default IngressClass exists, Kubernetes assigns the default. If no default exists and no class is specified, the Ingress may not be picked up by any controller.


5. Annotations for Controller-Specific Features

The Ingress spec is intentionally minimal. Advanced features are configured through annotations that are specific to your controller. Here are commonly used NGINX Ingress annotations:

Rewrite Rules

metadata:
annotations:
# Requests to /api/users arrive at the backend as /users
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 80

Rate Limiting

metadata:
annotations:
# Limit each client IP to 10 requests per second
nginx.ingress.kubernetes.io/limit-rps: "10"
# Allow bursts of up to 20 requests
nginx.ingress.kubernetes.io/limit-burst-multiplier: "2"
# Return 429 (Too Many Requests) when exceeded
nginx.ingress.kubernetes.io/limit-rate-after: "10"

Other Common Annotations

metadata:
annotations:
# Force HTTPS redirect
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Upload size limit
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# Timeout settings
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
nginx.ingress.kubernetes.io/proxy-send-timeout: "120"
# Custom headers
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header X-Frame-Options "SAMEORIGIN";
# CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://frontend.example.com"

6. Default Backend

When a request arrives that does not match any Ingress rule (wrong host, wrong path), the controller can route it to a default backend:

spec:
defaultBackend:
service:
name: default-404-service
port:
number: 80
rules:
- host: myapp.example.com
# ... normal rules

If no default backend is configured, the Ingress Controller typically returns its own default 404 page. Configuring a custom default backend lets you serve a branded error page or redirect users to a help page.


7. Gateway API: The Successor to Ingress

The Ingress resource has known limitations: its spec is too simple for advanced use cases, it relies on non-portable annotations for features like header-based routing and traffic splitting, and it lacks role separation.

Gateway API is the next generation of Kubernetes traffic routing. It has been production-ready (GA) since Kubernetes 1.26 for core HTTP routing features.

Key Differences

FeatureIngressGateway API
Role modelSingle resource, one authorGatewayClass (infra), Gateway (cluster ops), HTTPRoute (dev)
Header routingAnnotation-dependentNative spec support
Traffic splittingNot supportedNative weight-based routing
Protocol supportHTTP/HTTPS onlyHTTP, gRPC, TCP, UDP, TLS
PortabilityAnnotations are controller-specificSpec-level features are portable

Gateway API Example

# Cluster operator creates a Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: infra
spec:
gatewayClassName: nginx
listeners:
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: wildcard-tls
---
# Developer creates an HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: team-backend
spec:
parentRefs:
- name: production-gateway
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: api-v2
port: 80
weight: 90 # 90% of traffic to v2
- name: api-v3
port: 80
weight: 10 # 10% of traffic to v3 (canary)

For new projects, evaluate whether Gateway API is supported by your chosen controller. For existing projects, Ingress continues to work and is not being removed. See the Gateway API documentation for a detailed guide.


Troubleshooting

Diagnosing 404 Errors

The first question when you see a 404 is: who generated it?

Controller 404 (no matching rule): The Ingress Controller received the request but found no matching rule.

  • Check kubectl get ingress to verify your rules exist.
  • Verify the Host header matches. Use curl -H "Host: myapp.example.com" http://<controller-ip>/.
  • Check ingressClassName -- if it does not match your controller, the Ingress is ignored.
  • Look at controller logs: kubectl logs -n ingress-nginx <controller-pod>.

Application 404 (route reached backend, app returned 404): The Ingress successfully routed to your service, but the application did not recognize the path.

  • Check whether your app expects the prefix (e.g., /api/users) or a stripped path (e.g., /users).
  • If your app expects /users but receives /api/users, add a rewrite-target annotation.

Diagnosing 502 Bad Gateway Errors

A 502 means the Ingress Controller reached your Service but could not get a response from the backend pods.

  • Pods not ready: Check kubectl get pods -- are your pods in Running state with readiness probes passing?
  • Wrong port: Verify the Ingress backend port matches the Service port, and the Service targetPort matches the container port.
  • Service has no endpoints: Run kubectl get endpoints <service-name>. If the list is empty, no pods match the Service selector.
  • Timeout: The backend is too slow. Increase proxy-read-timeout annotation.

Diagnosing 503 Service Unavailable

  • The Service exists but has zero healthy endpoints.
  • Check pod readiness probes and ensure pods are passing them.

Common Pitfalls

1. No Ingress Controller installed. Creating an Ingress resource without a controller does nothing. No error is thrown -- the rules simply have no effect.

2. Missing ingressClassName. If multiple controllers exist and you omit the class, the wrong controller (or no controller) may pick up your Ingress.

3. Forgetting rewrite-target. Your Ingress listens on /api but your app serves on /. Without a rewrite, the backend receives /api/users instead of /users and returns 404.

4. TLS Secret in the wrong namespace. The TLS Secret must be in the same namespace as the Ingress resource. A Secret in namespace default cannot be referenced by an Ingress in namespace production.

5. Using Ingress for non-HTTP traffic. Ingress is HTTP/HTTPS only. For TCP or UDP services, use a LoadBalancer Service, or adopt Gateway API which supports non-HTTP protocols.

6. Not monitoring the Ingress Controller. The controller is a critical single point of entry. If it runs out of resources or crashes, all external traffic stops. Set resource requests/limits, configure multiple replicas, and monitor its /metrics endpoint with Prometheus.

7. Wildcard hosts without TLS. Using host: "*.example.com" routes all subdomains through one rule. This works but requires a wildcard TLS certificate. Without TLS, traffic flows unencrypted and may be intercepted.


Best Practices

  1. Always specify ingressClassName. Even if you have only one controller today, explicitly declaring the class prevents issues when a second controller is added later.

  2. Use cert-manager for TLS. Manual certificate management does not scale and certificates eventually expire. cert-manager handles issuance and renewal automatically.

  3. Set rate limits on public-facing Ingresses. This protects your backend services from abuse and DDoS attacks.

  4. Configure health checks on the controller. The Ingress Controller itself needs monitoring. Set up readiness probes on the controller pods and monitor their metrics endpoint.

  5. Use separate Ingress resources per team or service instead of one monolithic Ingress with dozens of rules. This improves isolation and reduces the blast radius of misconfigurations.

  6. Evaluate Gateway API for new projects. It is more expressive, portable, and has proper role separation between infrastructure operators and application developers.

  7. Run multiple controller replicas. The Ingress Controller is a single point of entry for all external traffic. Running at least 2-3 replicas with pod anti-affinity ensures that a single node failure does not take down your entire ingress layer.

  8. Use separate Ingress resources for TLS and non-TLS traffic when you need different certificate configurations. This keeps each resource focused and easier to debug when certificate issues arise.


What's Next?

  • Service Discovery (DNS) -- Understand how internal DNS resolution works, which your Ingress backends rely on.
  • RBAC (Access Control) -- Control who can create and modify Ingress resources in your cluster.
  • Observability -- Monitor Ingress Controller metrics (request rates, latency, error rates) with Prometheus and Grafana.
  • Gateway API -- Deep dive into the next-generation traffic routing API that supersedes Ingress.
  • Service Mesh -- Advanced traffic management with Istio or Linkerd for east-west (service-to-service) routing.
  • Services -- Review the foundational Service resource that Ingress routes traffic to.