Home/Engineer/Docker / Containers/Complete Kubernetes Ingress …
Docker / ContainersAdvancedlinuxkubernetesingressnginx

Complete Kubernetes Ingress Guide — Routing External Traffic with the Nginx Ingress Controller

Install the Nginx Ingress Controller and learn practical Ingress setup step by step: host- and path-based routing, TLS termination, rewrites, and authentication.

Contents
  1. What Is Kubernetes Ingress?
  2. Installing the Nginx Ingress Controller
  3. Deploy Demo Backends
  4. Path-Based Routing
  5. Host-Based Virtual Hosting (Domains)
  6. TLS Termination (HTTPS)
  7. Common Nginx Annotations
  8. Troubleshooting
  9. Summary

What Is Kubernetes Ingress?

Ingress is a set of L7 rules that route HTTP/HTTPS traffic from outside the cluster to internal Services. Creating a type: LoadBalancer Service for every app spins up another cloud load balancer each time and drives up cost. Ingress, by contrast, uses a single entry point and branches traffic to multiple Services based on hostname and path.

  • Ingress resource: Declares only which host/path should go to which Service
  • Ingress Controller: The reverse proxy that actually implements those rules (Nginx, HAProxy, Traefik, and so on)
  • If you create an Ingress resource without a Controller, nothing happens.

An Ingress object is only a spec. To actually handle traffic you must install an Ingress Controller separately. Miss this and you fall into the classic trap: "I created an Ingress but nothing connects."


Installing the Nginx Ingress Controller

Install the most widely used controller, ingress-nginx (the official Kubernetes project), with Helm.

Bash
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 \
  --set controller.metrics.enabled=true

After install, check the controller and its external IP.

Bash
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx ingress-nginx-controller
# EXTERNAL-IP IP/

On-premises (bare metal), a LoadBalancer IP will not be assigned, so use MetalLB as well or install with controller.service.type=NodePort.

Check IngressClass

On recent Kubernetes, you specify which controller handles an Ingress with ingressClassName.

Bash
kubectl get ingressclass
# NAME    CONTROLLER             PARAMETERS   AGE
# nginx   k8s.io/ingress-nginx   <none>       2m

Deploy Demo Backends

Spin up two demo services so you can verify routing.

YAML
# apps.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-a
spec:
  replicas: 2
  selector: { matchLabels: { app: web-a } }
  template:
    metadata: { labels: { app: web-a } }
    spec:
      containers:
      - name: web
        image: hashicorp/http-echo
        args: ["-text=Hello from A", "-listen=:8080"]
        ports: [{ containerPort: 8080 }]
---
apiVersion: v1
kind: Service
metadata:
  name: web-a
spec:
  selector: { app: web-a }
  ports: [{ port: 80, targetPort: 8080 }]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-b
spec:
  replicas: 2
  selector: { matchLabels: { app: web-b } }
  template:
    metadata: { labels: { app: web-b } }
    spec:
      containers:
      - name: web
        image: hashicorp/http-echo
        args: ["-text=Hello from B", "-listen=:8080"]
        ports: [{ containerPort: 8080 }]
---
apiVersion: v1
kind: Service
metadata:
  name: web-b
spec:
  selector: { app: web-b }
  ports: [{ port: 80, targetPort: 8080 }]
Bash
kubectl apply -f apps.yaml

Path-Based Routing

Split traffic to different backends by path on a single host.

YAML
# ingress-path.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: demo.example.com
    http:
      paths:
      - path: /a
        pathType: Prefix
        backend:
          service:
            name: web-a
            port: { number: 80 }
      - path: /b
        pathType: Prefix
        backend:
          service:
            name: web-b
            port: { number: 80 }

There are three pathType values.

pathTypeMeaning
PrefixPrefix match (/a matches /a and /a/x)
ExactExact path match only
ImplementationSpecificLeft to the controller (regex, etc.)
Bash
kubectl apply -f ingress-path.yaml
# /etc/hosts or DNS demo.example.com → EXTERNAL-IP after
curl http://demo.example.com/a   # Hello from A
curl http://demo.example.com/b   # Hello from B

Host-Based Virtual Hosting (Domains)

This pattern exposes a different service per subdomain.

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: a.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: { service: { name: web-a, port: { number: 80 } } }
  - host: b.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: { service: { name: web-b, port: { number: 80 } } }

TLS Termination (HTTPS)

When Ingress terminates TLS, backends speak plain HTTP while only HTTPS is exposed externally.

1) Store the certificate as a Secret

Use a self-signed cert for testing; for production, prefer cert-manager + Let's Encrypt.

Bash
# certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=demo.example.com/O=demo"

kubectl create secret tls demo-tls --cert=tls.crt --key=tls.key

2) Add a tls block to the Ingress

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  annotations:
 nginx.ingress.kubernetes.io/ssl-redirect: "true" # HTTP→HTTPS
spec:
  ingressClassName: nginx
  tls:
  - hosts: [demo.example.com]
    secretName: demo-tls
  rules:
  - host: demo.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend: { service: { name: web-a, port: { number: 80 } } }
Bash
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
YAML
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 }
    solvers:
    - http01:
        ingress: { ingressClassName: nginx }

Add only the cert-manager.io/cluster-issuer: letsencrypt-prod annotation to the Ingress and certificates are issued and renewed automatically.


Common Nginx Annotations

Fine-tune ingress-nginx behavior with annotations.

AnnotationPurpose
nginx.ingress.kubernetes.io/rewrite-targetPath rewrite (use regex capture $1)
nginx.ingress.kubernetes.io/ssl-redirectHTTP→HTTPS redirect
nginx.ingress.kubernetes.io/proxy-body-sizeMax upload size (default 1m)
nginx.ingress.kubernetes.io/proxy-read-timeoutBackend response timeout (seconds)
nginx.ingress.kubernetes.io/auth-typeAuthentication such as Basic Auth
nginx.ingress.kubernetes.io/configuration-snippetInject arbitrary Nginx config

rewrite-target example — strip the /api prefix

YAML
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: demo.example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend: { service: { name: web-a, port: { number: 80 } } }

A request to /api/users is forwarded to the backend as /users.

Apply Basic Auth

Bash
htpasswd -c auth admin #
kubectl create secret generic basic-auth --from-file=auth
YAML
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

Troubleshooting

Bash
# check
kubectl describe ingress app-ingress

# — 502/503, certificate
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller -f

# nginx.conf
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf
SymptomCause / Check
404 Not Found (nginx)host/path mismatch or missing ingressClassName
503 Service UnavailableNo backend Pods, or Service selector mismatch
TLS certificate not appliedTypo in tls.secretName, or Secret not in the same namespace
502 Bad GatewayBackend port (targetPort) mismatch, or container not running

The Ingress resource and the backend Service must be in the same namespace. Cross-namespace routing is not possible with the base Ingress spec; you need a workaround such as an ExternalName Service.


Summary

ItemKey point
Ingress resourceDeclares host/path → Service routing rules
Ingress ControllerThe proxy that actually applies the rules (must be installed)
ingressClassNameSelects which controller handles the Ingress
pathTypePrefix / Exact / ImplementationSpecific
TLStls block + Secret; in production, auto-renew with cert-manager
Behavior controlnginx.ingress.kubernetes.io/* annotations
Debuggingdescribe ingress + controller logs

The core value of Ingress is a single entry point that cuts cost and centralizes L7 routing, TLS, and authentication. In production, automate certificates with cert-manager and always tune real-world constraints such as timeouts and body size via annotations.

#kubernetes#ingress#nginx#tls#routing
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Questions & answers

Ask a question about this guide. We'll review and reply.