Skip to content

Kubernetes Ingress Controllers and Routing Rules — Complete Guide with NGINX and TLS

DodaTech Updated 2026-06-22 7 min read

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
â„šī¸ Info

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

  1. 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.

  2. Missing ingressClassName: In clusters with multiple Ingress Controllers (NGINX, Traefik, HAProxy), the ingressClassName field tells Kubernetes which controller should Process the rule. Without it, the default IngressClass is used or the rule is ignored.

  3. 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.

  4. Path rewrite not matching expectations: Without the rewrite-target annotation, the full original path is forwarded. With regex capture groups, the rewrite must reference the correct group index. A request to /api/users with rewrite-target: /$1 and pattern /api/(.*) correctly forwards /users, but a wrong group index forwards an empty path.

  5. Backend Service port mismatch: The Ingress port number must match the Service's target port. If api-service exposes port 80 but the Service targetPort is 8080, and the Ingress references port 8080, routing fails. Use the same port name in both the Service and Ingress.

Practice Questions

  1. 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.

  2. 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.

  3. What happens if no hostname matches in an Ingress rule? Answer: If an Ingress rule has no host field, 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.

  4. How does cert-manager integrate with Ingress for automatic TLS? Answer: The annotation cert-manager.io/cluster-issuer: letsencrypt-prod tells 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.

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