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.
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=trueAfter install, check the controller and its external IP.
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.
kubectl get ingressclass
# NAME CONTROLLER PARAMETERS AGE
# nginx k8s.io/ingress-nginx <none> 2mDeploy Demo Backends
Spin up two demo services so you can verify routing.
# 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 }]kubectl apply -f apps.yamlPath-Based Routing
Split traffic to different backends by path on a single host.
# 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.
| pathType | Meaning |
|---|---|
Prefix | Prefix match (/a matches /a and /a/x) |
Exact | Exact path match only |
ImplementationSpecific | Left to the controller (regex, etc.) |
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 BHost-Based Virtual Hosting (Domains)
This pattern exposes a different service per subdomain.
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.
# 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.key2) Add a tls block to the Ingress
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 } } }Auto-renewal with cert-manager (recommended for production)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yamlapiVersion: 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.
| Annotation | Purpose |
|---|---|
nginx.ingress.kubernetes.io/rewrite-target | Path rewrite (use regex capture $1) |
nginx.ingress.kubernetes.io/ssl-redirect | HTTP→HTTPS redirect |
nginx.ingress.kubernetes.io/proxy-body-size | Max upload size (default 1m) |
nginx.ingress.kubernetes.io/proxy-read-timeout | Backend response timeout (seconds) |
nginx.ingress.kubernetes.io/auth-type | Authentication such as Basic Auth |
nginx.ingress.kubernetes.io/configuration-snippet | Inject arbitrary Nginx config |
rewrite-target example — strip the /api prefix
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
htpasswd -c auth admin #
kubectl create secret generic basic-auth --from-file=authmetadata:
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
# 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| Symptom | Cause / Check |
|---|---|
| 404 Not Found (nginx) | host/path mismatch or missing ingressClassName |
| 503 Service Unavailable | No backend Pods, or Service selector mismatch |
| TLS certificate not applied | Typo in tls.secretName, or Secret not in the same namespace |
| 502 Bad Gateway | Backend 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
| Item | Key point |
|---|---|
| Ingress resource | Declares host/path → Service routing rules |
| Ingress Controller | The proxy that actually applies the rules (must be installed) |
| ingressClassName | Selects which controller handles the Ingress |
| pathType | Prefix / Exact / ImplementationSpecific |
| TLS | tls block + Secret; in production, auto-renew with cert-manager |
| Behavior control | nginx.ingress.kubernetes.io/* annotations |
| Debugging | describe 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.
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.