Agent-almanac configure-reverse-proxy
install
source · Clone the upstream repo
git clone https://github.com/pjt222/agent-almanac
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/configure-reverse-proxy" ~/.claude/skills/pjt222-agent-almanac-configure-reverse-proxy-b33796 && rm -rf "$T"
manifest:
skills/configure-reverse-proxy/SKILL.mdsource content
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
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
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:
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:
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; } }
Expected: WebSocket connections establish and persist.
On failure: Check
proxy_http_version 1.1 is set. Verify Upgrade and Connection headers.
Step 5: Traefik — Docker Label Auto-Discovery
docker-compose.yml:
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:
Expected: Traefik auto-discovers services via labels, provisions SSL certificates.
Step 6: Traefik — Path-Based Routing with Labels
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
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
# 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/
Expected: 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
headersX-Forwarded-For - Traefik auto-discovers new services via labels (if using Traefik)
- Configuration survives
docker compose restart
Common Pitfalls
- Trailing slash mismatch:
vsproxy_pass http://app/
behaves differently with path stripping in Nginx.http://app - WebSocket timeout: Default
is 60s. Long-lived WebSocket connections needproxy_read_timeout
(24h).86400 - Docker socket security: Mounting
in Traefik gives it full Docker access. Use/var/run/docker.sock
mount and consider socket proxy.ro - DNS resolution: Nginx resolves upstreams at startup. Use
for Docker's internal DNS with dynamic services.resolver 127.0.0.11 - Missing
: Shiny and SSE endpoints needproxy_buffering off
for real-time streaming.proxy_buffering off
Related Skills
- detailed Nginx configuration with SSL and security headersconfigure-nginx
- ShinyProxy for containerized Shiny app hostingdeploy-shinyproxy
- compose stack that uses a reverse proxysetup-compose-stack
- API gateway patterns with Kong and Traefikconfigure-api-gateway