The Problem
You have multiple services running in Kubernetes. Each needs external access, but creating a LoadBalancer service for each one means paying for multiple cloud load balancers and managing separate DNS entries. You need a single entry point that routes traffic to different services based on hostname or URL path, with centralized TLS termination.
Kubernetes Ingress with the Nginx controller gives you exactly this — one load balancer, multiple services, automatic TLS.
Installing the Nginx Ingress Controller
Using Helm (recommended)
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 \
--set controller.resources.requests.cpu=100m \
--set controller.resources.requests.memory=90Mi
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
Using kubectl (manifest-based)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
Get the external IP
kubectl get svc ingress-nginx-controller -n ingress-nginx
Point your DNS records to this IP/hostname.
Basic Ingress Resource
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
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-basic.yaml
kubectl get ingress -n production
Host-Based Routing
Route different domains to different services:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
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
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 3000
Path-Based Routing
Route URL paths to different services (microservices pattern):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-based-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /api/v1/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
- path: /api/v1/orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
- path: /api/v1/payments
pathType: Prefix
backend:
service:
name: payment-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
Path types explained
Prefix— Matches the URL path prefix./apimatches/api,/api/,/api/usersExact— Only matches the exact path./apidoes not match/api/usersImplementationSpecific— Depends on IngressClass
Order matters: more specific paths should come first.
TLS with cert-manager
Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
kubectl get pods -n cert-manager
Create a ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
Add TLS to your Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress-tls
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
- api.example.com
secretName: app-tls-secret
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
cert-manager automatically requests and renews certificates:
kubectl get certificates -n production
kubectl describe certificate app-tls-secret -n production
kubectl get challenges -n production
Production Annotations
Rate limiting
metadata:
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"
nginx.ingress.kubernetes.io/limit-connections: "5"
Timeouts and body size
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
CORS configuration
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"
Canary deployments
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service-v2
port:
number: 80
Troubleshooting
# Check ingress controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=100
# Check if ingress has an address
kubectl get ingress -n production -o wide
# Inspect generated nginx config
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \
cat /etc/nginx/nginx.conf | grep -A 20 "server_name app.example.com"
# Test from inside cluster
kubectl run debug --rm -it --image=curlimages/curl -- \
curl -H "Host: app.example.com" http://ingress-nginx-controller.ingress-nginx.svc.cluster.local
# Check events
kubectl describe ingress app-ingress -n production
Common Mistakes
ingressClassName: nginx — Without this, the Nginx controller ignores your Ingress resource. Older clusters used kubernetes.io/ingress.class: nginx annotation instead.proxy-body-size — Default is 1MB. File uploads fail with 413 errors.Quick Reference
| Task | Configuration |
|---|---|
| Basic routing | <code class="inline-code">spec.rules[].host</code> + <code class="inline-code">paths[].backend</code> |
| TLS termination | <code class="inline-code">spec.tls[]</code> + cert-manager annotation |
| Path routing | Multiple <code class="inline-code">paths[]</code> entries |
| Rate limiting | <code class="inline-code">limit-rps</code> annotation |
| CORS | <code class="inline-code">enable-cors</code> annotation |
| Canary deploy | <code class="inline-code">canary: true</code> + <code class="inline-code">canary-weight</code> annotations |
| Body size | <code class="inline-code">proxy-body-size</code> annotation |
| SSL redirect | <code class="inline-code">ssl-redirect: "true"</code> annotation |
| WebSocket | <code class="inline-code">proxy-read-timeout: "3600"</code> annotation |
| Custom headers | <code class="inline-code">configuration-snippet</code> annotation |
Summary
The Nginx Ingress Controller gives you a single cloud load balancer that routes traffic to any number of services based on hostname and path. Combined with cert-manager for automatic TLS, you get production-grade HTTPS routing with minimal configuration. Start with basic routing, add TLS, then layer on rate limiting and auth annotations as needed.
---
Frequently Asked Questions
What is Kubernetes Ingress and how is it different from a Service?
A Service provides internal load balancing within the cluster (ClusterIP) or basic external access (NodePort, LoadBalancer). Ingress adds HTTP/HTTPS routing rules — path-based routing, host-based virtual hosting, TLS termination, and more — without needing a LoadBalancer per service. Ingress requires an Ingress Controller (like nginx) to function.
How do I set up TLS/HTTPS with Ingress-NGINX?
Create a TLS Secret with your certificate and key, then reference it in your Ingress resource under spec.tls. For automatic certificate management, install cert-manager and add annotations like cert-manager.io/cluster-issuer: letsencrypt-prod to your Ingress. cert-manager will automatically provision and renew Let's Encrypt certificates.
Why is my Ingress returning 404 or 502 errors?
A 404 means no Ingress rule matched the request — check that host and path match exactly (including trailing slashes). A 502 means the Ingress Controller cannot reach the backend — verify the Service exists, endpoints are healthy, and port numbers match between Ingress, Service, and Pod. Use kubectl describe ingress and check controller logs for details.
What is the difference between Ingress-NGINX and NGINX Ingress Controller?
There are two projects: kubernetes/ingress-nginx (community-maintained, most common) and nginxinc/kubernetes-ingress (NGINX Inc.'s commercial version). The community version uses configmaps and annotations for configuration, while NGINX Inc.'s version supports NGINX Plus features. Most teams use the community kubernetes/ingress-nginx.
How do I route traffic to different services based on URL path?
Define multiple path rules in your Ingress spec under the same host. Set path: /api pointing to your API service and path: / pointing to your frontend service. Use pathType: Prefix for prefix matching or pathType: Exact for exact path matching. The Ingress Controller routes requests to the appropriate backend based on the longest matching path.
---
Related Resources
- kubectl Cheatsheet — 71 kubectl commands searchable by task
- DevOps Interview Academy — 60 DevOps interview questions including Kubernetes
- Production Troubleshooting Scenarios — 15 production troubleshooting scenarios
- Nginx Reverse Proxy Setup — Nginx configuration fundamentals for reverse proxying
- Kubernetes Pod Troubleshooting Guide — Debugging ingress-related pod failures