---
title: "Ingress Controllers and Routing Patterns"
description: "How to configure Ingress resources with nginx and traefik, including path-based routing, TLS termination, and the annotations and pitfalls that trip people up."
url: https://agent-zone.ai/knowledge/kubernetes/ingress-patterns/
section: knowledge
date: 2026-02-22
categories: ["kubernetes"]
tags: ["ingress","nginx","traefik","tls","routing"]
skills: ["ingress-configuration","tls-termination","traffic-routing"]
tools: ["kubectl","helm","openssl"]
levels: ["intermediate"]
word_count: 803
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/ingress-patterns/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/ingress-patterns/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Ingress+Controllers+and+Routing+Patterns
---


# Ingress Controllers and Routing Patterns

An Ingress resource defines HTTP routing rules -- which hostnames and paths map to which backend Services. But an Ingress resource does nothing on its own. You need an Ingress controller running in the cluster to watch for Ingress resources and configure the actual reverse proxy.

## Ingress Controllers

The two most common controllers are nginx-ingress and Traefik.

**nginx-ingress (ingress-nginx):**

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace
```

Note: there are two different nginx ingress projects. `kubernetes/ingress-nginx` (community) and `nginxinc/kubernetes-ingress` (NGINX Inc). The community version is far more common. Make sure you install from `https://kubernetes.github.io/ingress-nginx`, not the NGINX Inc chart.

**Traefik:**

Traefik is the default ingress controller in k3s. For other clusters:

```bash
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik --namespace traefik --create-namespace
```

Traefik uses its own CRDs (IngressRoute) in addition to standard Ingress resources.

## Basic Ingress Resource

```yaml
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: web-frontend
                port:
                  number: 80
```

The `ingressClassName` field tells Kubernetes which controller should handle this Ingress. If you omit it, the cluster's default IngressClass is used (if one is configured). If you have multiple controllers and no `ingressClassName`, the Ingress may be ignored entirely.

## Path Types: Prefix vs Exact vs ImplementationSpecific

This is where most routing bugs come from.

- **`Prefix`** matches based on URL path prefix split by `/`. The path `/api` matches `/api`, `/api/`, and `/api/users`, but does NOT match `/apiv2`.
- **`Exact`** matches the URL path exactly. `/api` matches only `/api`, not `/api/` or `/api/users`.
- **`ImplementationSpecific`** leaves matching up to the IngressClass. With nginx-ingress, this behaves like a regex-capable prefix match.

```yaml
paths:
  - path: /api
    pathType: Prefix    # matches /api, /api/, /api/anything
    backend:
      service:
        name: api-backend
        port:
          number: 8080
  - path: /
    pathType: Prefix    # catches everything else
    backend:
      service:
        name: web-frontend
        port:
          number: 80
```

Order matters less than specificity. Kubernetes sorts rules by path length -- longer paths match first.

## Host-Based Routing

Route different domains to different backends:

```yaml
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 8080
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80
```

If you omit `host`, the rule matches all incoming requests regardless of hostname.

## TLS Termination

Create a TLS secret and reference it in the Ingress:

```bash
kubectl create secret tls app-tls \
  --cert=tls.crt --key=tls.key \
  -n production
```

```yaml
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80
```

The host in `tls.hosts` must match the host in `rules`. If they do not match, TLS terminates with the default fake certificate and browsers show a certificate error.

For automatic certificate management, use cert-manager with a ClusterIssuer:

```yaml
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls-auto   # cert-manager creates this
```

## Annotations That Matter (nginx-ingress)

```yaml
annotations:
  # Rewrite /api/foo to /foo before forwarding to backend
  nginx.ingress.kubernetes.io/rewrite-target: /$1

  # Increase request body size (default is 1m, too small for file uploads)
  nginx.ingress.kubernetes.io/proxy-body-size: "50m"

  # WebSocket support
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

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

  # Backend protocol (when backend uses HTTPS)
  nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

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

  # CORS
  nginx.ingress.kubernetes.io/enable-cors: "true"
  nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
```

## Common Pitfalls

**Trailing slash problems.** A request to `/app` hitting a backend that expects `/app/` results in a redirect loop or 404. Fix with rewrite-target or configure your backend to handle both.

**Rewrite-target with regex capture groups.** When using path regex with rewrite-target, you must use capture groups:

```yaml
annotations:
  nginx.ingress.kubernetes.io/rewrite-target: /$2
  nginx.ingress.kubernetes.io/use-regex: "true"
# ...
paths:
  - path: /api(/|$)(.*)
    pathType: ImplementationSpecific
```

Without `use-regex: "true"`, the regex is treated as a literal string.

**413 Request Entity Too Large.** The default `proxy-body-size` is 1MB. Any file upload or large POST will get a 413. Set the annotation to the size you need.

**502 Bad Gateway.** Usually means the backend service has no ready endpoints. Check `kubectl get endpoints <service-name>`.

## Debugging Ingress

```bash
# See the ingress rules and their status
kubectl describe ingress <ingress-name> -n <namespace>

# Check if the ingress controller assigned an address
kubectl get ingress -n <namespace>
# ADDRESS column should show the controller's IP/hostname

# View nginx-ingress controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

# Test from inside the cluster
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- \
  curl -H "Host: app.example.com" http://ingress-nginx-controller.ingress-nginx.svc/healthz

# Check the generated nginx configuration
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A 20 "server_name app.example.com"
```

If `kubectl describe ingress` shows the correct rules but traffic does not reach your backend, check that the `ingressClassName` matches your controller's IngressClass, that the backend service exists and has endpoints, and that no network policy is blocking traffic from the ingress controller namespace to your application namespace.

