Configure nginx and Traefik as reverse proxies. Implement SSL termination and routing. Use when setting up application gateways.
npx skills add https://github.com/BagelHole/DevOps-Security-Agent-Skills --skill reverse-proxy
Configure reverse proxies to route traffic, terminate TLS, enforce rate limits, and serve as the gateway between clients and backend services.
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name app.example.com;
# TLS configuration
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# Proxy to backend
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
# Timeouts
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# Buffering
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
# Frontend SPA
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
# API backend
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 120s;
}
# WebSocket endpoint
location /ws/ {
proxy_pass http://127.0.0.1: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 86400s; # 24h for long-lived connections
}
# Static assets with caching
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
# Define rate limit zones in http block
http {
# 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 1 request/second for login
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
# Connection limit per IP
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
listen 443 ssl http2;
server_name app.example.com;
# Apply rate limit to API
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}
# Strict rate limit on auth endpoints
location /api/auth/ {
limit_req zone=login_limit burst=5;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}
# Connection limit
location / {
limit_conn conn_limit 100;
proxy_pass http://127.0.0.1:3000;
}
}
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
# Brotli (requires ngx_brotli module)
# brotli on;
# brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
# brotli_comp_level 6;
}
# Install certbot with nginx plugin
sudo apt install certbot python3-certbot-nginx
# Obtain and install certificate
sudo certbot --nginx -d app.example.com -d www.example.com
# Auto-renewal is configured via systemd timer
sudo systemctl status certbot.timer
# Manual renewal test
sudo certbot renew --dry-run
# traefik.yml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
docker:
exposedByDefault: false
file:
directory: /etc/traefik/dynamic/
api:
dashboard: true
insecure: false
log:
level: INFO
accessLog:
filePath: /var/log/traefik/access.log
# /etc/traefik/dynamic/services.yml
http:
routers:
app:
rule: "Host(`app.example.com`)"
entryPoints:
- websecure
service: app
tls:
certResolver: letsencrypt
middlewares:
- security-headers
- rate-limit
api:
rule: "Host(`app.example.com`) && PathPrefix(`/api`)"
entryPoints:
- websecure
service: api
tls:
certResolver: letsencrypt
services:
app:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
healthCheck:
path: /health
interval: 10s
timeout: 3s
api:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
healthCheck:
path: /api/health
interval: 10s
timeout: 3s
middlewares:
security-headers:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: strict-origin-when-cross-origin
rate-limit:
rateLimit:
average: 100
burst: 50
period: 1m
# docker-compose.yml
version: "3.8"
services:
traefik:
image: traefik:v3.0
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- letsencrypt:/letsencrypt
frontend:
image: my-frontend:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`app.example.com`)"
- "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.frontend.loadbalancer.server.port=3000"
api:
image: my-api:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`app.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.services.api.loadbalancer.server.port=8080"
- "traefik.http.routers.api.middlewares=api-ratelimit"
- "traefik.http.middlewares.api-ratelimit.ratelimit.average=50"
- "traefik.http.middlewares.api-ratelimit.ratelimit.burst=25"
volumes:
letsencrypt:
# Test configuration syntax
sudo nginx -t
# Reload without downtime
sudo nginx -s reload
# View active connections
sudo nginx -s status
# Check which config file is active
nginx -V 2>&1 | grep -o '\-\-conf-path=[^ ]*'
# Monitor access logs
tail -f /var/log/nginx/access.log
# Monitor error logs
tail -f /var/log/nginx/error.log
# Allow only specific IPs (admin panel)
location /admin/ {
allow 203.0.113.0/24;
allow 198.51.100.5;
deny all;
proxy_pass http://127.0.0.1:3000;
}
# Block by country (requires GeoIP2 module)
# geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
# auto_reload 60m;
# $geoip2_data_country_iso_code country iso_code;
# }
# if ($geoip2_data_country_iso_code = "XX") {
# return 403;
# }
| Symptom | Cause | Fix |
|---------|-------|-----|
| 502 Bad Gateway | Backend not running or unreachable | Verify backend is listening; check proxy_pass URL |
| 504 Gateway Timeout | Backend too slow | Increase proxy_read_timeout; check backend performance |
| Mixed content warnings | X-Forwarded-Proto not set | Add proxy_set_header X-Forwarded-Proto $scheme |
| WebSocket disconnects after 60s | Default proxy timeout expires | Set proxy_read_timeout 86400s for WebSocket locations |
| Rate limit hits legitimate users | Zone rate too aggressive | Increase rate or burst values; use different zones per endpoint |
| Let's Encrypt renewal fails | Port 80 blocked or wrong server block | Ensure .well-known/acme-challenge/ is accessible |
| Traefik shows 404 for all routes | Docker labels not detected | Verify Docker socket is mounted; check exposedByDefault setting |
| TLS handshake failure | Certificate chain incomplete | Include intermediate certificates in ssl_certificate |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take bagelhole/reverse-proxy from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.