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.
- Choose an implementation: your cloud's Gateway controller (GKE, AWS, Azure), Envoy Gateway, Traefik, Istio, NGINX Gateway Fabric, or another maintained Ingress controller.
- Prefer the Gateway API (
GatewayplusHTTPRoute) for new clusters; use Ingress where your platform only supports that. - Issue certificates with cert-manager (Let's Encrypt) or the cloud's managed certificates.
- Make Django trust the controller's
X-Forwarded-Proto:SECURE_PROXY_SSL_HEADER, and pods reachable only through the controller. - Set request body size and timeouts at the controller to match uploads and slow views.
- 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.
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.
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
ingress2gatewayhelp 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
- General patterns: Deploy Django on Kubernetes.
- If links come out as http, see Fix build_absolute_uri Returning http Instead of https.
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.
Why does Django generate http links behind the Gateway?
Django does not trust the forwarded protocol by default. Set SECURE_PROXY_SSL_HEADER.