# Docker Nginx 反向代理完整指南
# Nginx 反向代理簡介
反向代理(Reverse Proxy)的作用:
- 隱藏後端服務:客戶端無需知道實際服務地址
- 負載均衡:分散流量到多個後端服務器
- SSL 終止:集中管理 HTTPS 証書
- 緩存加速:減少後端服務器壓力
- 安全防護:保護内部不安全端口
# Docker 環境準備
# 項目結構
nginx-proxy/
├── docker-compose.yml
├── nginx/
│ ├── nginx.conf
│ ├── conf.d/
│ │ └── default.conf
│ └── ssl/
│ ├── cert.pem
│ └── key.pem
└── .env
# docker-compose.yml
| version: '3.8' |
| |
| services: |
| nginx: |
| image: nginx:1.25-alpine |
| container_name: nginx-proxy |
| restart: always |
| ports: |
| - "80:80" |
| - "443:443" |
| volumes: |
| - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro |
| - ./nginx/conf.d/:/etc/nginx/conf.d/:ro |
| - ./nginx/ssl/:/etc/nginx/ssl/:ro |
| - nginx-logs:/var/log/nginx |
| environment: |
| TZ: Asia/Taipei |
| networks: |
| - proxy-network |
| healthcheck: |
| test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"] |
| interval: 30s |
| timeout: 10s |
| retries: 3 |
| start_period: 10s |
| |
| backend1: |
| image: httpbin/httpbin:latest |
| container_name: backend-app-1 |
| expose: |
| - "80" |
| environment: |
| TZ: Asia/Taipei |
| networks: |
| - proxy-network |
| healthcheck: |
| test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"] |
| interval: 30s |
| timeout: 10s |
| retries: 3 |
| |
| backend2: |
| image: httpbin/httpbin:latest |
| container_name: backend-app-2 |
| expose: |
| - "80" |
| environment: |
| TZ: Asia/Taipei |
| networks: |
| - proxy-network |
| healthcheck: |
| test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"] |
| interval: 30s |
| timeout: 10s |
| retries: 3 |
| |
| volumes: |
| nginx-logs: |
| driver: local |
| |
| networks: |
| proxy-network: |
| driver: bridge |
# Nginx 配置
# nginx.conf(主配置文件)
| user nginx; |
| worker_processes auto; |
| error_log /var/log/nginx/error.log warn; |
| pid /var/run/nginx.pid; |
| |
| events { |
| worker_connections 2048; |
| use epoll; |
| multi_accept on; |
| } |
| |
| http { |
| include /etc/nginx/mime.types; |
| default_type application/octet-stream; |
| |
| |
| log_format combined '$remote_addr - $remote_user [$time_local] ' |
| '"$request" $status $body_bytes_sent ' |
| '"$http_referer" "$http_user_agent" ' |
| 'rt=$request_time uct="$upstream_connect_time" ' |
| 'uht="$upstream_header_time" urt="$upstream_response_time"'; |
| |
| access_log /var/log/nginx/access.log combined buffer=32k flush=5s; |
| |
| |
| sendfile on; |
| tcp_nopush on; |
| tcp_nodelay on; |
| types_hash_max_size 2048; |
| server_tokens off; |
| |
| |
| keepalive_timeout 65; |
| keepalive_requests 100; |
| |
| |
| client_max_body_size 100m; |
| client_body_buffer_size 128k; |
| |
| |
| gzip on; |
| gzip_vary on; |
| gzip_comp_level 6; |
| gzip_types text/plain text/css text/xml text/javascript |
| application/json application/javascript application/xml+rss; |
| gzip_min_length 1024; |
| |
| |
| upstream backend { |
| least_conn; |
| server backend1:80 max_fails=3 fail_timeout=30s; |
| server backend2:80 max_fails=3 fail_timeout=30s; |
| keepalive 32; |
| } |
| |
| |
| server { |
| listen 80 default_server; |
| server_name _; |
| |
| location /health { |
| access_log off; |
| return 200 "healthy\n"; |
| add_header Content-Type text/plain; |
| } |
| |
| |
| location / { |
| return 301 https://$host$request_uri; |
| } |
| } |
| |
| include /etc/nginx/conf.d/*.conf; |
| } |
# conf.d/default.conf(虛擬主機配置)
| |
| server { |
| listen 80; |
| server_name example.com www.example.com; |
| |
| |
| location /.well-known/acme-challenge/ { |
| root /var/www; |
| } |
| |
| |
| location / { |
| return 301 https://$server_name$request_uri; |
| } |
| } |
| |
| |
| server { |
| listen 443 ssl http2; |
| server_name example.com www.example.com; |
| |
| |
| ssl_certificate /etc/nginx/ssl/cert.pem; |
| ssl_certificate_key /etc/nginx/ssl/key.pem; |
| ssl_protocols TLSv1.2 TLSv1.3; |
| ssl_ciphers HIGH:!aNULL:!MD5; |
| ssl_prefer_server_ciphers on; |
| ssl_session_cache shared:SSL:10m; |
| ssl_session_timeout 10m; |
| |
| |
| add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; |
| |
| |
| add_header X-Frame-Options "SAMEORIGIN" always; |
| add_header X-Content-Type-Options "nosniff" always; |
| add_header X-XSS-Protection "1; mode=block" always; |
| add_header Referrer-Policy "strict-origin-when-cross-origin" always; |
| |
| |
| location / { |
| proxy_pass http://backend; |
| proxy_http_version 1.1; |
| |
| |
| proxy_set_header Connection ""; |
| 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; |
| proxy_set_header X-Forwarded-Host $server_name; |
| |
| |
| proxy_connect_timeout 60s; |
| proxy_send_timeout 60s; |
| proxy_read_timeout 60s; |
| |
| |
| proxy_buffering on; |
| proxy_buffer_size 4k; |
| proxy_buffers 8 4k; |
| proxy_busy_buffers_size 8k; |
| |
| |
| proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; |
| proxy_next_upstream_tries 2; |
| } |
| |
| |
| location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ { |
| expires 30d; |
| add_header Cache-Control "public, immutable"; |
| proxy_pass http://backend; |
| proxy_cache_valid 200 10m; |
| } |
| |
| |
| location ~ /\. { |
| deny all; |
| access_log off; |
| log_not_found off; |
| } |
| } |
# SSL 證書管理
# 從 Let's Encrypt 獲取免費証書
| |
| apt-get install -y certbot python3-certbot-docker |
| |
| |
| certbot certonly \ |
| --standalone \ |
| --email your-email@example.com \ |
| --agree-tos \ |
| -d example.com,www.example.com |
| |
| |
| |
| |
# 使用自簽名證書(測試用)
| |
| openssl req -new -newkey rsa:4096 \ |
| -x509 -sha256 -days 365 \ |
| -nodes \ |
| -out cert.pem \ |
| -keyout key.pem \ |
| -subj "/CN=example.com" |
| |
| |
| openssl req -new -newkey rsa:4096 \ |
| -x509 -sha256 -days 365 \ |
| -nodes \ |
| -out cert.pem \ |
| -keyout key.pem |
# 自動續期 Let's Encrypt 證書
| |
| 0 3 * * * certbot renew --quiet && \ |
| docker cp /etc/letsencrypt/live/example.com/cert.pem nginx-proxy:/etc/nginx/ssl/ && \ |
| docker cp /etc/letsencrypt/live/example.com/privkey.pem nginx-proxy:/etc/nginx/ssl/key.pem && \ |
| docker exec nginx-proxy nginx -s reload |
# 實際應用場景
# 場景 1:多後端負載均衡
| upstream multi_backend { |
| least_conn; |
| server api-server1:8080 weight=5; |
| server api-server2:8080 weight=3; |
| server api-server3:8080 weight=2; |
| keepalive 32; |
| } |
| |
| server { |
| listen 443 ssl http2; |
| server_name api.example.com; |
| |
| ssl_certificate /etc/nginx/ssl/api.pem; |
| ssl_certificate_key /etc/nginx/ssl/api.key; |
| |
| location / { |
| proxy_pass http://multi_backend; |
| |
| } |
| } |
# 場景 2:不同路徑代理到不同服務
| server { |
| listen 443 ssl http2; |
| server_name example.com; |
| |
| ssl_certificate /etc/nginx/ssl/cert.pem; |
| ssl_certificate_key /etc/nginx/ssl/key.pem; |
| |
| location / { |
| proxy_pass http://web-frontend:3000; |
| } |
| |
| location /api/ { |
| proxy_pass http://api-backend:8080; |
| proxy_set_header X-Forwarded-Path /api; |
| } |
| |
| location /admin/ { |
| proxy_pass http://admin-panel:5000; |
| auth_basic "Admin Area"; |
| auth_basic_user_file /etc/nginx/.htpasswd; |
| } |
| } |
# 場景 3:WebSocket 支持
| map $http_upgrade $connection_upgrade { |
| default upgrade; |
| '' close; |
| } |
| |
| server { |
| listen 443 ssl http2; |
| server_name ws.example.com; |
| |
| ssl_certificate /etc/nginx/ssl/cert.pem; |
| ssl_certificate_key /etc/nginx/ssl/key.pem; |
| |
| location /socket.io/ { |
| proxy_pass http://node-app:3000; |
| proxy_http_version 1.1; |
| proxy_set_header Upgrade $http_upgrade; |
| proxy_set_header Connection $connection_upgrade; |
| proxy_set_header Host $host; |
| } |
| } |
# 場景 4:速率限制
| limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; |
| limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s; |
| |
| server { |
| listen 443 ssl http2; |
| server_name api.example.com; |
| |
| location / { |
| limit_req zone=general burst=20 nodelay; |
| proxy_pass http://backend; |
| } |
| |
| location /api/ { |
| limit_req zone=api burst=10 nodelay; |
| proxy_pass http://backend; |
| } |
| } |
# Docker 化部署
# 完整啟動
| |
| docker-compose up -d |
| |
| |
| docker-compose logs -f nginx |
| |
| |
| docker exec nginx-proxy nginx -s reload |
| |
| |
| docker-compose down |
# 配置變更流程
| |
| vim nginx/conf.d/default.conf |
| |
| |
| docker exec nginx-proxy nginx -t |
| |
| |
| docker exec nginx-proxy nginx -s reload |
| |
| |
| docker exec nginx-proxy tail -20 /var/log/nginx/error.log |
# 故障排查
# 1. 查看日誌
| |
| docker exec nginx-proxy tail -100 /var/log/nginx/access.log |
| |
| |
| docker exec nginx-proxy tail -50 /var/log/nginx/error.log |
| |
| |
| docker exec nginx-proxy tail -f /var/log/nginx/error.log |
# 2. 測試後端連接
| |
| docker exec nginx-proxy \ |
| wget -qO- http://backend1:80/health |
| |
| docker exec nginx-proxy \ |
| curl -v http://backend1:80/ |
# 3. Nginx 診斷命令
| |
| docker exec nginx-proxy nginx -t |
| |
| |
| docker exec nginx-proxy nginx -T | grep -A 20 "server {" |
| |
| |
| docker exec nginx-proxy ps aux | grep nginx |
| |
| |
| docker exec nginx-proxy netstat -tlnp | grep nginx |
# 4. 常見問題
| 問題 | 原因 | 解決方案 |
|---|
| 502 Bad Gateway | 後端服務不可達 | 檢查後端服務健康狀態,驗證網絡連接 |
| 504 Gateway Timeout | 代理超時 | 增加 proxy_read_timeout 或檢查後端性能 |
| SSL_ERROR_RX_RECORD_TOO_LONG | HTTP 請求訪問 HTTPS | 確保使用 https:// 訪問 |
| HSTS 錯誤 | HSTS 策略沖突 | 清除瀏覽器緩存或用無痕模式測試 |
| 無法獲得 SSL 証書 | 端口 80 不可達 | 檢查防火牆和端口轉發 |
# 性能優化
# 1. 啟用 HTTP/2
# 2. 套接字優化
| |
| echo "nginx soft nofile 65536" >> /etc/security/limits.conf |
| echo "nginx hard nofile 65536" >> /etc/security/limits.conf |
| systemctl restart nginx |
# 3. 監控性能指標
| |
| docker exec nginx-proxy \ |
| curl http://localhost:80/nginx_status |
| |
| |
| docker stats nginx-proxy |
# 最佳實踐
- 始終使用 HTTPS:設置 HTTP→HTTPS 自動重定向
- 啟用 HSTS:強制客戶端使用加密連接
- 限制請求大小:防止緩衝區溢出攻擊
- 配置日誌:記錄完整信息便於故障排查
- 定期備份配置:確保配置恢復能力
- 監控後端健康:及時發現故障服務
- 優化緩衝區:根據實際流量調整
- 版本更新:定期更新 Nginx 和依賴
# 完整示例倉庫
可參考官方文檔了解更多高級配置:
- Nginx 文檔:http://nginx.org/en/docs/
- Docker Hub Nginx:https://hub.docker.com/_/nginx
# 總結
通過 Docker Nginx 反向代理,你可以實現:
- ✅ 安全的 HTTPS 終止
- ✅ 透明的負載均衡
- ✅ 靈活的流量控制
- ✅ 高效的緩存策略
- ✅ 完整的監控和日誌
掌握這些配置,你就可以構建一個高效可靠的現代應用基礎設施。