Complete Guide to Kubernetes Ingress
Configure and troubleshoot Kubernetes Ingress controllers. Covers NGINX Ingress, TLS, path routing, annotations, IngressClass, and common pitfalls.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Complete Guide to Kubernetes Ingress
Introduction
Kubernetes Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. It provides name-based virtual hosting, path-based routing, TLS termination, and other Layer 7 features that a plain Service (Layer 4) cannot. This guide covers installing an Ingress controller, configuring routing rules, enabling TLS, using annotations, and troubleshooting common issues.
What Is Ingress?
Ingress is a Kubernetes resource type that manages external access to cluster services. It is not a controller itself — it defines rules, and an Ingress controller implements them.
- Ingress Resource: A set of routing rules (host, path, backend service).
- Ingress Controller: A daemon that watches Ingress resources and configures a proxy (NGINX, Traefik, HAProxy, Envoy) to enforce the rules.
- IngressClass: A cluster-scoped resource that links Ingress resources to a specific controller.
Without an Ingress controller, Ingress resources have no effect.
Installing an Ingress Controller
NGINX Ingress Controller (most common)
# Install 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
# Verify installation
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
Verify the IngressClass
# Check the IngressClass created by the controller
kubectl get ingressclass
# Output:
# NAME CONTROLLER PARAMETERS AGE
# nginx k8s.io/ingress-nginx <none> 2m
Basic Ingress Resource
Single service routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
ingressClassName: nginx
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
Path-based routing (multiple services)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-service-ingress
spec:
ingressClassName: nginx
rules:
- host: app.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: 80
- path: /
pathType: Prefix
backend:
service:
name: default-service
port:
number: 80
Name-based virtual hosting
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vhost-ingress
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog-service
port:
number: 80
TLS Termination
Install cert-manager for automatic TLS
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
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
solvers:
- http01:
ingress:
class: nginx
Ingress with TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
TLS passthrough (no termination)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: passthrough-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
spec:
ingressClassName: nginx
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-service
port:
number: 443
Common Annotations
Rewrite target
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: app.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 8080
CORS
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://frontend.example.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
Rate limiting
metadata:
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst: "20"
Basic authentication
# Create htpasswd secret
htpasswd -c auth admin
kubectl create secret generic basic-auth --from-file=auth -n my-namespace
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
Custom timeouts
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
WebSocket support
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
Session affinity (sticky sessions)
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
Advanced Configurations
Default backend (custom 404)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: default-backend-ingress
spec:
ingressClassName: nginx
defaultBackend:
service:
name: custom-404-service
port:
number: 80
Canary deployments
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-ingress
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: canary-service
port:
number: 80
Multiple Ingress resources for the same host
Multiple Ingress resources matching the same host are merged by the controller. Use this to split routing rules across teams or namespaces.
Troubleshooting
Check Ingress status
# List all Ingress resources
kubectl get ingress -A
# Describe a specific Ingress
kubectl describe ingress my-app-ingress -n my-namespace
# Check Ingress controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# Check if the Ingress controller is running
kubectl get pods -n ingress-nginx
Common issues
404 Not Found:
- The backend service does not exist or has no ready pods
- The path does not match any rule
- The IngressClass is not set or does not match
502 Bad Gateway:
- The backend service port is wrong
- The backend pods are crashing
- The backend is not listening on the expected port
503 Service Unavailable:
- No healthy pods in the backend service
- The service selector does not match any pods
TLS certificate not working:
- cert-manager is not installed or the ClusterIssuer is not ready
- The TLS secret is not in the same namespace as the Ingress
- The domain does not point to the Ingress controller’s external IP
Verify DNS resolution
# Get the Ingress controller external IP
kubectl get svc -n ingress-nginx
# Check DNS
dig app.example.com
nslookup app.example.com
# Test locally with curl
curl -H "Host: app.example.com" http://<ingress-controller-ip>
Debug with kubectl
# Check Ingress events
kubectl get events -n my-namespace --field-selector reason=Sync
# Check cert-manager certificates
kubectl get certificate -A
kubectl describe certificate app-tls-secret -n my-namespace
# Check IngressClass
kubectl get ingressclass
kubectl describe ingressclass nginx
Ingress vs Gateway API
Kubernetes Gateway API is the successor to Ingress. It provides more expressive routing (header-based, weight-based) and better multi-tenant support.
| Feature | Ingress | Gateway API |
|---|---|---|
| Layer | L7 only | L4 and L7 |
| Routing | Host + path | Host, path, header, weight |
| TLS | Per-Ingress | Per-listener |
| Multi-tenant | Limited | Native (Route namespaces) |
| Status | Stable | GA (since K8s 1.25) |
Best Practices
- Always set ingressClassName — without it, the Ingress may not be picked up by any controller
- Use cert-manager for TLS — manual certificate management does not scale
- Set resource limits on the controller — the Ingress controller is critical infrastructure
- Monitor the controller — track 4xx/5xx rates, latency, active connections
- Use pathType: Prefix for most cases —
ImplementationSpecificis controller-dependent - Namespace your Ingress resources — keep them in the same namespace as the backend services
- Use annotations sparingly — they are controller-specific and reduce portability
- Test with curl -H “Host:” — verify routing before pointing DNS
- Keep the controller updated — security patches and bug fixes
- Run multiple replicas — a single Ingress controller is a single point of failure
Frequently Asked Questions
Do I need an Ingress controller?
Yes. Ingress resources are just rules. Without a controller watching and implementing them, they have no effect. Install NGINX Ingress Controller, Traefik, or another controller.
Can I use Ingress for TCP/UDP?
Not with the standard Ingress resource. Ingress is HTTP/HTTPS only. For TCP/UDP, use a LoadBalancer Service or the controller’s TCP/UDP passthrough configuration (NGINX supports this via ConfigMap).
How do I route to a service in another namespace?
You cannot directly. Ingress resources must reference services in the same namespace. Use a Service of type ExternalName or a re-export Service in the Ingress namespace.
What is the difference between Ingress and LoadBalancer?
A LoadBalancer Service gives you a single cloud load balancer pointing at a single service. Ingress gives you one entry point with routing rules to many services — name-based virtual hosting, path-based routing, and TLS termination.