Set Up NGINX Ingress
Set up an NGINX Ingress Controller in this Kubernetes tutorial — practical, step-by-step, lesson 40.
Focus: set up an nginx ingress controller
You've mastered Services and Deployments, but exposing your app to the real world still feels like a chore — every new service needs its own LoadBalancer, ports get tangled, and you're burning cloud credits on each external IP. An Ingress Controller, specifically NGINX Ingress, solves this by giving you a single, intelligent entry point that routes traffic based on hostnames and paths, saving costs and centralizing TLS termination.
The problem this lesson solves
In a default Kubernetes cluster, every Service of type LoadBalancer provisions a separate cloud load balancer. That means for three microservices, you pay for three external IPs and three sets of networking rules. Worse, you can't easily do path-based routing (e.g., /api → one service, /web → another). An Ingress Controller eliminates this waste by running a single reverse proxy (NGINX) inside your cluster that watches Ingress resources and configures itself automatically.
Core concept / mental model
Think of an Ingress Controller as a smart traffic cop at the entrance of a busy intersection. The Ingress resource is a set of rule signs the cop reads — "If the car says /api, send it to lane A; if it says /admin, send it to lane B." The Ingress Controller (the cop) is the actual NGINX Pod that listens on ports 80 and 443, examines each incoming request, and forwards it to the correct Service. You don't configure NGINX directly; you write YAML rules, and the controller translates them into NGINX config automatically.
Kubernetes does NOT ship with a built-in Ingress Controller. You must deploy one yourself. The
Ingressresource is just a declaration — without a controller, it does nothing. NGINX Ingress is the most popular choice, but alternatives like Traefik and HAProxy also exist.
How it works step by step
-
Deploy the NGINX Ingress Controller — Using a
Deploymentor Helm chart, you create a Pod that runs the official NGINX Ingress image. This Pod is usually exposed via aLoadBalancerService (orNodePortif testing locally) to receive external traffic. -
Create an Ingress resource — You define an
IngressYAML that specifies hostnames, paths, and backend Services. For example,myapp.com/api→api-service:8080. -
Controller watches for changes — The NGINX Ingress Pod runs a controller loop that watches the Kubernetes API for new or updated
Ingressobjects. Whenever a change occurs, it regenerates the NGINX configuration file and reloads the NGINX process without downtime. -
Traffic flows: Outside request → cloud load balancer → NGINX Pod → routing rules → cluster IP Service → Pod.
Hands-on walkthrough
Let's set up an NGINX Ingress Controller on a local cluster (Minikube or kind) and route traffic to two demo services.
Step 1: Deploy a sample backend service
# demo-app.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-service
spec:
selector:
app: hello
ports:
- port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deployment
spec:
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: hashicorp/http-echo
args:
- "-text=Hello from the backend"
ports:
- containerPort: 5678
Note on ports: NGINX Ingress routes to a Service's
targetPort, not the Pod's default port. Adjust the ServiceportandtargetPortas needed.
Apply with: kubectl apply -f demo-app.yaml.
Step 2: Install NGINX Ingress Controller
For Minikube, you can enable the NGINX Ingress addon directly:
minikube addons enable ingress
For a standard cluster (e.g., kind or cloud), use the official manifest:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
Wait for the controller to be ready:
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
Step 3: Create an Ingress resource
# ingress-demo.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.local
http:
paths:
- path: /hello
pathType: Prefix
backend:
service:
name: hello-service
port:
number: 80
Apply it: kubectl apply -f ingress-demo.yaml.
Step 4: Test the routing
If using Minikube, get the ingress IP: minikube ip. Add to /etc/hosts:
192.168.49.2 myapp.local
Then curl:
curl http://myapp.local/hello
Expected output:
Hello from the backend
Pro tip: Use
kubectl describe ingress demo-ingressto see the current rules and any errors in the Events section.
Compare options / when to choose what
| Feature | NGINX Ingress | Traefik | HAProxy Ingress |
|---|---|---|---|
| Performance | High (C-based) | Medium (Go) | Very High (C-based) |
| Configuration reload | Hot reload using SIGHUP | Dynamic (no reload) | Dynamic via HAProxy API |
| Built-in dashboard | No (separate Prometheus metrics) | Yes (web UI) | No (metrics only) |
| Helm chart maturity | Very stable (ingress-nginx) | Stable (traefik) | Mature but smaller community |
| Learning curve | Moderate | Low (lots of automagic) | Moderate |
| Ideal for | Production, large teams, complex routing | Smaller teams, quick setup, TLS automation | High-throughput, low-latency |
If you need a battle-tested, community-backed solution with deep Kubernetes integration, NGINX Ingress wins. For edge deployments where you want a GUI, Traefik is fine. For extreme loads (10k+ requests/second) with sensitive latency, HAProxy Ingress is worth a look.
Troubleshooting & edge cases
"404 Not Found" after deployment
Cause: Ingress rules don't match. The host field is required — without it, the rule is ignored unless you specify host: "" or use a default backend.
Fix: Ensure your request's Host header matches the host in the rule. Use curl -H "Host: myapp.local" http://<ingress-ip> or fix /etc/hosts.
Controller pod is CrashLoopBackOff
Cause: Port conflicts or missing RBAC permissions. The controller needs ClusterRole bindings to watch Ingresses.
Fix: Check the pod logs: kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx. Often it's a missing --publish-service flag or firewall blocking the port.
Paths are not rewriting (rewrite-target issue)
Cause: Without the rewrite-target annotation, the full path is sent to the backend. For example, /hello gets forwarded as /hello rather than /.
Fix: Add nginx.ingress.kubernetes.io/rewrite-target: / in the metadata annotations of the Ingress resource.
SSL/TLS certificate not showing
Cause: The TLS secret must be in the same namespace as the Ingress resource. Secret names are case-sensitive.
Fix: Verify the secret exists: kubectl get secret -n <namespace>. If missing, create it: kubectl create secret tls my-tls --cert=cert.pem --key=key.pem.
What you learned & what's next
You now understand how to set up an NGINX Ingress Controller: the core mental model of Ingress resources vs. controller, step-by-step deployment, routing traffic with path-based rules, and troubleshooting common pitfalls. You can connect this to your existing workloads and reduce cloud costs by centralizing ingress.
Next lesson: Securing Ingress with TLS/SSL — you'll learn to attach TLS certificates, enforce HTTPS redirects, and automate certificate renewal with cert-manager.
Practice recap
Deploy a second sample app (e.g., an nginx:alpine image) and create a second Ingress rule on a different path, like /v2. Then modify the existing Ingress to route /v1 to the first app and /v2 to the second. Verify each path returns the correct content. This solidifies path-based routing before you move to TLS.
Common mistakes
- Forgetting to enable the ingress addon or deploy the controller — without it, Ingress resources do nothing and you'll see no error.
- Omitting the
ingressClassName: nginxfield (or the deprecatedkubernetes.io/ingress.classannotation) — the controller ignores rules that don't match its class. - Using a NodePort service for the controller but forgetting to open the node port on the firewall — test with
kubectl port-forwardfirst. - Mixing
spec.rules.hostandspec.tls.hosts— they must match, or TLS won't apply.
Variations
- Use Helm to install NGINX Ingress:
helm install nginx-ingress ingress-nginx/ingress-nginx— easier to manage upgrades and custom values. - For bare-metal or on-prem clusters, consider a MetalLB load balancer to provide IPs to the controller's Service.
- Try the "nginx-ingress" community project (k8s.io/ingress-nginx) vs. NGINX's own
nginxinc/kubernetes-ingress— the former is more widely adopted.
Real-world use cases
- A SaaS platform routing multiple customer domains (e.g., customer1.example.com and customer2.example.com) to separate microservice deployments using host-based rules.
- A microservices e-commerce app where
/api/checkoutgoes to a checkout service,/api/productsto a product service, and/static/*to an asset server — all through one Ingress. - A staging environment with a single public IP that routes
staging.myapp.comto a blue deployment andcanary.myapp.comto a green deployment for A/B testing.
Key takeaways
- An Ingress Controller (like NGINX) is mandatory for
Ingressresources to work — it is not built into Kubernetes. - NGINX Ingress automatically watches for Ingress YAML changes and reloads its configuration without downtime.
- Host- and path-based routing allow you to fan out traffic from a single IP to many services, reducing cloud costs.
- The
rewrite-targetannotation is essential when you want to strip path prefixes before forwarding to the backend. - Always verify your Ingress rules with
kubectl describe ingressand check the controller logs for configuration errors. - For local development,
minikube addons enable ingressis the fastest way to get started.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.