In Kubernetes, a Service gives pods a stable address inside the cluster. To make web apps reachable from the internet by host name and path, with TLS, you use an Ingress plus an Ingress controller. The most common controller is ingress-nginx, which runs NGINX, and configures it automatically from Ingress resources.

Kubernetes basics: Kubernetes deployments and services and kubectl cheat sheet.

Note: the Kubernetes project announced the retirement of the community ingress-nginx project, and best-effort maintenance ended in March 2026. Existing installations keep working, but receive no further fixes. For new clusters, consider an actively maintained controller (for example Traefik, which k3s ships by default, or a Gateway API implementation). The concepts below apply to Ingress in general.

How it fits together

Internet → (LoadBalancer / node ports 80,443) → ingress-nginx controller → Service → Pods
  • Ingress resource: routing rules (host app.example.com → service web on port 80).
  • Ingress controller: the proxy that implements the rules.

Install 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
kubectl get pods,svc -n ingress-nginx

See Helm tutorial.

On VPS clusters without a cloud load balancer, the controller’s Service of type LoadBalancer stays “Pending”. Options:

  • hostNetwork / hostPort mode, so the controller listens directly on ports 80/443 of the node(s):
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace \
  --set controller.hostNetwork=true --set controller.kind=DaemonSet --set controller.service.type=ClusterIP
  • MetalLB to hand out IPs from your servers’ extra IP addresses;
  • k3s ships Traefik as its built-in ingress, so you may not need ingress-nginx at all. See k3s.

Point your DNS A record at the node’s public IP.

Route a host to a service

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  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: web
                port:
                  number: 80
kubectl apply -f ingress.yaml
kubectl get ingress
curl -H "Host: app.example.com" http://<node-ip>/

Several hosts and paths can share one controller, so one entry point serves many apps.

TLS with cert-manager

Add a tls section, and let cert-manager obtain Let’s Encrypt certificates automatically:

metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [app.example.com]
      secretName: app-example-com-tls

See Kubernetes cert-manager.

Useful annotations (ingress-nginx)

Annotation Purpose
nginx.ingress.kubernetes.io/proxy-body-size Upload size limit
nginx.ingress.kubernetes.io/ssl-redirect Force HTTPS (default true with TLS)
nginx.ingress.kubernetes.io/limit-rps Rate limiting per client IP
nginx.ingress.kubernetes.io/whitelist-source-range Allow only certain IP ranges
nginx.ingress.kubernetes.io/proxy-read-timeout Long requests, WebSockets

Troubleshooting

kubectl describe ingress web
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller
kubectl get endpoints web          # does the service have ready pods?
  • 404 from nginx: the host or path doesn’t match, or ingressClassName is missing.
  • 503: the service has no ready endpoints. Check pods and readiness probes. See Kubernetes debugging pods.

Ingress vs Gateway API

The newer Gateway API (Gateway, HTTPRoute) is more expressive, and is the long-term direction for Kubernetes traffic routing. Many controllers (Envoy Gateway, Traefik, Cilium, NGINX Gateway Fabric) implement it.

Quick reference

Task Command / field
Install helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace
Route Ingress with host, path, backend.service
Class ingressClassName: nginx
TLS tls: + cert-manager annotation
Logs kubectl logs -n ingress-nginx deploy/ingress-nginx-controller

Related: Kubernetes vs Docker Compose and Kubernetes security basics.

  • Kubernetes
  • Nginx
  • DevOps