Skip to content

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