Reverse Proxy Patterns for Microservices: Traefik, Nginx, and Caddy Compared
Master reverse proxy patterns for microservice architectures. Covers path-based routing, host-based routing, load balancing, circuit breaking, and rate...
Why Reverse Proxies Matter
In a microservice architecture, clients should never talk directly to backend services. A reverse proxy sits between the internet and your services, handling SSL termination, routing, load balancing, rate limiting, and authentication. It is the single most important piece of your infrastructure.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 170" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="170" rx="12" fill="#1a1a2e"/><text x="60" y="30" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Internet</text><circle cx="60" cy="60" r="25" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="60" y="57" text-anchor="middle" fill="#3b82f6" font-size="18" font-family="system-ui">🌐</text><rect x="155" y="25" width="120" height="120" rx="10" fill="#6366f1" opacity="0.15"/><rect x="155" y="25" width="120" height="120" rx="10" fill="none" stroke="#6366f1" stroke-width="1.5"/><text x="215" y="50" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Reverse</text><text x="215" y="65" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Proxy</text><text x="215" y="85" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">TLS termination</text><text x="215" y="98" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Load balancing</text><text x="215" y="111" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Path routing</text><text x="215" y="124" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Rate limiting</text><rect x="350" y="20" width="110" height="35" rx="6" fill="#2dd4bf" opacity="0.8"/><text x="405" y="42" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">app.example.com</text><rect x="350" y="65" width="110" height="35" rx="6" fill="#a855f7" opacity="0.8"/><text x="405" y="87" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">api.example.com</text><rect x="350" y="110" width="110" height="35" rx="6" fill="#f59e0b" opacity="0.8"/><text x="405" y="132" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">cdn.example.com</text><defs><marker id="arrow11" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="87" y1="60" x2="153" y2="75" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow11)"/><line x1="277" y1="55" x2="348" y2="37" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><line x1="277" y1="85" x2="348" y2="82" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><line x1="277" y1="115" x2="348" y2="127" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><text x="120" y="55" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">HTTPS</text><text x="505" y="42" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:3000</text><text x="505" y="87" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:8080</text><text x="505" y="132" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:9000</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">A reverse proxy terminates TLS, routes requests by hostname, and load-balances across backend services.</p></div>
Pattern 1: Host-Based Routing
Route traffic based on the subdomain:
Traefik (Docker Labels)
services:
api:
image: myapp/api:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.services.api.loadbalancer.server.port=8080"
dashboard:
image: myapp/dashboard:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`app.example.com`)"
- "traefik.http.services.dashboard.loadbalancer.server.port=3000"
docs:
image: myapp/docs:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.docs.rule=Host(`docs.example.com`)"
- "traefik.http.services.docs.loadbalancer.server.port=80"Nginx
# /etc/nginx/conf.d/api.conf
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://api:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
server {
listen 443 ssl http2;
server_name app.example.com;
location / {
proxy_pass http://dashboard:3000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}Caddy
api.example.com {
reverse_proxy api:8080
}
app.example.com {
reverse_proxy dashboard:3000
}
docs.example.com {
reverse_proxy docs:80
}Caddy's simplicity is remarkable — automatic HTTPS, no SSL configuration needed.
Pattern 2: Path-Based Routing
Route based on URL path — useful for monolithic frontends talking to multiple backends:
Traefik
services:
users-service:
labels:
- "traefik.http.routers.users.rule=Host(`api.example.com`) && PathPrefix(`/api/users`)"
- "traefik.http.middlewares.users-strip.stripprefix.prefixes=/api/users"
- "traefik.http.routers.users.middlewares=users-strip"
orders-service:
labels:
- "traefik.http.routers.orders.rule=Host(`api.example.com`) && PathPrefix(`/api/orders`)"
- "traefik.http.middlewares.orders-strip.stripprefix.prefixes=/api/orders"
- "traefik.http.routers.orders.middlewares=orders-strip"Nginx
server {
listen 443 ssl http2;
server_name api.example.com;
location /api/users/ {
rewrite ^/api/users/(.*) /\$1 break;
proxy_pass http://users-service:8080;
}
location /api/orders/ {
rewrite ^/api/orders/(.*) /\$1 break;
proxy_pass http://orders-service:8080;
}
location /api/payments/ {
rewrite ^/api/payments/(.*) /\$1 break;
proxy_pass http://payments-service:8080;
}
}Pattern 3: Forward Authentication
Authenticate requests before they reach your services:
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><rect x="20" y="20" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="42" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Web</text><rect x="20" y="65" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="87" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Mobile</text><rect x="20" y="110" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="132" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">IoT</text><rect x="150" y="20" width="120" height="130" rx="10" fill="#6366f1" opacity="0.9"/><text x="210" y="50" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui" font-weight="bold">Gateway</text><line x1="165" y1="60" x2="255" y2="60" stroke="#ffffff" stroke-width="0.5" opacity="0.3"/><text x="210" y="80" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Rate Limit</text><text x="210" y="95" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Auth</text><text x="210" y="110" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Load Balance</text><text x="210" y="125" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Transform</text><text x="210" y="140" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Cache</text><rect x="340" y="15" width="95" height="35" rx="6" fill="#a855f7" opacity="0.8"/><text x="387" y="37" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service A</text><rect x="340" y="60" width="95" height="35" rx="6" fill="#2dd4bf" opacity="0.8"/><text x="387" y="82" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service B</text><rect x="340" y="105" width="95" height="35" rx="6" fill="#f59e0b" opacity="0.8"/><text x="387" y="127" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service C</text><rect x="490" y="55" width="80" height="45" rx="6" fill="none" stroke="#e2e8f0" stroke-width="1"/><text x="530" y="82" text-anchor="middle" fill="#e2e8f0" font-size="10" font-family="system-ui">DB / Cache</text><defs><marker id="arrow7" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="92" y1="37" x2="148" y2="55" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="92" y1="82" x2="148" y2="85" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="92" y1="127" x2="148" y2="115" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="55" x2="338" y2="32" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="85" x2="338" y2="77" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="115" x2="338" y2="122" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="437" y1="77" x2="488" y2="77" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">API gateway pattern: a single entry point handles auth, rate limiting, and routing to backend services.</p></div>
Traefik + Authelia
# traefik dynamic config
http:
middlewares:
authelia:
forwardAuth:
address: "http://authelia:9091/api/authz/forward-auth"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Email"Then apply to any service:
services:
internal-app:
labels:
- "traefik.http.routers.internal.middlewares=authelia@file"Pattern 4: Rate Limiting
Traefik
http:
middlewares:
rate-limit:
rateLimit:
average: 100
burst: 50
period: 1m
sourceCriterion:
ipStrategy:
depth: 1Nginx
limit_req_zone \$binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}Pattern 5: Load Balancing
Nginx Upstream
upstream api_backend {
least_conn; # or round-robin, ip_hash, random
server api1:8080 weight=3;
server api2:8080 weight=2;
server api3:8080 weight=1;
server api4:8080 backup;
keepalive 32;
}Traefik (Docker Replicas)
services:
api:
image: myapp/api:latest
deploy:
replicas: 3
labels:
- "traefik.http.services.api.loadbalancer.server.port=8080"
- "traefik.http.services.api.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.api.loadbalancer.healthcheck.interval=10s"Pattern 6: WebSocket Proxying
# Nginx WebSocket proxy
location /ws/ {
proxy_pass http://websocket-service:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_read_timeout 3600s;
}Comparison Table
|---------|---------|-------|-------|
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><rect x="230" y="15" width="140" height="35" rx="8" fill="#6366f1" opacity="0.9"/><text x="300" y="38" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui" font-weight="bold">API Gateway</text><rect x="30" y="80" width="100" height="50" rx="8" fill="#3b82f6" opacity="0.8"/><text x="80" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Auth</text><text x="80" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service</text><rect x="160" y="80" width="100" height="50" rx="8" fill="#a855f7" opacity="0.8"/><text x="210" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">User</text><text x="210" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service</text><rect x="290" y="80" width="100" height="50" rx="8" fill="#2dd4bf" opacity="0.8"/><text x="340" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Order</text><text x="340" y="115" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service</text><rect x="420" y="80" width="100" height="50" rx="8" fill="#f59e0b" opacity="0.8"/><text x="470" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Payment</text><text x="470" y="115" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service</text><line x1="265" y1="50" x2="80" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="285" y1="50" x2="210" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="315" y1="50" x2="340" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="335" y1="50" x2="470" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><ellipse cx="80" cy="175" rx="35" ry="12" fill="none" stroke="#3b82f6" stroke-width="1.5"/><line x1="45" y1="175" x2="45" y2="190" stroke="#3b82f6" stroke-width="1.5"/><line x1="115" y1="175" x2="115" y2="190" stroke="#3b82f6" stroke-width="1.5"/><ellipse cx="80" cy="190" rx="35" ry="12" fill="none" stroke="#3b82f6" stroke-width="1.5"/><line x1="80" y1="130" x2="80" y2="163" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><ellipse cx="340" cy="175" rx="35" ry="12" fill="none" stroke="#2dd4bf" stroke-width="1.5"/><line x1="305" y1="175" x2="305" y2="190" stroke="#2dd4bf" stroke-width="1.5"/><line x1="375" y1="175" x2="375" y2="190" stroke="#2dd4bf" stroke-width="1.5"/><ellipse cx="340" cy="190" rx="35" ry="12" fill="none" stroke="#2dd4bf" stroke-width="1.5"/><line x1="340" y1="130" x2="340" y2="163" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><rect x="155" y="160" width="150" height="30" rx="6" fill="#a855f7" opacity="0.3"/><text x="230" y="180" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">Message Bus / Events</text><line x1="210" y1="130" x2="210" y2="158" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><line x1="470" y1="130" x2="470" y2="175" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><line x1="305" y1="175" x2="470" y2="175" stroke="#94a3b8" stroke-width="0.5" stroke-dasharray="3,3" opacity="0.3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Microservices architecture: independent services communicate through an API gateway and event bus.</p></div>
Which Should You Choose?
At TechSaaS, we run Traefik v3 in front of 50+ Docker containers. Adding a new service is as simple as adding Docker labels — no proxy config files to edit, no SSL certificates to manage. Combined with Authelia for SSO, every service gets enterprise-grade authentication and routing automatically.
Questions about reverse proxy architecture? Email us at [email protected].
Need help with platform engineering?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.