NGINX Ingress Controller Reference: Annotations, TLS, Rewrites, Rate Limiting & Canary
NGINX Ingress Controller routes external HTTP/HTTPS traffic into Kubernetes services. It translates Kubernetes Ingress resources into NGINX config — adding path-based routing, SSL termination, authentication, rate limiting, and rewrites via annotations. Distinct from vanilla NGINX config: K8s annotations drive everything.
1. Install & Basic Ingress
Install the controller and create your first Ingress resource
| ingress-nginx (community) | NGINX Inc (nginx/ingress-nginx) | |
|---|---|---|
| Repo | kubernetes/ingress-nginx | nginxinc/kubernetes-ingress |
| Config | Annotations + ConfigMap | VirtualServer CRDs + annotations |
| Most used | Yes — default in most K8s guides | NGINX Plus commercial support |
# Install ingress-nginx (community) with Helm:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.replicaCount=2 --set controller.resources.requests.cpu=100m --set controller.resources.requests.memory=90Mi
# Get the LoadBalancer external IP:
kubectl get svc -n ingress-nginx ingress-nginx-controller
# EXTERNAL-IP: 34.123.45.67 → point your DNS A records here
# Basic Ingress (path-based routing):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: nginx # or use ingressClassName below
spec:
ingressClassName: nginx
tls:
- hosts: [api.example.com]
secretName: api-tls-secret # TLS cert from cert-manager
rules:
- host: api.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service: {name: api-service, port: {number: 8080}}
- path: /
pathType: Prefix
backend:
service: {name: frontend-service, port: {number: 3000}}
2. Path Rewrites & Redirects
Strip path prefixes, redirect HTTP to HTTPS, and custom rewrites
# Rewrite target (strip /api prefix before forwarding):
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: example.com
http:
paths:
- path: /api(/|$)(.*) # capture group $2 = everything after /api
pathType: ImplementationSpecific
# Example: /api/users → /users (prefix stripped)
# /api → / (root)
# Force HTTPS redirect:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true" # default is true when TLS configured
nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # redirect even without TLS config
# Permanent redirect (301) to new domain:
annotations:
nginx.ingress.kubernetes.io/permanent-redirect: https://new.example.com$request_uri
# Redirect to HTTPS with www:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($host = 'example.com') {
return 301 https://www.example.com$request_uri;
}
# Custom error pages:
annotations:
nginx.ingress.kubernetes.io/custom-http-errors: "404,503"
nginx.ingress.kubernetes.io/default-backend: error-pages-service
3. SSL/TLS with cert-manager
Automatic certificate provisioning from Let’s Encrypt
# 1. Install cert-manager (if not already):
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
# 2. Create 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-key
solvers:
- http01:
ingress:
class: nginx # must match ingress controller class
# 3. Annotate Ingress to auto-provision cert:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: [api.example.com]
secretName: api-example-com-tls # cert-manager creates this Secret
# cert-manager automatically:
# 1. Creates ACME challenge Ingress
# 2. Let's Encrypt validates it
# 3. Stores cert in Secret api-example-com-tls
# 4. Renews automatically before expiry
# Check cert status:
kubectl get certificate -n production
kubectl describe certificate api-example-com-tls -n production
4. Rate Limiting & Authentication
Rate limit by IP, basic auth, and OAuth2 proxy integration
# Rate limiting (per client IP): annotations: nginx.ingress.kubernetes.io/limit-rps: "10" # 10 requests/second per IP nginx.ingress.kubernetes.io/limit-connections: "5" # 5 concurrent connections per IP nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" # burst up to 30 req/s # Basic authentication: # 1. Create htpasswd secret: htpasswd -c auth admin # creates 'auth' file with admin user kubectl create secret generic basic-auth --from-file=auth --namespace production # 2. Annotate Ingress: annotations: nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth nginx.ingress.kubernetes.io/auth-realm: "Restricted Area" # IP allowlist (only allow specific IPs): annotations: nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.1.100" # External authentication (OAuth2 Proxy — forward auth to sidecar): annotations: nginx.ingress.kubernetes.io/auth-url: "https://oauth2.example.com/oauth2/auth" nginx.ingress.kubernetes.io/auth-signin: "https://oauth2.example.com/oauth2/start?rd=$escaped_request_uri" nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-User,X-Auth-Email" # CORS headers: 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: "Authorization, Content-Type"
5. Advanced Configuration & Snippets
Global ConfigMap, proxy buffers, custom headers, and upstream keepalive
# Global config via ConfigMap (ingress-nginx namespace):
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
use-gzip: "true"
gzip-level: "6"
proxy-buffer-size: "16k" # increase for large headers (JWT tokens)
proxy-connect-timeout: "10"
proxy-read-timeout: "120"
proxy-send-timeout: "120"
keep-alive: "75"
upstream-keepalive-connections: "100"
log-format-upstream: '{"time":"$time_iso8601","remote_addr":"$remote_addr","status":$status,"request":"$request"}'
# Per-Ingress configuration snippet (raw NGINX config injected):
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Frame-Options: SAMEORIGIN";
more_set_headers "X-Content-Type-Options: nosniff";
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
# Server snippet (add to server block):
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
location /health {
access_log off;
return 200 "ok";
}
# WebSocket support:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Canary ingress (traffic splitting — send 10% to new version):
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% of traffic to this Ingress
# Debug: check generated NGINX config:
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A5 "api.example.com"
Track NGINX Ingress Controller, cert-manager, and Kubernetes releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.
Related: cert-manager Reference | Kubernetes YAML Reference | Nginx Reference | Nginx EOL Tracker
🔍 Free tool: K8s YAML Security Linter — check your nginx Ingress and K8s workload manifests for 12 security misconfigurations.
Founded
2023 in London, UK
Contact
hello@releaserun.com