Your API Gateway Might Be Over-Engineering a Simple Problem
API gateways promise centralized authentication, rate limiting, routing, observability, and protocol translation. In practice, they’re also one of the most commonly over-provisioned pieces of infrastructure — a full Kong or Envoy deployment for an application that handles 200 requests per second is like using a freight elevator to move a suitcase. This guide covers what API gateways actually do, when you need one, when a simpler reverse proxy suffices, and when the major options (Kong, Envoy, Traefik) make sense.
What an API Gateway Actually Does
An API gateway sits in front of your services and handles cross-cutting concerns that would otherwise need to be implemented in every service:
- Authentication and authorization: Validate JWT tokens, API keys, OAuth flows before requests reach your services
- Rate limiting: Per-user, per-IP, or per-key request throttling
- Request routing: Route /v1/users to the user service, /v1/orders to the order service
- Protocol transformation: REST-to-gRPC, REST-to-GraphQL aggregation
- Observability: Centralized access logs, metrics, distributed tracing initiation
- Request/response transformation: Add headers, transform payloads, aggregate responses
- SSL termination: Handle TLS in one place rather than every service
Do You Actually Need an API Gateway?
Before evaluating tools, ask whether you need an API gateway at all. You probably don’t if:
- You have fewer than 5 backend services
- You’re running a monolith with a single deployment
- Your authentication is handled entirely within your application
- You’re not exposing a public API to third-party developers
What you likely need instead: a well-configured reverse proxy (Nginx or Caddy) plus application-level middleware.
# Nginx as a simple "gateway" for a 3-service application
# This handles 80% of what small teams need from an API gateway
upstream user_service {
server user-service:8001;
keepalive 32;
}
upstream order_service {
server order-service:8002;
keepalive 32;
}
upstream payment_service {
server payment-service:8003;
keepalive 32;
}
server {
listen 443 ssl;
server_name api.example.com;
# Rate limiting (simple, built into Nginx)
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req zone=api burst=20 nodelay;
# Route by path prefix
location /v1/users/ {
proxy_pass http://user_service/;
proxy_set_header X-Request-ID $request_id;
proxy_set_header Authorization $http_authorization;
}
location /v1/orders/ {
proxy_pass http://order_service/;
proxy_set_header X-Request-ID $request_id;
}
location /v1/payments/ {
# Extra rate limiting for payment endpoints
limit_req zone=api burst=5 nodelay;
proxy_pass http://payment_service/;
}
}
This handles routing, rate limiting, request ID propagation, and SSL termination with zero additional dependencies.
Kong: The Extensible Gateway for Complex Needs
Kong is the most widely deployed open-source API gateway. Built on Nginx/OpenResty, it adds a plugin system, admin API, and declarative configuration on top.
# Kong declarative config (kong.yml) — deploy with Kong in DB-less mode
_format_version: "3.0"
services:
- name: user-service
url: http://user-service:8001
plugins:
- name: jwt
config:
key_claim_name: kid
secret_is_base64: false
- name: rate-limiting
config:
minute: 1000
hour: 10000
policy: local
routes:
- name: user-routes
paths:
- /v1/users
strip_path: false
- name: payment-service
url: http://payment-service:8003
plugins:
- name: jwt
- name: rate-limiting
config:
minute: 100 # Stricter for payments
policy: redis # Distributed rate limiting via Redis
redis_host: redis
routes:
- name: payment-routes
paths:
- /v1/payments
plugins:
- name: correlation-id # Add X-Correlation-ID to all requests
config:
header_name: X-Request-ID
generator: uuid
- name: prometheus # Expose /metrics endpoint
- name: file-log
config:
path: /dev/stdout
reopen: false
# docker-compose.yml for Kong in DB-less mode
services:
kong:
image: kong:3.6
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: "0.0.0.0:8001"
ports:
- "80:8000"
- "443:8443"
- "8001:8001" # Admin API — do NOT expose publicly
volumes:
- ./kong.yml:/kong/declarative/kong.yml
When Kong Makes Sense
- Developer portal for external API consumers
- Complex authentication flows (OAuth, multiple JWT issuers)
- Per-consumer rate limiting (different limits for different API key tiers)
- Rich plugin ecosystem (LDAP, AWS Lambda, GraphQL, canary deploys)
- Teams that need a GUI admin interface
Envoy: The Proxy for Service Meshes
Envoy is a Layer 7 proxy originally built at Lyft. It’s the data plane for Istio and the core of most Kubernetes service meshes. Unlike Kong (which is designed as an ingress/edge gateway), Envoy is designed to run as a sidecar proxy next to every service instance.
# envoy.yaml — front proxy configuration
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
myapp_jwt:
issuer: "https://auth.example.com"
remote_jwks:
http_uri:
uri: "https://auth.example.com/.well-known/jwks.json"
cluster: auth_cluster
timeout: 5s
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: services
domains: ["*"]
routes:
- match:
prefix: "/v1/users"
route:
cluster: user_service
- match:
prefix: "/v1/orders"
route:
cluster: order_service
When Envoy Makes Sense
- Running in Kubernetes with a service mesh requirement
- Need advanced traffic management (circuit breaking, retries, outlier detection built in)
- gRPC-first architecture (Envoy has first-class gRPC support)
- Platform teams building internal infrastructure products
When Envoy Is Overkill
Envoy’s configuration is notoriously verbose and complex. Running Istio on a 3-service application adds significant operational overhead — Envoy sidecars to every pod, control plane components, certificate management. Many teams adopt it for Kubernetes “best practices” compliance and spend more time debugging the service mesh than their actual application.
Traefik: The Developer-Friendly Middle Ground
Traefik is purpose-built for container environments. It auto-discovers Docker and Kubernetes services and configures itself based on labels/annotations — no manual routing configuration:
# docker-compose.yml with Traefik auto-discovery
services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080" # Dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
user-service:
image: myapp/user-service
labels:
- "traefik.enable=true"
- "traefik.http.routers.users.rule=PathPrefix(`/v1/users`)"
- "traefik.http.routers.users.entrypoints=websecure"
- "traefik.http.routers.users.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.users-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.users-ratelimit.ratelimit.burst=50"
- "traefik.http.routers.users.middlewares=users-ratelimit"
The Decision Matrix
Matching your situation to the right tool:
- Monolith or 2-3 services, no public API: Nginx or Caddy reverse proxy. No API gateway needed.
- Docker Compose, small microservices, developer experience matters: Traefik. Auto-discovery is a genuine productivity win.
- Public developer API, complex auth, per-tier rate limits: Kong. The plugin ecosystem and admin UI are worth it.
- Kubernetes, service mesh, platform engineering team: Envoy/Istio or Cilium service mesh.
- Managed cloud: AWS API Gateway, GCP API Gateway, or Cloudflare API Shield if you’re already on those platforms.
The key insight: an API gateway is infrastructure you have to operate, monitor, debug, and upgrade. Choose the simplest option that meets your actual requirements, not the one with the most features.
