Kubernetes Ingress: HTTP Routing and TLS

Kubernetes Ingress solves a fundamental problem: how do you expose dozens of HTTP services without creating dozens of expensive LoadBalancer services? Each cloud LoadBalancer costs money and consumes...

Key Insights

  • Ingress provides a unified HTTP/HTTPS entry point to your cluster, replacing the need for multiple LoadBalancer services and reducing cloud costs significantly
  • TLS termination at the Ingress layer centralizes certificate management and offloads encryption overhead from application pods
  • cert-manager automates the entire certificate lifecycle with Let’s Encrypt, eliminating manual certificate renewal and reducing operational burden

Understanding Kubernetes Ingress

Kubernetes Ingress solves a fundamental problem: how do you expose dozens of HTTP services without creating dozens of expensive LoadBalancer services? Each cloud LoadBalancer costs money and consumes a public IP address. Ingress provides a single entry point that intelligently routes traffic to backend services based on hostnames and URL paths.

An Ingress resource is just a configuration object. The actual routing happens in an Ingress Controller—a pod running proxy software like NGINX, Traefik, or HAProxy. You must install an Ingress Controller before Ingress resources do anything. This separation of concerns lets you swap controllers without changing your Ingress definitions.

Popular controllers include NGINX Ingress Controller (the most widely deployed), Traefik (known for automatic service discovery), and cloud-specific options like AWS ALB Ingress Controller. Each has different features and annotation syntax, but they all implement the core Ingress specification.

Basic HTTP Routing

Ingress routing operates on two dimensions: hostnames and URL paths. Host-based routing directs traffic based on the HTTP Host header, while path-based routing uses the URL path. You can combine both for fine-grained control.

Here’s a simple Ingress that routes traffic for a single hostname:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-ingress
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

The pathType field is critical. Prefix matches any path starting with the specified value, while Exact requires an exact match. Use Prefix for most cases.

Path-based routing splits traffic to different services based on URL structure:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-based-routing
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
      - path: /web
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 3000
      - path: /
        pathType: Prefix
        backend:
          service:
            name: default-service
            port:
              number: 80

Order matters here. Most Ingress Controllers evaluate rules from most specific to least specific, but always put more specific paths first to avoid ambiguity.

For multi-tenant applications, host-based routing isolates traffic by subdomain:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-tenant-ingress
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: tenant1.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: tenant1-service
            port:
              number: 80
  - host: tenant2.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: tenant2-service
            port:
              number: 80

Advanced Routing Patterns

Real applications need more sophisticated routing. Path rewriting strips URL prefixes before forwarding requests to backend services. This is controller-specific and uses annotations.

For NGINX Ingress Controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: api-service
            port:
              number: 8080

The regex captures everything after /api/ and rewrites it to the root path. A request to /api/users becomes /users when it reaches the backend service.

Combining host and path rules creates complex routing topologies:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: complex-routing
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /api/v1
        pathType: Prefix
        backend:
          service:
            name: api-v1-service
            port:
              number: 8080
      - path: /api/v2
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 8080
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 3000

TLS Configuration

TLS termination at the Ingress layer centralizes certificate management. The Ingress Controller handles HTTPS connections and forwards plain HTTP to backend services.

First, create a Kubernetes TLS secret containing your certificate and private key:

kubectl create secret tls myapp-tls \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key \
  --namespace=default

Reference this secret in your Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  namespace: default
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

Force HTTPS redirects with an annotation:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: https-redirect
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - secure.example.com
    secretName: secure-tls
  rules:
  - host: secure.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: secure-service
            port:
              number: 80

Multiple TLS certificates for different hosts:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-tls-ingress
  namespace: default
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app1.example.com
    secretName: app1-tls
  - hosts:
    - app2.example.com
    secretName: app2-tls
  rules:
  - host: app1.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app1-service
            port:
              number: 80
  - host: app2.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app2-service
            port:
              number: 80

Automated Certificate Management with cert-manager

Manual certificate management doesn’t scale. cert-manager automates certificate provisioning and renewal using Let’s Encrypt or other ACME providers.

Install cert-manager:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml

Create a ClusterIssuer for Let’s Encrypt production:

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
    solvers:
    - http01:
        ingress:
          class: nginx

Now Ingress resources can request automatic certificates:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: auto-tls-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-auto-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

cert-manager creates the TLS secret automatically and renews certificates before expiration.

Troubleshooting and Best Practices

Debug Ingress issues systematically. Check the Ingress resource first:

kubectl describe ingress myapp-ingress -n default

Look for events indicating backend service issues or configuration errors. Check Ingress Controller logs:

kubectl logs -n ingress-nginx deployment/ingress-nginx-controller

Verify backend services are healthy:

kubectl get endpoints myapp-service -n default

Empty endpoints mean no pods match the service selector.

Production Ingress configurations should include resource limits and rate limiting:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: production-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rate-limit: "100"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - prod.example.com
    secretName: prod-tls
  rules:
  - host: prod.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prod-service
            port:
              number: 80

For complex environments, use multiple Ingress classes to separate traffic types—public-facing traffic on one controller, internal APIs on another. This provides isolation and independent scaling.

Ingress is the foundation of HTTP routing in Kubernetes. Master these patterns, automate certificate management, and you’ll have a robust, production-ready ingress architecture.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.