reverse-proxy

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Reverse Proxy

反向代理

Configure reverse proxies to route traffic, terminate TLS, enforce rate limits, and serve as the gateway between clients and backend services.
配置反向代理以实现流量路由、TLS终止、速率限制,并作为客户端与后端服务之间的网关。

When to Use

适用场景

  • Routing traffic from a public domain to one or more backend services.
  • Terminating TLS at the edge and forwarding plain HTTP to backends.
  • Adding rate limiting, CORS, security headers, and access control.
  • Consolidating multiple services under a single domain with path-based routing.
  • Handling WebSocket upgrades, gRPC proxying, or HTTP/2 passthrough.
  • 将公网域名的流量路由至一个或多个后端服务。
  • 在边缘节点终止TLS连接,向后端转发明文HTTP请求。
  • 添加速率限制、CORS、安全标头及访问控制。
  • 通过基于路径的路由将多个服务整合到单个域名下。
  • 处理WebSocket升级、gRPC代理或HTTP/2透传。

Prerequisites

前置条件

  • Backend service(s) running on known host:port.
  • TLS certificate (Let's Encrypt, ACM, or self-signed for development).
  • nginx 1.25+ or Traefik 3.x installed.
  • DNS record pointing the domain to the proxy server.
  • 后端服务已在已知的host:port上运行。
  • 拥有TLS证书(可使用Let's Encrypt、ACM,或开发环境使用自签名证书)。
  • 已安装Nginx 1.25+或Traefik 3.x。
  • DNS记录已将域名指向代理服务器。

nginx Reverse Proxy

Nginx反向代理

Basic HTTPS Proxy with Redirect

基础HTTPS代理与重定向

nginx
undefined
nginx
undefined

/etc/nginx/sites-available/app.example.com

/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;
}
}
undefined
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;
}
}
undefined

Path-Based Routing to Multiple Services

基于路径的多服务路由

nginx
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";
    }
}
nginx
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";
    }
}

Rate Limiting

速率限制

nginx
undefined
nginx
undefined

Define rate limit zones in http block

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;
}
}
undefined
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;
}
}
undefined

Gzip and Brotli Compression

Gzip与Brotli压缩

nginx
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;
}
nginx
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;
}

Let's Encrypt with Certbot

使用Certbot配置Let's Encrypt证书

bash
undefined
bash
undefined

Install certbot with nginx plugin

Install certbot with nginx plugin

sudo apt install certbot python3-certbot-nginx
sudo apt install certbot python3-certbot-nginx

Obtain and install certificate

Obtain and install certificate

sudo certbot --nginx -d app.example.com -d www.example.com
sudo certbot --nginx -d app.example.com -d www.example.com

Auto-renewal is configured via systemd timer

Auto-renewal is configured via systemd timer

sudo systemctl status certbot.timer
sudo systemctl status certbot.timer

Manual renewal test

Manual renewal test

sudo certbot renew --dry-run
undefined
sudo certbot renew --dry-run
undefined

Traefik Reverse Proxy

Traefik反向代理

Static Configuration

静态配置

yaml
undefined
yaml
undefined

traefik.yml

traefik.yml

entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443"
certificatesResolvers: letsencrypt: acme: email: admin@example.com 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
undefined
entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443"
certificatesResolvers: letsencrypt: acme: email: admin@example.com 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
undefined

Dynamic Configuration (File Provider)

动态配置(文件提供者)

yaml
undefined
yaml
undefined

/etc/traefik/dynamic/services.yml

/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
undefined
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
undefined

Traefik with Docker Labels

使用Docker Labels配置Traefik

yaml
undefined
yaml
undefined

docker-compose.yml

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:
undefined
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:
undefined

nginx Testing and Management

Nginx测试与管理

bash
undefined
bash
undefined

Test configuration syntax

Test configuration syntax

sudo nginx -t
sudo nginx -t

Reload without downtime

Reload without downtime

sudo nginx -s reload
sudo nginx -s reload

View active connections

View active connections

sudo nginx -s status
sudo nginx -s status

Check which config file is active

Check which config file is active

nginx -V 2>&1 | grep -o '--conf-path=[^ ]*'
nginx -V 2>&1 | grep -o '--conf-path=[^ ]*'

Monitor access logs

Monitor access logs

tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/access.log

Monitor error logs

Monitor error logs

tail -f /var/log/nginx/error.log
undefined
tail -f /var/log/nginx/error.log
undefined

IP Allowlisting and Geoblocking

IP白名单与地理封锁

nginx
undefined
nginx
undefined

Allow only specific IPs (admin panel)

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; }
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)

Block by country (requires GeoIP2 module)

geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {

geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {

auto_reload 60m;

auto_reload 60m;

$geoip2_data_country_iso_code country iso_code;

$geoip2_data_country_iso_code country iso_code;

}

}

if ($geoip2_data_country_iso_code = "XX") {

if ($geoip2_data_country_iso_code = "XX") {

return 403;

return 403;

}

}

undefined
undefined

Troubleshooting

故障排查

SymptomCauseFix
502 Bad GatewayBackend not running or unreachableVerify backend is listening; check
proxy_pass
URL
504 Gateway TimeoutBackend too slowIncrease
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 60sDefault proxy timeout expiresSet
proxy_read_timeout 86400s
for WebSocket locations
Rate limit hits legitimate usersZone rate too aggressiveIncrease
rate
or
burst
values; use different zones per endpoint
Let's Encrypt renewal failsPort 80 blocked or wrong server blockEnsure
.well-known/acme-challenge/
is accessible
Traefik shows 404 for all routesDocker labels not detectedVerify Docker socket is mounted; check
exposedByDefault
setting
TLS handshake failureCertificate chain incompleteInclude intermediate certificates in
ssl_certificate
症状原因解决方法
502 Bad Gateway后端服务未运行或无法访问验证后端服务是否在监听;检查
proxy_pass
地址
504 Gateway Timeout后端响应过慢增大
proxy_read_timeout
值;检查后端性能
混合内容警告未设置
X-Forwarded-Proto
添加
proxy_set_header X-Forwarded-Proto $scheme
配置
WebSocket连接60秒后断开默认代理超时到期为WebSocket路径设置
proxy_read_timeout 86400s
合法用户触发速率限制速率限制规则过于严格增大
rate
burst
值;为不同端点使用不同的限制区域
Let's Encrypt证书续期失败80端口被阻塞或服务器块配置错误确保
.well-known/acme-challenge/
路径可访问
Traefik所有路由返回404Docker标签未被识别验证Docker套接字是否已挂载;检查
exposedByDefault
设置
TLS握手失败证书链不完整
ssl_certificate
中包含中间证书

Related Skills

相关技能

  • load-balancing - Multi-backend traffic distribution
  • cdn-setup - CDN in front of reverse proxy
  • dns-management - DNS records for proxy domains
  • service-mesh - Service-level routing in Kubernetes
  • 负载均衡 - 多后端流量分发
  • CDN配置 - 在反向代理前端部署CDN
  • DNS管理 - 代理域名的DNS记录配置
  • 服务网格 - Kubernetes中的服务级路由