Kubernetes Ingress 2025 — Route External Traffic with Nginx and Cloud Controllers
Advertisement
Introduction
Why This Matters
Every Kubernetes cluster that serves external HTTP/HTTPS traffic needs an Ingress solution. Without Ingress, you would need a separate cloud load balancer for every Service — expensive and operationally painful. A single Ingress controller handles routing for all services in the cluster, enabling path-based routing, host-based routing, TLS termination, rate limiting, and authentication at the edge.
In 2025, Ingress is being supplemented by the Kubernetes Gateway API (GA since Kubernetes 1.28), which provides more expressive routing rules and better multi-team support. Understanding both Ingress and Gateway API is important as the ecosystem transitions.
Ingress vs Service LoadBalancer
Without Ingress:
- Each Service needs a LoadBalancer — expensive (one cloud LB per service)
- No shared TLS termination
- No path-based routing between services
With Ingress:
- One cloud load balancer feeds traffic to the Ingress controller
- The controller routes to multiple services based on hostname and path
- Centralized TLS termination and SSL certificate management
Installing 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.nodeSelector."kubernetes\.io/os"=linux
# Verify
kubectl get pods -n ingress-nginx
kubectl get service ingress-nginx-controller -n ingress-nginx
# EXTERNAL-IP shows the cloud load balancer's IP/hostnameBasic Ingress Resource
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80kubectl apply -f ingress.yaml
kubectl get ingress -n production
kubectl describe ingress api-ingress -n productionPath-Based Routing
Route different URL paths to different backend services:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /api/v1
pathType: Prefix
backend:
service:
name: api-v1-service
port:
number: 80
- path: /api/v2
pathType: Prefix
backend:
service:
name: api-v2-service
port:
number: 80
- path: /static
pathType: Prefix
backend:
service:
name: static-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80Host-Based Routing
Route different hostnames to different services:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
namespace: production
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 80
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80TLS with cert-manager (Automated Certificates)
cert-manager automatically provisions and renews Let's Encrypt certificates:
# Install cert-manager
helm repo add jetstack https://charts.jetstack.io
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: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx# Ingress with TLS and automatic certificate provisioning
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
- www.example.com
secretName: example-tls-cert # cert-manager creates this Secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80Common Nginx Ingress Annotations
metadata:
annotations:
# Rate limiting
nginx.ingress.kubernetes.io/limit-rps: "100"
nginx.ingress.kubernetes.io/limit-connections: "50"
# Request size limit
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# Timeouts
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
# CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://www.example.com"
# Authentication (using oauth2-proxy)
nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://auth.example.com/oauth2/start"
# Canary deployment (route 10% of traffic to new version)
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
# Rewrite URL path
nginx.ingress.kubernetes.io/rewrite-target: /$2AWS Application Load Balancer Controller
On EKS, use the AWS Load Balancer Controller for ALB-native Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-alb-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123:certificate/abc
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80Kubernetes Gateway API (Next Generation)
Gateway API is the successor to Ingress, now GA in Kubernetes 1.28+:
# Gateway (cluster-level, managed by platform team)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: production
spec:
gatewayClassName: nginx
listeners:
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: example-tls-cert
---
# HTTPRoute (namespace-level, managed by app teams)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: production
spec:
parentRefs:
- name: main-gateway
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 80Common Mistakes
- Not setting
ingressClassName— the ingress may be ignored if the cluster has multiple controllers - Forgetting to redirect HTTP to HTTPS (
ssl-redirect: "true") after enabling TLS - Using the wrong
pathType—Exactmatches only that path;Prefixmatches all subpaths - Not installing cert-manager before deploying TLS Ingress with
cert-manager.ioannotations - Creating Ingress resources across namespaces without understanding that each namespace needs its own Ingress or uses a shared gateway
Best Practices
- Use cert-manager with Let's Encrypt for all HTTPS — never manage certificates manually
- Set rate limiting annotations on public-facing Ingresses to prevent abuse
- Use
ssl-redirect: "true"to force HTTPS on all Ingress resources - Monitor Ingress controller metrics (request rate, error rate, latency) with Prometheus annotations
- Migrate to Gateway API for new clusters — it provides better role separation between platform and application teams
Key Takeaways
- Ingress routes external HTTP/HTTPS traffic to internal Services based on hostname and URL path
- A single Ingress controller (Nginx, AWS ALB) handles routing for all services — one cloud load balancer for the whole cluster
- cert-manager with Let's Encrypt automates TLS certificate provisioning and renewal for Ingress resources
- Nginx Ingress annotations enable rate limiting, CORS, authentication, and canary deployments without code changes
- AWS ALB Ingress Controller on EKS integrates natively with ACM certificates, WAF, and target group health checks
- Gateway API (GA in K8s 1.28+) replaces Ingress with better role separation and more expressive routing rules
pathType: Prefixmatches the path and all sub-paths;pathType: Exactmatches only the exact path- Always monitor the Ingress controller itself — it is a single point of failure for all external traffic
Advertisement