Skip to main content
  1. Posts/

The Middleman

·732 words·4 mins
Photograph By Jahanzeb Ahsan
Blog Software Engineering Infrastructure
Table of Contents

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:

ConcernWhere to Handle
DDoS protectionCDN/Edge (Cloudflare)
SSL terminationReverse proxy or cloud LB
CORS headersAPI gateway or cloud LB (not the app)
AuthenticationAPI gateway or application
Rate limitingMultiple layers (each catches different things)
Request routingReverse proxy / Ingress
Response cachingCDN or reverse proxy
Business logicApplication (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.

Aaron Yong
Author
Aaron Yong
Building things for the web. Writing about development, Linux, cloud, and everything in between.

Related

Who Goes There
·909 words·5 mins
Photograph By Mediamodifier
Blog Software Engineering Security
Sessions, JWTs, OAuth, and SSO — what they are and when to use each
The One-Line Fix
·1004 words·5 mins
Photograph By Maksym Kaharlytskyi
Blog Software Engineering PostgreSQL
Database indexing, EXPLAIN ANALYZE, and why one CREATE INDEX beat five new pods
The Contract
·785 words·4 mins
Photograph By Romain Dancre
Blog Software Engineering Web Development
REST API design patterns that save your future self from debugging nightmares