configure-reverse-proxy · v1.0 · 2026-06-05 · sha256 358c40ec12dd4fd5
configure-reverse-proxy v1.0A
Immutable. This exact content is served forever at /api/v1/blob/358c40ec12dd4fd5.
---
name: configure-reverse-proxy
locale: caveman-lite
source_locale: en
source_commit: 82c77053
translator: "Julius Brussee homage — caveman"
translation_date: "2026-04-19"
description: >
Configure reverse proxy patterns across multiple tools including Nginx,
Traefik, and ShinyProxy. Covers WebSocket proxying, path-based and
host-based routing, SSL termination, and Docker label auto-discovery.
Use when routing multiple services behind a single entry point, proxying
WebSocket connections (Shiny, Socket.IO), auto-discovering Docker services
with Traefik labels, or adding SSL termination to services that don't
handle TLS natively.
license: MIT
allowed-tools: Read Write Edit Bash Grep Glob
metadata:
author: Philipp Thoss
version: "1.0"
domain: containerization
complexity: intermediate
language: multi
tags: reverse-proxy, traefik, nginx, websocket, routing, shinyproxy, ssl
---
# Configure Reverse Proxy
Set up reverse proxy patterns for routing traffic to backend services using Nginx, Traefik, or ShinyProxy.
## When to Use
- Routing multiple services behind a single entry point
- Proxying WebSocket connections (Shiny, Socket.IO, live reload)
- Auto-discovering Docker services with Traefik labels
- Path-based or host-based routing to different backends
- Adding SSL termination to services that don't handle TLS
## Inputs
- **Required**: Backend services to proxy (host:port)
- **Required**: Routing strategy (path-based, host-based, or both)
- **Optional**: Proxy tool preference (Nginx, Traefik)
- **Optional**: Domain name(s) for host-based routing
- **Optional**: WebSocket endpoints to proxy
## Procedure
### Step 1: Choose Proxy Tool
| Feature | Nginx | Traefik |
|---|---|---|
| Configuration | Static files | Docker labels / dynamic |
| Auto-discovery | No (manual) | Yes (Docker provider) |
| Let's Encrypt | Via certbot | Built-in ACME |
| Dashboard | No (3rd party) | Built-in |
| WebSocket | Manual config | Automatic |
| Best for | Static config, high traffic | Dynamic Docker environments |
### Step 2: Nginx — Path-Based Routing
```nginx
server {
listen 80;
location /api/ {
proxy_pass http://api:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /app/ {
proxy_pass http://webapp:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
```
**Note:** Trailing `/` on `proxy_pass` strips the location prefix. `proxy_pass http://api:8000/;` with `location /api/` forwards `/api/users` as `/users`.
### Step 3: Nginx — Host-Based Routing
```nginx
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://webapp:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
### Step 4: Nginx — WebSocket Proxying
WebSockets require upgrade headers. Essential for Shiny, Socket.IO, and live reload:
```nginx
location /ws/ {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
```
For Shiny apps specifically:
```nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location / {
proxy_pass http://shiny:3838;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 86400;
proxy_buffering off;
}
}
```
**Got:** WebSocket connections establish and persist.
**If fail:** Check `proxy_http_version 1.1` is set. Verify `Upgrade` and `Connection` headers.
### Step 5: Traefik — Docker Label Auto-Discovery
`docker-compose.yml`:
```yaml
services:
traefik:
image: traefik:v3.2
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
api:
image: myapi:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.services.api.loadbalancer.server.port=8000"
webapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.webapp.entrypoints=websecure"
- "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
- "traefik.http.services.webapp.loadbalancer.server.port=3000"
volumes:
letsencrypt:
```
**Got:** Traefik auto-discovers services via labels, provisions SSL certificates.
### Step 6: Traefik — Path-Based Routing with Labels
```yaml
services:
api:
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.api.middlewares=strip-api"
- "traefik.http.middlewares.strip-api.stripprefix.prefixes=/api"
- "traefik.http.services.api.loadbalancer.server.port=8000"
```
### Step 7: Traefik — Rate Limiting and Headers
```yaml
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.middlewares.security.headers.stsSeconds=63072000"
- "traefik.http.middlewares.security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security.headers.frameDeny=true"
- "traefik.http.routers.app.middlewares=ratelimit,security"
```
### Step 8: Verify Proxy Configuration
```bash
# Nginx: test config
docker compose exec nginx nginx -t
# Check routing
curl -H "Host: api.example.com" http://localhost/health
# Check WebSocket (needs wscat: npm install -g wscat)
wscat -c ws://localhost/ws/
# Traefik dashboard (if enabled)
# http://localhost:8080/dashboard/
```
**Got:** Requests route to correct backends. WebSocket upgrades succeed.
## Validation
- [ ] HTTP requests route to the correct backend based on path or host
- [ ] WebSocket connections establish and maintain
- [ ] SSL termination works (if configured)
- [ ] Backend services receive correct `Host`, `X-Real-IP`, `X-Forwarded-For` headers
- [ ] Traefik auto-discovers new services via labels (if using Traefik)
- [ ] Configuration survives `docker compose restart`
## Pitfalls
- **Trailing slash mismatch**: `proxy_pass http://app/` vs `http://app` behaves differently with path stripping in Nginx.
- **WebSocket timeout**: Default `proxy_read_timeout` is 60s. Long-lived WebSocket connections need `86400` (24h).
- **Docker socket security**: Mounting `/var/run/docker.sock` in Traefik gives it full Docker access. Use `ro` mount and consider socket proxy.
- **DNS resolution**: Nginx resolves upstreams at startup. Use `resolver 127.0.0.11` for Docker's internal DNS with dynamic services.
- **Missing `proxy_buffering off`**: Shiny and SSE endpoints need `proxy_buffering off` for real-time streaming.
## Related Skills
- `configure-nginx` - detailed Nginx configuration with SSL and security headers
- `deploy-shinyproxy` - ShinyProxy for containerized Shiny app hosting
- `setup-compose-stack` - compose stack that uses a reverse proxy
- `configure-api-gateway` - API gateway patterns with Kong and Traefik