Kubernetes Ingress Controllers and Routing Rules â Complete Guide with NGINX and TLS
In this tutorial, you'll learn about Kubernetes Ingress Controllers and Routing Rules. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Kubernetes Ingress is an API object that manages external access to cluster services through HTTP and HTTPS routing rules defined by hostnames and paths, with TLS termination and Load Balancing built in.
What You'll Learn
Why It Matters
A Service of type LoadBalancer provisions one cloud load balancer per service, which becomes expensive and hard to manage as you scale to dozens of Microservices. Ingress collapses all external routing into a single entry point, supports TLS termination, path-based and host-based routing, Rate Limiting, and integrates with cert-manager for automatic certificate provisioning. Without Ingress, managing external traffic at scale is inefficient and insecure.
Real-World Use
DodaTech routes traffic to Durga Antivirus Pro's API, web dashboard, and status pages through a single NGINX Ingress Controller that terminates TLS, routes by hostname, enforces rate limits per tenant, and logs all requests to the centralized ELK Stack.
flowchart TD
A["Internet"] --> B["DNS: app.dodatech.com"]
B --> C["Load Balancer (TCP/443)"]
C --> D["Ingress Controller (NGINX)"]
D -->|"Host: api.example.com"| E["Ingress Rule: /v1/*"]
D -->|"Host: app.example.com"| F["Ingress Rule: /dashboard/*"]
D -->|"Host: status.example.com"| G["Ingress Rule: /healthz"]
E --> H["Service: api-svc:8080"]
F --> I["Service: web-svc:80"]
G --> J["Service: status-svc:9090"]
H --> K["Pod: API v2"]
I --> L["Pod: Frontend"]
J --> M["Pod: Status Page"]
style D fill:#269539,color:#fff
style E fill:#326CE5,color:#fff
style F fill:#326CE5,color:#fff
style G fill:#326CE5,color:#fff
Prerequisites: Working Kubernetes cluster, understanding of Services and Deployments, and a domain name pointing to your cluster's load balancer IP.
Ingress vs Service Types
Before writing Ingress rules, understand where Ingress fits in the Kubernetes networking stack.
| Exposure Method | Layer | Use Case | Cost | TLS |
|---|---|---|---|---|
| ClusterIP | L4 | Internal-only services | Free | No |
| NodePort | L4 | Development, direct node access | Free | Manual |
| LoadBalancer | L4 | Production single-service exposure | Per LB | Manual |
| Ingress | L7 | Multi-service routing, TLS, rules | One LB for all | Automatic with cert-manager |
Installing an Ingress Controller
The Ingress resource does nothing without a controller. NGINX Ingress Controller is the most widely adopted option for Kubernetes.
# Install NGINX Ingress Controller using 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.service.type=LoadBalancer
# Expected output:
# NAME: ingress-nginx
# LAST DEPLOYED: ...
# NAMESPACE: ingress-nginx
# STATUS: deployed
Expected behavior: A LoadBalancer service is provisioned, and the NGINX controller Pod runs in the ingress-nginx namespace. The controller watches for Ingress resources and configures NGINX automatically.
# Verify the controller is running
kubectl get pods -n ingress-nginx
# Expected output:
# NAME READY STATUS RESTARTS AGE
# ingress-nginx-controller-xxxxxxxxx-yyyyy 1/1 Running 0 2m
# Get the external IP
kubectl get svc -n ingress-nginx
# Expected output:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.0.123.45 X.X.X.X 80:30080/TCP,443:30443/TCP
# ingress-nginx-controller-admission ClusterIP 10.0.123.46 <none> 443/TCP
Basic Host-Based Routing
Route traffic to different Services based on the HTTP Host header.
# ingress-basic.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.dodatech.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- host: app.dodatech.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Expected behavior: Requests arriving with Host: api.dodatech.com route to api-service:8080. Requests with Host: app.dodatech.com route to web-service:80. An Ingress without a host field matches all traffic, acting as a default backend.
# Apply the Ingress
kubectl apply -f ingress-basic.yaml
# Expected output:
# ingress.networking.k8s.io/multi-host-ingress created
# Check the Ingress details
kubectl describe ingress multi-host-ingress
# Expected output shows the routing rules and the address of the Ingress Controller:
# Rules:
# Host Path Backends
# ---- ---- --------
# api.dodatech.com / api-service:8080
# app.dodatech.com / web-service:80
TLS Termination
Secure your routes with TLS by referencing a Secret that contains the certificate and key.
# tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: dodatech-tls
type: kubernetes.io/tls
data:
# base64-encoded cert and key (shown as placeholders)
tls.crt: <base64-cert>
tls.key: <base64-key>
# ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.dodatech.com
secretName: dodatech-tls
rules:
- host: api.dodatech.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Expected behavior: The Ingress Controller terminates TLS using the certificate in dodatech-tls. When using cert-manager with the annotation cert-manager.io/cluster-issuer, certificates are automatically requested and renewed from Let's Encrypt.
# Create the TLS secret
kubectl create secret tls dodatech-tls \
--cert=fullchain.pem --key=privkey.pem
# Expected output:
# secret/dodatech-tls created
# Verify TLS is active
curl -I https://api.dodatech.com
# Expected output:
# HTTP/2 200
# ...
Path-Based Routing with Rewrites
Route different URL paths to different backend services, optionally rewriting the path before forwarding.
# ingress-path.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: app.dodatech.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 8080
- path: /web(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: web-service
port:
number: 80
- path: /(.*)
pathType: ImplementationSpecific
backend:
service:
name: static-service
port:
number: 8080
Expected behavior: A request to app.dodatech.com/api/users is rewritten to /users and forwarded to api-service:8080. Requests to /web/dashboard become /dashboard and go to web-service:80. Everything else falls through to static-service.
Common Errors
No Ingress Controller installed: Creating an Ingress resource without a running controller results in no routing. The resource exists but nothing processes it. Always verify the controller Pod is running.
Missing ingressClassName: In clusters with multiple Ingress Controllers (NGINX, Traefik, HAProxy), the
ingressClassNamefield tells Kubernetes which controller should Process the rule. Without it, the default IngressClass is used or the rule is ignored.TLS Secret in wrong namespace: Ingress resources can only reference Secrets in the same namespace. A cross-namespace reference causes the controller to fail with "TLS handshake error" and the route falls back to HTTP.
Path rewrite not matching expectations: Without the
rewrite-targetannotation, the full original path is forwarded. With regex capture groups, the rewrite must reference the correct group index. A request to/api/userswithrewrite-target: /$1and pattern/api/(.*)correctly forwards/users, but a wrong group index forwards an empty path.Backend Service port mismatch: The Ingress port number must match the Service's target port. If
api-serviceexposes port80but the ServicetargetPortis8080, and the Ingress references port8080, routing fails. Use the same port name in both the Service and Ingress.
Practice Questions
What is the difference between an Ingress and a LoadBalancer Service? Answer: A LoadBalancer Service provisions one cloud load balancer per Service. An Ingress uses a single load balancer (shared by the Ingress Controller) to route traffic to multiple Services based on hostnames and paths, making it more cost-effective and feature-rich for L7 routing.
How does the Ingress Controller know which Ingress resources to Process? Answer: The Ingress Controller watches the Kubernetes API for Ingress resources matching its
ingressClassName. It reads the rules and dynamically configures the underlying proxy (NGINX, Traefik, etc.) to enforce the routing rules.What happens if no hostname matches in an Ingress rule? Answer: If an Ingress rule has no
hostfield, it acts as a default backend catching all traffic that does not match other rules. If no default backend is defined, the Ingress Controller returns HTTP 404.How does cert-manager integrate with Ingress for automatic TLS? Answer: The annotation
cert-manager.io/cluster-issuer: letsencrypt-prodtells cert-manager to watch Ingress resources. When a TLS block references a Secret that does not exist, cert-manager creates the certificate using the specified issuer and stores it in the Secret, which the Ingress Controller then uses.
Challenge
Deploy three Microservices (users-api, orders-api, web-frontend) behind a single Ingress. Use path-based routing: /api/users/* to users-api, /api/orders/* to orders-api, and / to web-frontend. Add TLS termination with a self-signed certificate (or cert-manager if available). Configure Rate Limiting using NGINX annotations (nginx.ingress.Kubernetes.io/limit-rps) to allow 100 requests per second per client IP. Verify the routing rules with curl.
Mini Project
Set up a production-grade Ingress stack: deploy the NGINX Ingress Controller, create three Services (api, web, admin), write an Ingress with host-based routing for api.dodatech.com and app.dodatech.com, add path-based routing under app.dodatech.com/web/* and app.dodatech.com/admin/*, configure TLS using cert-manager with Let's Encrypt staging issuer, add annotations for Rate Limiting, CORS headers, and custom error pages. Test each route with curl and verify TLS termination. Document the setup in a Helm values file for repeatable deployments.
Related Resources
| Resource | Description |
|---|---|
| Kubernetes Services | Foundational networking concepts |
| Helm Charts | Deploying Ingress with Helm |
| Container Security | Securing container traffic |
| GitOps with ArgoCD | Managing Ingress with GitOps |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro