Expose Django on Kubernetes With Gateway API or Ingress

Route HTTPS traffic to Django on Kubernetes with the Gateway API or a maintained Ingress controller: HTTPRoutes or Ingress rules, TLS with cert-manager, forwarded headers, body size and timeouts, and moving off the retired ingress-nginx.

Problem statement

A Django Deployment and Service on Kubernetes are reachable only inside the cluster until something routes external HTTPS traffic to them. That something is an Ingress controller or, increasingly, a Gateway API implementation. Many existing guides use the community ingress-nginx controller, which the Kubernetes project announced in late 2025 it would retire in 2026, so new setups should choose a maintained option. Whatever you pick, Django still needs the same things: TLS, correct forwarded headers, upload size limits, timeouts, and health-aware routing.

Traffic into a Django clusterLoad balancercloud or MetalLBGateway / IngressTLS, routingServiceClusterIPDjango podsready onlyThe controller terminates TLS and routes to the Service, which sends traffic only to ready pods.
  1. Choose an implementation: your cloud's Gateway controller (GKE, AWS, Azure), Envoy Gateway, Traefik, Istio, NGINX Gateway Fabric, or another maintained Ingress controller.
  2. Prefer the Gateway API (Gateway plus HTTPRoute) for new clusters; use Ingress where your platform only supports that.
  3. Issue certificates with cert-manager (Let's Encrypt) or the cloud's managed certificates.
  4. Make Django trust the controller's X-Forwarded-Proto: SECURE_PROXY_SSL_HEADER, and pods reachable only through the controller.
  5. Set request body size and timeouts at the controller to match uploads and slow views.
  6. Use readiness probes so only ready pods receive traffic during rollouts.

Step-by-step solution

1. Gateway API resources

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web
  namespace: app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  gatewayClassName: envoy            # or your platform's class
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: example.com
      tls:
        mode: Terminate
        certificateRefs: [{ name: example-com-tls }]
    - name: http
      protocol: HTTP
      port: 80
      hostname: example.com
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: django, namespace: app }
spec:
  parentRefs: [{ name: web, sectionName: https }]
  hostnames: [example.com]
  rules:
    - backendRefs: [{ name: web, port: 80 }]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: redirect-https, namespace: app }
spec:
  parentRefs: [{ name: web, sectionName: http }]
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect: { scheme: https, statusCode: 301 }

cert-manager's Gateway API support creates the certificate Secret referenced by the listener. The Gateway API separates infrastructure (the Gateway, often owned by platform engineers) from routes (owned by the app team).

2. Or a plain Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: django
  namespace: app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  ingressClassName: traefik          # a maintained controller
  tls: [{ hosts: [example.com], secretName: example-com-tls }]
  rules:
    - host: example.com
      http:
        paths: [{ path: /, pathType: Prefix, backend: { service: { name: web, port: { number: 80 } } } }]

Controller-specific settings (body size, timeouts, redirects) live in annotations or controller CRDs that differ between implementations, which is one reason the Gateway API standardises more of them.

choosing how traffic enters the clusterAPIStatus for new setupsCloud Gateway controllerGateway API✓good, managed LBEnvoy Gateway / IstioGateway API✓good, portableTraefik (Ingress or Gateway)Ingress + Gateway✓good, k3s defaultingress-nginx (community)Ingress✕retired, migratePick a maintained implementation; the Gateway API keeps routes portabledjango-deployment.com
Check each project's current status and support policy before standardising.

3. Django settings behind the controller

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
ALLOWED_HOSTS = ["example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com"]

Only trust the header if pods are not reachable from outside the cluster except through the controller (ClusterIP Service, NetworkPolicies). Client IPs arrive in X-Forwarded-For; configure your controller to preserve the source IP (for example externalTrafficPolicy: Local on its Service) if you rate-limit by IP.

4. Body size and timeouts

Defaults vary: some controllers limit request bodies to 1 MB, others allow much more, and idle timeouts range from 60 seconds upward. Set them explicitly for your uploads and longest views using your implementation's policy resources (for example Envoy Gateway's ClientTrafficPolicy and BackendTrafficPolicy, or Traefik middlewares), and keep Gunicorn's timeout slightly below the controller's.

5. Readiness and rollouts

readinessProbe:
  httpGet: { path: /health/, port: 8000, httpHeaders: [{ name: Host, value: example.com }] }
  periodSeconds: 5

The Service only lists ready pods as endpoints, so the controller never routes to a pod that is starting or shutting down. Add a short preStop sleep so in-flight connections drain before Gunicorn receives SIGTERM.

edge routing for Django on KubernetesMaintained controller chosennot the retired ingress-nginxTLS via cert-manager or managed certsHTTP redirects to HTTPSDjango trusts X-Forwarded-Protopods only reachable via controllerBody size and timeouts set explicitlymatch uploads and viewsReadiness probe and preStop drainclean rolloutsThe controller is part of the app's request path; configure it deliberatelydjango-deployment.com
Load-test a rollout to confirm no requests fail while pods are replaced.

Verification check: kubectl get gateway,httproute -n app (or ingress) shows the routes accepted, curl -I http://example.com redirects to HTTPS, curl -I https://example.com returns 200 with a valid certificate, and a rolling update completes without errors in the controller's access logs.

Explanation

Kubernetes Services provide stable in-cluster addresses but not HTTP routing or TLS from the internet. Ingress was the original API for that, with most behaviour left to controller-specific annotations. The Gateway API is its successor, with richer, standardised resources and a role split between cluster operators and application teams. Both rely on a controller that watches these resources and configures a proxy (Envoy, Traefik, NGINX, or a cloud load balancer). From Django's point of view, that proxy is just another reverse proxy whose headers and limits must be configured consistently.

Edge cases / notes

  • Migrating from ingress-nginx: translate annotations (body size, timeouts, rewrites, auth) to the new controller's equivalents and test each; tools such as ingress2gateway help convert resources.
  • WebSockets need upgrade support and longer idle timeouts on the route.
  • Static files can be served by WhiteNoise in the pods or offloaded to a CDN in front of the Gateway.
  • Several apps can share one Gateway with separate HTTPRoutes in their own namespaces.

Next steps

FAQ

Should I use Ingress or the Gateway API for Django?

The Gateway API for new clusters where your platform supports it; Ingress where it does not.

Is ingress-nginx still a good choice?

The community ingress-nginx project was retired in 2026. Choose a maintained controller and plan a migration for existing clusters.

How do I get HTTPS certificates on Kubernetes?

cert-manager with Let's Encrypt works with both Gateway API and Ingress; cloud platforms also offer managed certificates.

Django does not trust the forwarded protocol by default. Set SECURE_PROXY_SSL_HEADER.

Learning path · step 3 of 16

Django on Kubernetes

Keep going