Skip to main content

Configuring Ingress Controllers with NGINX for Production Traffic

Configure NGINX Ingress Controllers on Kubernetes to route production HTTP and HTTPS traffic with SSL termination, path routing, and rate limiting.

52 Terms

Overview and What You Will Learn

Without an Ingress Controller, exposing services in Kubernetes means creating a separate cloud load balancer for every single service — expensive, unmanageable, and impossible to maintain at scale. This lab walks you through deploying and configuring the NGINX Ingress Controller on a production Kubernetes cluster, setting up path-based and host-based routing, enabling SSL termination, and applying rate limiting to protect your services.

By the end of this guide you will be able to:

  • Deploy the NGINX Ingress Controller using Helm on a production cluster
  • Configure host-based and path-based routing rules for multiple services
  • Terminate SSL using Kubernetes TLS secrets and cert-manager
  • Apply rate limiting and connection throttling annotations to protect APIs
  • Troubleshoot common Ingress misconfiguration issues with real kubectl commands

Why This Matters in Production

Consider Razorpay running dozens of internal microservices — payments, dashboard, settlements, webhooks, and reporting. Without Ingress, each service needs its own cloud load balancer IP, multiplying infrastructure costs and SSL certificate management overhead. A single NGINX Ingress Controller handles all external traffic through one IP, routes it to the correct service based on hostname or path, terminates SSL centrally, and applies security policies uniformly.

At the scale of platforms like PhonePe or CRED, a misconfigured Ingress can mean routing payment traffic to the wrong backend, or leaving an internal admin service accidentally exposed to the public internet. Getting Ingress right is a fundamental production skill.

Core Principles

How traffic flows through an NGINX Ingress Controller:

◈ DIAGRAM
External User Request (https://api.razorpay.in/v1/payments)
│
▼
Cloud Load Balancer (single external IP — provisioned by cloud provider)
│
▼
NGINX Ingress Controller Pod (reads Ingress rules from API server)
│
├─► Host: api.razorpay.in + Path: /v1/payments
│ └─► payments-service:4000
│
├─► Host: api.razorpay.in + Path: /v1/dashboard
│ └─► dashboard-service:8080
│
└─► Host: admin.razorpay.in
└─► admin-service:9000 (internal only)

Key components every engineer must understand:

  • Ingress Controller — the actual NGINX pod that processes routing rules. Must be deployed separately — Kubernetes does not ship with one by default.
  • Ingress Resource — the YAML object that defines the routing rules. Useless without a running controller to read and apply them.
  • IngressClass — tells Kubernetes which controller should process a given Ingress resource. Critical when running multiple controllers in one cluster.
  • Annotations — NGINX-specific configuration applied per Ingress resource to enable SSL redirect, rate limiting, custom timeouts, and more.

Detailed Step-by-Step Practical Lab

Step 1 — Deploy NGINX Ingress Controller Using Helm
Bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Deploy NGINX Ingress Controller into its own namespace
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2 \
--set controller.nodeSelector."kubernetes\.io/os"=linux \
--set controller.service.externalTrafficPolicy=Local
# Verify the controller pods are running
kubectl get pods -n ingress-nginx
# Get the external IP assigned by the cloud load balancer
kubectl get service ingress-nginx-controller -n ingress-nginx
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.96.12.100 34.93.45.201 80:31080/TCP,443:31443/TCP
Remember

The EXTERNAL-IP shown above is what your DNS records must point to. All your domain names (api.razorpay.in, dashboard.razorpay.in) should have A records pointing to this single IP.

Step 2 — Create a Basic Ingress Resource with Path-Based Routing
YAML
# ingress-basic.yaml — route traffic to two backend services
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: razorpay-api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # Force HTTPS
nginx.ingress.kubernetes.io/use-regex: "true" # Enable regex paths
nginx.ingress.kubernetes.io/proxy-body-size: "10m" # Max request body size
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30" # Connection timeout seconds
nginx.ingress.kubernetes.io/proxy-read-timeout: "60" # Read timeout seconds
spec:
ingressClassName: nginx # Must match your installed IngressClass
rules:
- host: api.razorpay.in
http:
paths:
- path: /v1/payments
pathType: Prefix
backend:
service:
name: payments-service
port:
number: 4000
- path: /v1/dashboard
pathType: Prefix
backend:
service:
name: dashboard-service
port:
number: 8080
- path: /v1/webhooks
pathType: Prefix
backend:
service:
name: webhook-service
port:
number: 5000
Bash
kubectl apply -f ingress-basic.yaml
# Verify Ingress was created and has an address
kubectl get ingress -n production
# NAME CLASS HOSTS ADDRESS PORTS AGE
# razorpay-api-ingress nginx api.razorpay.in 34.93.45.201 80, 443 2m
Step 3 — Enable SSL with cert-manager and Let's Encrypt
Bash
# Install cert-manager for automatic SSL certificate provisioning
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
# Verify cert-manager pods are running
kubectl get pods -n cert-manager
YAML
# cluster-issuer.yaml — configure Let's Encrypt production certificate issuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: rahul@devopsnetwork.in # Must be a real monitored email
privateKeySecretRef:
name: letsencrypt-production-key
solvers:
- http01:
ingress:
class: nginx
Bash
kubectl apply -f cluster-issuer.yaml
YAML
# ingress-ssl.yaml — Ingress with automatic SSL certificate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: razorpay-api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-production" # Auto-provision SSL
spec:
ingressClassName: nginx
tls:
- hosts:
- api.razorpay.in
- dashboard.razorpay.in
secretName: razorpay-tls-secret # cert-manager stores the cert here
rules:
- host: api.razorpay.in
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: payments-service
port:
number: 4000
- host: dashboard.razorpay.in
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dashboard-service
port:
number: 8080
Bash
kubectl apply -f ingress-ssl.yaml
# Watch the SSL certificate being issued — takes 60-120 seconds
kubectl get certificate -n production -w
# NAME READY SECRET AGE
# razorpay-tls-secret True razorpay-tls-secret 90s
Tip

If the certificate stays in False state for more than 5 minutes, check the CertificateRequest and Order objects: kubectl describe certificaterequest -n production — it will show exactly which ACME challenge step failed.

Step 4 — Apply Rate Limiting to Protect APIs
YAML
# ingress-ratelimit.yaml — protect the payments API from abuse
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: payments-ingress-protected
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-production"
# Rate limiting — critical for payment APIs
nginx.ingress.kubernetes.io/limit-rps: "20" # Max 20 requests/second per IP
nginx.ingress.kubernetes.io/limit-connections: "10" # Max 10 concurrent connections per IP
nginx.ingress.kubernetes.io/limit-burst-multiplier: "5" # Allow bursts up to 100 rps briefly
# Security headers
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: DENY";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
spec:
ingressClassName: nginx
tls:
- hosts:
- api.razorpay.in
secretName: razorpay-tls-secret
rules:
- host: api.razorpay.in
http:
paths:
- path: /v1/payments
pathType: Prefix
backend:
service:
name: payments-service
port:
number: 4000
Step 5 — Configure Host-Based Routing for Multiple Domains
YAML
# ingress-multidomain.yaml — separate domains to separate services
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: razorpay-multidomain-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.razorpay.in
- admin.razorpay.in
- webhooks.razorpay.in
secretName: razorpay-multidomain-tls
rules:
- host: api.razorpay.in # Public API — internet facing
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: public-api-service
port:
number: 4000
- host: admin.razorpay.in # Admin panel — restrict via firewall rules
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 9000
- host: webhooks.razorpay.in # Webhook receiver — high timeout needed
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webhook-service
port:
number: 5000
Security

Never expose admin dashboards through a public Ingress without IP whitelisting. Add nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,YOUR_OFFICE_IP/32" to the admin Ingress annotation block to restrict access to known IPs only.

Step 6 — Verify and Troubleshoot the Ingress
Bash
# Check Ingress resource status and backend addresses
kubectl describe ingress razorpay-api-ingress -n production
# Check NGINX Ingress Controller logs for routing errors
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/name=ingress-nginx \
--tail=100 -f
# Test routing from inside the cluster using a debug pod
kubectl run curl-test --image=curlimages/curl -it --rm -n production -- sh
# Inside the debug pod — test internal routing
curl -H "Host: api.razorpay.in" http://10.96.12.100/v1/payments

Production Best Practices & Common Pitfalls

  • Always run at least 2 replicas of the Ingress Controller with a PodDisruptionBudget. A single controller pod is a single point of failure for all cluster traffic.
  • Use externalTrafficPolicy: Local on the Ingress Controller service to preserve the real client IP in access logs — essential for rate limiting and security auditing.
  • Set resource requests and limits on the Ingress Controller pods. An unthrottled controller under traffic spike can OOMKill and drop all traffic simultaneously.
  • Always use IngressClass explicitly. Relying on the legacy kubernetes.io/ingress.class annotation causes unpredictable behaviour in newer Kubernetes versions.
  • Separate Ingress resources per service rather than one monolithic Ingress. Smaller resources are easier to audit, roll back, and debug independently.
Common Mistake

Creating an Ingress resource without installing an Ingress Controller first. The Ingress object will be accepted by the API server with no errors but traffic will never be routed. Always confirm the controller is running with kubectl get pods -n ingress-nginx before creating Ingress resources.

Quick Reference & Troubleshooting Commands

Command Purpose
kubectl get ingress -n <ns> List all Ingress resources and their addresses
kubectl describe ingress <name> -n <ns> Full Ingress config with backend endpoint status
kubectl get ingressclass List available IngressClass controllers in cluster
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx NGINX controller logs for routing errors
kubectl get certificate -n <ns> SSL certificate issuance status
kubectl describe certificaterequest -n <ns> Debug failed SSL certificate provisioning
kubectl get events -n ingress-nginx --sort-by='.lastTimestamp' Ingress controller event timeline
helm upgrade ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx Upgrade NGINX Ingress Controller version
kubectl exec -it <nginx-pod> -n ingress-nginx -- nginx -t Validate generated NGINX config inside controller

Resources

Velero vs etcd Snapshot: Not a Real Choice

Velero vs etcd Snapshot: Not a Real Choice

Velero and etcd snapshots protect different layers of a cluster, not the same thing. Here's what each covers and why production DR needs both.

5 min read•Aug 2026
Istio Ambient vs Linkerd in 2026

Istio Ambient vs Linkerd in 2026

Istio Ambient killed the sidecar-tax argument. The real 2026 decision is waypoint topology and Buoyant's licensing shift, not features vs simplicity.

5 min read•Aug 2026
Nginx Ingress vs Traefik vs Gateway API in 2026

Nginx Ingress vs Traefik vs Gateway API in 2026

Ingress-nginx retired in March 2026. Here's how Traefik and the Gateway API actually compare as replacements — and why "just swap it" is the wrong frame.

5 min read•Aug 2026
OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno compared for 2026 - Rego vs YAML, mutation maturity, operational overhead, and which policy engine fits your cluster.

5 min read•Aug 2026
Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize compared for 2026 - templating vs patching, Helm 4's new features, and why most production teams end up running both.

5 min read•Aug 2026
Prometheus vs Datadog vs New Relic: Real Costs

Prometheus vs Datadog vs New Relic: Real Costs

Prometheus, Datadog, and New Relic compared for 2026 - real pricing at scale, hidden cost drivers, and which fits a Kubernetes-heavy stack.

5 min read•Aug 2026
Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter compared for EKS in 2026 - provisioning speed, bin-packing, cloud support, and when each is the right default.

5 min read•Aug 2026
GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE, EKS, and AKS compared for 2026 - control plane pricing, Autopilot vs Karpenter vs Node Auto Provisioning, and which platform actually fits your team.

5 min read•Aug 2026
K3s vs K8s vs MicroK8s in 2026

K3s vs K8s vs MicroK8s in 2026

K3s, full Kubernetes, and MicroK8s compared for 2026 - resource footprint, production readiness, and which fits edge, homelab, or cloud workloads.

5 min read•Aug 2026
Canary Deployments with Argo Rollouts & Flagger

Canary Deployments with Argo Rollouts & Flagger

Ship to 5% of users first and auto-rollback in minutes — a hands-on guide to canary deployments with Argo Rollouts and Flagger on Kubernetes.

5 min read•Jun 2026
OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry unifies metrics, logs, and traces under one open standard — how it works, what it replaces, and how to instrument a service in 20 minutes.

5 min read•Jun 2026
Kubernetes Cost Optimization Without Breaking SLOs

Kubernetes Cost Optimization Without Breaking SLOs

Average Kubernetes CPU utilization across production clusters is 8%. Here is the complete 2026 playbook for cutting cloud spend without touching your SLOs.

5 min read•Jun 2026
ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD and FluxCD are the two dominant GitOps engines for Kubernetes in 2026 — this breakdown tells you exactly which one to pick and why.

10 min read•Jun 2026

Explore More in Kubernetes Networking and Traffic Management

All 6 Topics

Frequently Asked Questions

Is Configuring Ingress Controllers with NGINX for Production Traffic free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Configuring Ingress Controllers with NGINX for Production Traffic topic cover?

Configure NGINX Ingress Controllers on Kubernetes to route production HTTP and HTTPS traffic with SSL termination, path routing, and rate limiting.