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.