Kubernetes Ingress: Routing External Traffic
How Ingress, IngressClass, and Ingress controllers route external HTTP and HTTPS traffic to your Kubernetes Services — with host and path rules, TLS termination, and notes on ingress-nginx vs Traefik.
What you'll learn
- ✓When to reach for a Service vs an Ingress
- ✓How IngressClass and Ingress controllers fit together
- ✓Writing host and path rules to route traffic across services
- ✓TLS termination at the edge and where cert-manager fits in
- ✓The differences between ingress-nginx and Traefik in practice
Prerequisites
- •Familiarity with Services and Deployments — see Pods, Deployments, and Services
- •A cluster you can install an ingress controller on (kind, minikube, or a managed cluster)
A Kubernetes Service can expose a workload, but it is not the right tool for a public website. type: LoadBalancer works, yet it gives you one cloud load balancer per service, no HTTP routing, and no TLS without extra work. Ingress is the answer: a single entry point that understands HTTP, terminates TLS, and routes by host and path to whichever Service should handle each request.
Service vs Ingress
A quick refresher with a clear contrast.
Service operates at Layer 4 — TCP and UDP. It gives a stable virtual IP and DNS name, load-balances across Pods, and (with type: LoadBalancer) provisions a cloud load balancer for external traffic. It does not know about hostnames, URL paths, or HTTPS certificates.
Ingress operates at Layer 7 — HTTP and HTTPS. It is a routing rulebook: send api.example.com/* to the api Service, send example.com/blog/* to the blog Service, and terminate TLS for both. One public IP fronts many internal Services.
For internal cluster-to-cluster traffic, Services are perfect. For anything that faces real users on the web, Ingress is almost always what you want.
The three pieces
Ingress is unusual in Kubernetes because the API object alone does nothing. You need three things:
- An Ingress controller — software that actually listens on ports 80 and 443 and routes traffic. ingress-nginx, Traefik, HAProxy, and cloud-native options like AWS Load Balancer Controller are all examples.
- An IngressClass — a Kubernetes object that names the controller and tells Ingress objects which controller to use.
- The Ingress object — your routing rules, referencing an IngressClass.
If you write an Ingress object with no controller installed, nothing happens. The Ingress sits there in pending forever. This trips up beginners constantly.
Installing a controller
For a learning cluster, ingress-nginx is the most common starting point. The official install command:
# Install ingress-nginx via the upstream manifest
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
# Wait for the controller pod to be ready
kubectl get pods -n ingress-nginx
# NAME READY STATUS RESTARTS AGE
# ingress-nginx-controller-7d5b8c8f5d-xq2bf 1/1 Running 0 45s
On a cloud cluster, this also provisions a real external load balancer pointed at the controller’s Service. On minikube or kind, you may need a provider-specific manifest (provider/kind/deploy.yaml) and a port-forward.
Verify the IngressClass is in place:
kubectl get ingressclass
# NAME CONTROLLER PARAMETERS AGE
# nginx k8s.io/ingress-nginx <none> 60s
Your first Ingress
Assume two Services already exist in the default namespace: web on port 80 and api on port 80. The Ingress below sends example.com/* to web and example.com/api/* to api.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Apply it:
kubectl apply -f example-ingress.yaml
# ingress.networking.k8s.io/example-ingress created
kubectl get ingress
# NAME CLASS HOSTS ADDRESS PORTS AGE
# example-ingress nginx example.com 203.0.113.42 80 20s
The ADDRESS is the controller’s external IP. Point your DNS for example.com at it, and HTTP traffic starts flowing through the rules.
pathType matters.
Prefixmatches any path starting with the given prefix;Exactmatches only the exact path. There is alsoImplementationSpecific, which means “ask the controller what it does.” Stick withPrefixorExactuntil you have a specific reason otherwise.
Host-based routing
A single Ingress can route many hostnames to different Services. This is how a small cluster can host a dozen projects on a single load balancer.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop
port:
number: 80
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog
port:
number: 80
The controller sees the Host header on each request and matches it against the rules. Each hostname needs a DNS record pointing at the controller’s IP.
Try it yourself. Stand up two tiny Deployments (try nginx and httpd) with matching Services. Write an Ingress with two host rules pointing at each. Without DNS, you can simulate hostnames with curl --resolve shop.example.com:80:<controller-ip> http://shop.example.com/. Confirm each hostname reaches the right backend.
TLS termination
Real websites need HTTPS. Ingress handles TLS termination: the controller holds the certificate, decrypts incoming HTTPS, and forwards plain HTTP to your Services inside the cluster.
You give it the cert and key as a kubernetes.io/tls Secret:
kubectl create secret tls example-tls \
--cert=fullchain.pem \
--key=privkey.pem
Then reference it in the Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
That works, but managing certificates by hand is a chore. The standard answer is cert-manager.
cert-manager and automatic certificates
cert-manager is a Kubernetes operator that requests certificates from Let’s Encrypt (or another ACME or internal CA) and stores them as TLS Secrets — exactly the shape Ingress expects.
Install it with one Helm command or a manifest, define a ClusterIssuer, then annotate your Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
cert-manager notices the annotation, solves an ACME challenge, fetches a real Let’s Encrypt certificate, and writes it to the example-tls Secret. The controller picks it up automatically and renewals happen on a schedule. The amount of TLS plumbing you have to think about drops to zero.
Popular controllers
A few choices dominate in practice. They all implement the same Ingress API but have different operational personalities.
ingress-nginx. The most widely deployed. Battle-tested, simple, and well-documented. Configuration is largely via annotations on the Ingress object. The right default for most teams.
Traefik. Modern, dashboard-friendly, with strong support for dynamic config. Popular in Docker Swarm origins and homelabs; its Kubernetes integration is solid and works with both the standard Ingress and its own IngressRoute CRD.
HAProxy Ingress. Excellent performance and observability, with deep HAProxy tunability.
Cloud-native controllers. AWS Load Balancer Controller (provisions ALBs), GKE’s GCE Ingress, and Azure Application Gateway Ingress Controller integrate directly with the cloud’s L7 load balancer. You get fewer cluster components but tighter cloud coupling.
You generally want one controller per cluster (or one per environment), with all Ingress objects pointing at it via ingressClassName. Mixing is possible but unusual.
The Gateway API
The Ingress API is intentionally minimal. To do anything advanced — header rewrites, rate limiting, traffic splitting — you reach for controller-specific annotations, and your YAML becomes tied to whichever controller you picked.
The newer Gateway API is Kubernetes’ answer: a richer set of objects (Gateway, HTTPRoute, TLSRoute) that express the same concepts portably. Most controllers now support both. For new projects, learn Ingress first — it is everywhere — but keep an eye on Gateway API for anything more sophisticated.
Try it yourself. Install cert-manager on a cluster with a real DNS name pointing at your controller. Create a ClusterIssuer for Let’s Encrypt staging (so you do not hit rate limits while experimenting). Annotate an Ingress and watch a Certificate and Secret appear automatically. Curl the HTTPS endpoint with -k first to confirm the staging cert, then switch the issuer to production.
Recap
You now know:
- Services handle L4 cluster networking; Ingress handles L7 HTTP routing
- An Ingress is just a rulebook — you need an Ingress controller and an IngressClass to make it work
- Rules combine hosts and paths with
PrefixorExactmatching - TLS termination uses a
kubernetes.io/tlsSecret; cert-manager automates the whole certificate lifecycle - ingress-nginx is a reasonable default; cloud-native controllers tie you closer to the cloud’s L7 LB
- The Gateway API is the future for advanced routing; Ingress is still the right thing to learn first
Once Ingress is in place, all the other Kubernetes patterns — blue/green deploys, canaries, multi-tenant clusters — sit naturally on top of it.
Next steps
Now that traffic can reach your workloads, you probably want to automate getting those workloads into the cluster. The CI/CD post in this series is the natural follow-up.
Useful adjacent reading:
- Deploy to Production with GitHub Actions — for shipping new images automatically
- ConfigMaps and Secrets — for the config side of the workloads behind your Ingress
- Pods, Deployments, and Services — for the layer below
Questions or feedback? Email codeloomdevv@gmail.com.