Skip to main content
Kubernetes·9 min read

Kubernetes Ingress Not Working? Nginx Setup, TLS & Path Routing Troubleshooting

Deploy and configure the Nginx Ingress Controller in Kubernetes. Covers installation, TLS certificates with cert-manager, path-based and host-based routing, and production annotations.

DT

DevOps Engineer & Technical Writer

The Problem

You have multiple services running in Kubernetes. Each needs external access, but creating a LoadBalancer service for each one means paying for multiple cloud load balancers and managing separate DNS entries. You need a single entry point that routes traffic to different services based on hostname or URL path, with centralized TLS termination.

KUBERNETES INGRESS NGINX TRAFFIC FLOW EXTERNAL Traffic HTTP/HTTPS CLOUD Load Balancer L4 / NLB Ingress Controller (nginx) TLS termination Host/path routing Rate limiting Routes by rules app.example.com Service A 3 pods api.example.com Service B 2 pods /admin path Service C 1 pod Pods

Kubernetes Ingress with the Nginx controller gives you exactly this — one load balancer, multiple services, automatic TLS.

Installing the Nginx Ingress Controller

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.replicaCount=2 \

--set controller.resources.requests.cpu=100m \

--set controller.resources.requests.memory=90Mi

kubectl get pods -n ingress-nginx

kubectl get svc -n ingress-nginx

Using kubectl (manifest-based)

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml

kubectl wait --namespace ingress-nginx \

--for=condition=ready pod \

--selector=app.kubernetes.io/component=controller \

--timeout=120s

Get the external IP

kubectl get svc ingress-nginx-controller -n ingress-nginx

Point your DNS records to this IP/hostname.

Basic Ingress Resource

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: app-ingress

namespace: production

annotations:

nginx.ingress.kubernetes.io/rewrite-target: /

spec:

ingressClassName: nginx

rules:

- host: app.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: frontend-service

port:

number: 80

kubectl apply -f ingress-basic.yaml

kubectl get ingress -n production

Host-Based Routing

Route different domains to different services:

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: multi-host-ingress

namespace: production

annotations:

nginx.ingress.kubernetes.io/proxy-body-size: "50m"

spec:

ingressClassName: nginx

rules:

- host: app.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: frontend-service

port:

number: 80

- host: api.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: api-service

port:

number: 8080

- host: admin.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: admin-service

port:

number: 3000

Path-Based Routing

Route URL paths to different services (microservices pattern):

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: path-based-ingress

namespace: production

annotations:

nginx.ingress.kubernetes.io/use-regex: "true"

spec:

ingressClassName: nginx

rules:

- host: app.example.com

http:

paths:

- path: /api/v1/users

pathType: Prefix

backend:

service:

name: user-service

port:

number: 8080

- path: /api/v1/orders

pathType: Prefix

backend:

service:

name: order-service

port:

number: 8080

- path: /api/v1/payments

pathType: Prefix

backend:

service:

name: payment-service

port:

number: 8080

- path: /

pathType: Prefix

backend:

service:

name: frontend-service

port:

number: 80

Path types explained

  • Prefix — Matches the URL path prefix. /api matches /api, /api/, /api/users
  • Exact — Only matches the exact path. /api does not match /api/users
  • ImplementationSpecific — Depends on IngressClass

Order matters: more specific paths should come first.

TLS with cert-manager

Install cert-manager

helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install cert-manager jetstack/cert-manager \

--namespace cert-manager \

--create-namespace \

--set installCRDs=true

kubectl get pods -n cert-manager

Create a ClusterIssuer

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-prod

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

email: ops@example.com

privateKeySecretRef:

name: letsencrypt-prod-key

solvers:

- http01:

ingress:

class: nginx

Add TLS to your Ingress

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: app-ingress-tls

namespace: production

annotations:

cert-manager.io/cluster-issuer: letsencrypt-prod

nginx.ingress.kubernetes.io/ssl-redirect: "true"

spec:

ingressClassName: nginx

tls:

- hosts:

- app.example.com

- api.example.com

secretName: app-tls-secret

rules:

- host: app.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: frontend-service

port:

number: 80

- host: api.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: api-service

port:

number: 8080

cert-manager automatically requests and renews certificates:

kubectl get certificates -n production

kubectl describe certificate app-tls-secret -n production

kubectl get challenges -n production

Production Annotations

Rate limiting

metadata:

annotations:

nginx.ingress.kubernetes.io/limit-rps: "10"

nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"

nginx.ingress.kubernetes.io/limit-connections: "5"

Timeouts and body size

metadata:

annotations:

nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"

nginx.ingress.kubernetes.io/proxy-read-timeout: "60"

nginx.ingress.kubernetes.io/proxy-send-timeout: "60"

nginx.ingress.kubernetes.io/proxy-body-size: "50m"

CORS configuration

metadata:

annotations:

nginx.ingress.kubernetes.io/enable-cors: "true"

nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"

nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"

nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type"

Canary deployments

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: app-canary

annotations:

nginx.ingress.kubernetes.io/canary: "true"

nginx.ingress.kubernetes.io/canary-weight: "10"

spec:

ingressClassName: nginx

rules:

- host: app.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: frontend-service-v2

port:

number: 80

Troubleshooting

# Check ingress controller logs

kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=100

# Check if ingress has an address

kubectl get ingress -n production -o wide

# Inspect generated nginx config

kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- \

cat /etc/nginx/nginx.conf | grep -A 20 "server_name app.example.com"

# Test from inside cluster

kubectl run debug --rm -it --image=curlimages/curl -- \

curl -H "Host: app.example.com" http://ingress-nginx-controller.ingress-nginx.svc.cluster.local

# Check events

kubectl describe ingress app-ingress -n production

Common Mistakes

  • Missing ingressClassName: nginx — Without this, the Nginx controller ignores your Ingress resource. Older clusters used kubernetes.io/ingress.class: nginx annotation instead.
  • Service port mismatch — The Ingress backend port must match the Service port, not the container targetPort.
  • DNS not pointing to ingress — The Ingress only works when your domain resolves to the controller's external IP.
  • TLS secret in wrong namespace — The TLS secret must be in the same namespace as the Ingress resource.
  • Path ordering — Longer/more specific paths must come before shorter/generic ones.
  • Not setting proxy-body-size — Default is 1MB. File uploads fail with 413 errors.
  • Quick Reference

    TaskConfiguration
    Basic routing<code class="inline-code">spec.rules[].host</code> + <code class="inline-code">paths[].backend</code>
    TLS termination<code class="inline-code">spec.tls[]</code> + cert-manager annotation
    Path routingMultiple <code class="inline-code">paths[]</code> entries
    Rate limiting<code class="inline-code">limit-rps</code> annotation
    CORS<code class="inline-code">enable-cors</code> annotation
    Canary deploy<code class="inline-code">canary: true</code> + <code class="inline-code">canary-weight</code> annotations
    Body size<code class="inline-code">proxy-body-size</code> annotation
    SSL redirect<code class="inline-code">ssl-redirect: &quot;true&quot;</code> annotation
    WebSocket<code class="inline-code">proxy-read-timeout: &quot;3600&quot;</code> annotation
    Custom headers<code class="inline-code">configuration-snippet</code> annotation

    Summary

    The Nginx Ingress Controller gives you a single cloud load balancer that routes traffic to any number of services based on hostname and path. Combined with cert-manager for automatic TLS, you get production-grade HTTPS routing with minimal configuration. Start with basic routing, add TLS, then layer on rate limiting and auth annotations as needed.

    ---

    Frequently Asked Questions

    What is Kubernetes Ingress and how is it different from a Service?

    A Service provides internal load balancing within the cluster (ClusterIP) or basic external access (NodePort, LoadBalancer). Ingress adds HTTP/HTTPS routing rules — path-based routing, host-based virtual hosting, TLS termination, and more — without needing a LoadBalancer per service. Ingress requires an Ingress Controller (like nginx) to function.

    How do I set up TLS/HTTPS with Ingress-NGINX?

    Create a TLS Secret with your certificate and key, then reference it in your Ingress resource under spec.tls. For automatic certificate management, install cert-manager and add annotations like cert-manager.io/cluster-issuer: letsencrypt-prod to your Ingress. cert-manager will automatically provision and renew Let's Encrypt certificates.

    Why is my Ingress returning 404 or 502 errors?

    A 404 means no Ingress rule matched the request — check that host and path match exactly (including trailing slashes). A 502 means the Ingress Controller cannot reach the backend — verify the Service exists, endpoints are healthy, and port numbers match between Ingress, Service, and Pod. Use kubectl describe ingress and check controller logs for details.

    What is the difference between Ingress-NGINX and NGINX Ingress Controller?

    There are two projects: kubernetes/ingress-nginx (community-maintained, most common) and nginxinc/kubernetes-ingress (NGINX Inc.'s commercial version). The community version uses configmaps and annotations for configuration, while NGINX Inc.'s version supports NGINX Plus features. Most teams use the community kubernetes/ingress-nginx.

    How do I route traffic to different services based on URL path?

    Define multiple path rules in your Ingress spec under the same host. Set path: /api pointing to your API service and path: / pointing to your frontend service. Use pathType: Prefix for prefix matching or pathType: Exact for exact path matching. The Ingress Controller routes requests to the appropriate backend based on the longest matching path.

    ---