Ingress Controllers in Kubernetes
A practical guide to Kubernetes Ingress: Ingress resources, NGINX Ingress Controller setup, TLS termination, path-based and host-based routing, annotations, and production configuration.
What you'll learn
- ✓What Ingress resources are and how they differ from Services
- ✓Installing and configuring NGINX Ingress Controller
- ✓Path-based and host-based routing rules
- ✓TLS termination with certificates
- ✓Annotations for rate limiting, redirects, and more
Prerequisites
- •Kubernetes fundamentals (Pods, Deployments, Services)
- •Basic understanding of HTTP, DNS, and TLS
- •Familiarity with kubectl and Helm
A Service of type LoadBalancer gives each service its own external IP and load balancer. If you have 20 services, you get 20 load balancers and 20 IPs. Ingress solves this by providing a single entry point that routes traffic to different services based on the hostname or URL path. One load balancer, one IP, and a set of routing rules that direct traffic to the right backend.
Ingress vs Services
Without Ingress:
Client → LoadBalancer A → Service A → Pods
Client → LoadBalancer B → Service B → Pods
Client → LoadBalancer C → Service C → Pods
(3 external IPs, 3 load balancers)
With Ingress:
Client → Ingress Controller (1 LoadBalancer) → Service A → Pods
→ Service B → Pods
→ Service C → Pods
(1 external IP, 1 load balancer)
An Ingress resource is a set of rules. An Ingress Controller is the software that reads those rules and configures itself to route traffic accordingly. The Ingress resource without a controller does nothing.
Installing NGINX Ingress Controller
The most common Ingress Controller is NGINX. Install it with Helm:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2
# Verify installation
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
# The controller creates a LoadBalancer service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.96.200.50 203.0.113.10 80:30080/TCP,443:30443/TCP
The EXTERNAL-IP is what you point your DNS records to.
Basic Ingress resource
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
kubectl apply -f ingress.yaml
kubectl get ingress
kubectl describe ingress app-ingress
This routes all traffic for app.example.com to frontend-service on port 80.
Path-based routing
Route different URL paths to different backend services.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /admin
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
app.example.com/api/* → api-service:8080
app.example.com/admin/* → admin-service:8080
app.example.com/* → frontend-service:80
Rules are evaluated in order from most specific to least specific. The /api and /admin rules match before the catch-all /.
pathType options
# Prefix: matches the path prefix
# /api matches /api, /api/, /api/users, /api/users/123
- path: /api
pathType: Prefix
# Exact: matches only the exact path
# /api matches only /api, not /api/ or /api/users
- path: /api
pathType: Exact
# ImplementationSpecific: behavior depends on the IngressClass
- path: /api
pathType: ImplementationSpecific
Host-based routing
Route traffic based on the hostname. Multiple domains pointing to the same Ingress Controller.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: main-app
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: docs.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: docs-service
port:
number: 80
Each hostname resolves to the same Ingress Controller IP, but the controller routes to different services based on the Host header.
TLS termination
Ingress can terminate TLS so your backend services receive plain HTTP traffic.
Create a TLS Secret
# From existing certificate files
kubectl create secret tls app-tls \
--cert=./tls.crt \
--key=./tls.key \
-n default
Configure TLS in Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
- api.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Automated certificates with cert-manager
cert-manager automates TLS certificate issuance and renewal from Let’s Encrypt.
# Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
# 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-key
solvers:
- http01:
ingress:
class: nginx
# Ingress with automatic certificate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-auto # cert-manager creates this Secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
cert-manager watches for Ingress resources with the cert-manager.io/cluster-issuer annotation, requests a certificate, stores it in the named Secret, and renews it before expiry.
NGINX Ingress annotations
Annotations configure NGINX-specific behavior on a per-Ingress basis.
Rate limiting
metadata:
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
Redirects
metadata:
annotations:
# Permanent redirect
nginx.ingress.kubernetes.io/permanent-redirect: "https://new-site.example.com"
# HTTP to HTTPS
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# www to non-www
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
Timeouts and buffer sizes
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
CORS
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type"
Custom NGINX configuration
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "X-XSS-Protection: 1; mode=block";
Default backend
Handle requests that do not match any Ingress rule.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: default-ingress
spec:
ingressClassName: nginx
defaultBackend:
service:
name: default-backend
port:
number: 80
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
Requests to unrecognized hosts or paths go to default-backend, which typically serves a custom 404 page.
Health checks
The NGINX Ingress Controller can configure health check endpoints for backends.
metadata:
annotations:
nginx.ingress.kubernetes.io/health-check-path: "/healthz"
nginx.ingress.kubernetes.io/health-check-interval: "10"
nginx.ingress.kubernetes.io/health-check-timeout: "5"
Monitoring Ingress
# Check Ingress status
kubectl get ingress -A
# View NGINX configuration generated by the controller
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf
# View controller logs
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller -f
# NGINX metrics (if enabled)
# The controller exposes Prometheus metrics on port 10254
kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller-metrics 10254:10254
curl http://localhost:10254/metrics
Alternative Ingress Controllers
NGINX is the most common but not the only option:
- Traefik — Auto-discovery, built-in Let’s Encrypt, middleware system
- HAProxy — High-performance, TCP/UDP support, advanced load balancing algorithms
- Kong — API gateway features, plugins for auth/rate-limiting/transformations
- AWS ALB Ingress Controller — Creates AWS Application Load Balancers directly
- Istio Gateway — Part of the Istio service mesh, advanced traffic management
Each controller has different strengths. NGINX is a safe default with the largest community and most documentation. Choose alternatives when you need specific features like API gateway functionality (Kong) or service mesh integration (Istio).
Ingress is the standard way to expose HTTP services in Kubernetes. Start with NGINX Ingress Controller, add cert-manager for automated TLS, and use annotations to configure behavior per-service. For production, always run at least two controller replicas and monitor the controller logs and metrics.
Related articles
- Kubernetes Kubernetes DaemonSets Explained with Practical Examples
Learn how DaemonSets run exactly one pod per node for logging, monitoring, and networking agents. Includes tolerations and update strategies.
- Kubernetes Kubernetes Persistent Volumes and Storage Classes
Understand PersistentVolumes, PersistentVolumeClaims, and StorageClasses to give your Kubernetes workloads reliable, portable storage.
- Kubernetes Kubernetes Network Policies: Pod-to-Pod Security Guide
Lock down pod communication with Kubernetes NetworkPolicies. Learn ingress and egress rules, label selectors, and namespace isolation.
- Kubernetes Kubernetes Resource Limits, Requests, and QoS Classes
Set CPU and memory requests and limits correctly. Understand QoS classes, OOMKilled errors, and CPU throttling in Kubernetes.