TL;DR

Ingress routes external HTTP(S) traffic to Kubernetes Services, but the Ingress object does nothing until an Ingress controller is installed. The controller receives traffic, reads Ingress rules, terminates or passes TLS, and proxies to Services with healthy endpoints. Gateway API is the newer, more role-oriented model: platform teams own GatewayClass/Gateway and app teams attach HTTPRoutes.

Mental Model

A Service gives stable access inside the cluster. Ingress gives HTTP and HTTPS access from outside the cluster into those Services. It is Layer 7 routing: hostnames, paths, headers, TLS certificates, redirects, and controller-specific annotations.

The biggest beginner mistake is thinking an Ingress is itself a load balancer. It is only a routing rule. A real controller such as NGINX Ingress, Traefik, HAProxy, AWS Load Balancer Controller, Istio, or another implementation must watch that rule and configure real proxy/load-balancing infrastructure.

ClientDNS / LBapi.example.comControllerNGINX / TraefikServiceweb-api:80Podsready endpointsIngress rule chooses backend Service; the Service still needs healthy EndpointSlices.

External HTTP(S) traffic flows through a controller before it reaches a Service.

Ingress Vs Gateway API

ConceptIngressGateway API
ControllerIngress controller watches Ingress objects.Gateway controller watches Gateway API objects.
Infrastructure ownerOften hidden in controller install, Service type, and annotations.GatewayClass and Gateway make infra ownership explicit.
App ownerCreates Ingress rules.Creates HTTPRoute objects that attach to a Gateway.
PortabilityBasic routing is portable; advanced behavior is annotation-heavy.More expressive standard for routing, attachment, status, and delegation.
Client realityStill extremely common.Growing quickly, especially in newer platform teams.

Build From Scratch

This example assumes you already have a working web-api Service in namespace app. The Ingress maps api.example.com to that Service. In a local lab, replace the hostname with something you control in /etc/hosts, local DNS, or your cloud DNS zone.

yamlbasic-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-api
  namespace: app
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-api
                port:
                  number: 80
bashverify-basic-ingress.sh
kubectl get ingressclass
kubectl apply -f basic-ingress.yaml
kubectl get ingress web-api -n app -o wide
kubectl describe ingress web-api -n app

# Confirm the backend Service has endpoints before testing the Ingress.
kubectl get svc web-api -n app -o wide
kubectl get endpointslice -n app -l kubernetes.io/service-name=web-api -o wide

# Replace INGRESS_IP with the load balancer IP/DNS for your controller.
curl -v --resolve api.example.com:80:<INGRESS_IP> http://api.example.com/

Host And Path Routing

Ingress can route several hostnames and paths through one controller. Keep routes boring where possible: exact hostnames, clear path prefixes, and one backend Service per app boundary.

yamlhost-path-routing.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: platform-routes
  namespace: app
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: web-api
                port:
                  number: 80
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: admin-ui
                port:
                  number: 80

TLS And cert-manager

Ingress TLS usually terminates at the controller. The Ingress references a Kubernetes TLS Secret in the same namespace. cert-manager can create and renew that Secret from an ACME issuer such as Let's Encrypt or from an internal CA.

yamltls-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-api
  namespace: app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-example-com-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-api
                port:
                  number: 80
bashtls-debug.sh
kubectl get certificate,certificaterequest,order,challenge -n app
kubectl describe certificate api-example-com-tls -n app
kubectl get secret api-example-com-tls -n app -o yaml

openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts

Annotations And Controller Behavior

Ingress annotations are not portable Kubernetes behavior; they are controller-specific configuration. NGINX, Traefik, AWS Load Balancer Controller, HAProxy, and cloud provider controllers all interpret different annotations.

NeedWhere It Usually LivesRisk
Redirect HTTP to HTTPSController annotation or global controller config.Wrong annotation can do nothing or redirect unexpectedly.
Request body sizeNGINX/Traefik-specific annotation or middleware.Uploads fail with 413 if too small.
TimeoutsController annotation, proxy config, or Gateway policy.Long requests fail with 504.
Rewrite pathController annotation or route filter.Apps see different paths than expected.
Cloud load balancer typeController Service or cloud-specific Ingress annotation.Can create public exposure when private was intended.

Gateway API Pattern

Gateway API splits responsibilities more cleanly. A platform team can own the Gateway and listener configuration; app teams can own HTTPRoutes that attach to the Gateway, subject to allowed namespaces and route policies.

yamlgateway-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-gateway
  namespace: gateway-system
spec:
  gatewayClassName: nginx
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: api.example.com
      tls:
        mode: Terminate
        certificateRefs:
          - name: api-example-com-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-api
  namespace: app
spec:
  parentRefs:
    - name: public-gateway
      namespace: gateway-system
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-api
          port: 80

Debugging Checklist

Debug from backend to edge. If the Service has no endpoints, the Ingress layer cannot fix it. If the controller is missing or watching a different class, the Ingress object may look valid while no traffic moves.

bashingress-debug.sh
NS=<namespace>
ING=<ingress>
SVC=<service>
HOST=<hostname>

# 1. Backend health.
kubectl get svc "$SVC" -n "$NS" -o wide
kubectl get endpointslice -n "$NS" -l kubernetes.io/service-name="$SVC" -o wide

# 2. Ingress rule and class.
kubectl get ingressclass
kubectl get ingress "$ING" -n "$NS" -o wide
kubectl describe ingress "$ING" -n "$NS"
kubectl get ingress "$ING" -n "$NS" -o yaml

# 3. Controller and external address.
kubectl get pods -A | grep -Ei 'ingress|traefik|nginx|haproxy'
kubectl get svc -A | grep -Ei 'ingress|traefik|nginx|haproxy'
kubectl logs -n <controller-namespace> deploy/<controller-deployment> --tail=100

# 4. Edge test with explicit host header.
curl -vk --resolve "$HOST:443:<INGRESS_IP>" "https://$HOST/"

Symptom To Cause

SymptomLikely CauseCheck First
Ingress has no external addressController Service pending, cloud LB issue, missing controller.Controller Service, events, cloud controller logs.
404 from controllerHost/path does not match any rule, wrong ingress class, default backend response.describe ingress, curl Host header, controller logs.
502 or 503Service has no endpoints, Pod not ready, wrong Service port.EndpointSlice and Service YAML.
504 timeoutBackend slow, timeout too low, network policy, app not responding.App logs, direct Service curl, controller timeout settings.
TLS certificate mismatchWrong Secret, wrong host, stale certificate, cert-manager failure.Ingress TLS block, Secret, cert-manager resources, openssl s_client.
HTTP works, HTTPS failsTLS listener missing, firewall/LB issue, Secret invalid.Controller Service ports, TLS Secret, external LB listeners.
Path rewrite breaks appController-specific rewrite annotation or pathType behavior.Controller annotations and app expected base path.

Safe Change Pattern

  1. Identify the controller and class before editing anything.
  2. Confirm backend Service endpoints are healthy.
  3. Change the source of truth: Helm values, ArgoCD app, Flux repo, or Terraform/platform module.
  4. Test with curl -H "Host: ..." or --resolve before changing public DNS.
  5. After rollout, verify status, controller logs, TLS certificate, access logs, and backend metrics.