Skip to content
C Codeloom
Kubernetes

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.

·8 min read · By Yash Kesharwani
Intermediate 11 min read

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:

  1. 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.
  2. An IngressClass — a Kubernetes object that names the controller and tells Ingress objects which controller to use.
  3. 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. Prefix matches any path starting with the given prefix; Exact matches only the exact path. There is also ImplementationSpecific, which means “ask the controller what it does.” Stick with Prefix or Exact until 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.

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 Prefix or Exact matching
  • TLS termination uses a kubernetes.io/tls Secret; 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:

Questions or feedback? Email codeloomdevv@gmail.com.