Three Names, One Chair#
Reverse proxy. API gateway. Load balancer. Three names that show up in every architecture diagram, often pointing at the same box. They’re related but not the same, and understanding which role a tool is playing helps you stop duplicating work across layers and debug “where did my request go?” issues faster.
The Russian Doll#
Think of them as nested:
A load balancer distributes traffic across servers. That’s its one job. We covered this .
A reverse proxy does everything a load balancer does, plus: SSL termination, caching, URL rewriting, header manipulation, and hiding your backend topology. Clients talk to the proxy, never to your servers directly.
An API gateway does everything a reverse proxy does, plus: authentication, rate limiting , API versioning, request/response transformation, analytics, circuit breaking, and developer portals.
Each layer adds features on top of the previous one. Most modern tools (Traefik, Kong, NGINX) can act as all three — the distinction is about which features you’re using, not which product you’re running.
My Two Middlemen#
I run the same concept in two very different implementations.
Homelab: Traefik#
Traefik sits inside my Docker infrastructure. It watches the Docker socket — when a container starts with the right labels, Traefik automatically picks it up and starts routing traffic to it.
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp-sec.rule=Host(`myapp.example.com`)"
- "traefik.http.routers.myapp-sec.tls=true"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
No config files to edit. No restart. It handles SSL termination with Let’s Encrypt certificates (via Cloudflare DNS challenge), routes by domain name, and integrates with Authentik for SSO. It’s my reverse proxy, load balancer, and partial API gateway in one.
Production: GCE Ingress + BackendConfig#
The GKE setup
is fundamentally different. When I create a Kubernetes Ingress with the gce class, GKE provisions an actual Google Cloud Load Balancer — an external piece of infrastructure that lives outside the cluster.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "gce"
kubernetes.io/ingress.global-static-ip-name: "my-static-ip"
SSL termination happens at Google’s edge. The static IP is a cloud resource, not a pod. Adding CORS headers happens via BackendConfig — a GKE-specific CRD that configures the load balancer’s backend:
apiVersion: cloud.google.com/v1
kind: BackendConfig
spec:
customResponseHeaders:
headers:
- "Access-Control-Allow-Origin: *"
- "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS"
CORS at the load balancer means my application code doesn’t handle it. One less thing to maintain per service.
The “Don’t Handle It Twice” Rule#
The most common mistake with middlemen is doing the same thing at multiple layers. If Cloudflare adds security headers, and Traefik adds the same headers, and your app adds them again — you might end up with duplicate headers or conflicting values.
Map out which layer handles what:
| Concern | Where to Handle |
|---|---|
| DDoS protection | CDN/Edge (Cloudflare) |
| SSL termination | Reverse proxy or cloud LB |
| CORS headers | API gateway or cloud LB (not the app) |
| Authentication | API gateway or application |
| Rate limiting | Multiple layers (each catches different things) |
| Request routing | Reverse proxy / Ingress |
| Response caching | CDN or reverse proxy |
| Business logic | Application (only) |
Rate limiting is the exception — it legitimately belongs at multiple layers because each layer catches different types of abuse. Everything else should have one owner.
When You Need a Full API Gateway#
Traefik and GCE Ingress are reverse proxies with some gateway features. For a full API gateway (Kong, AWS API Gateway), you’d want:
- API key management — issue, rotate, revoke keys per consumer
- Usage analytics — request counts, latency, errors per API/consumer
- Request transformation — modify payloads between consumer and service
- Developer portal — documentation, key registration, usage dashboard
- Circuit breaking — stop sending traffic to failing services
If you’re running a public API with external consumers, tiers, and usage limits, a full gateway is worth it. For internal services behind a GitOps pipeline , a reverse proxy with rate limiting is usually enough.
The Full Traffic Flow#
Putting it all together — how a request reaches my production services:
User → Cloudflare (CDN + WAF + DDoS)
→ Google Cloud Load Balancer (SSL termination + CORS + health checks)
→ Kubernetes Service (L4 internal LB)
→ Pod (application code)
Four layers. Each one handles something specific. No duplication. A request that gets blocked by Cloudflare never reaches GCE. A health check failure at the LB level removes a pod from rotation before users notice. The application only deals with business logic — everything else is handled before the request arrives.
