本文实现了一套安全、模块化、生产端的 Nginx 反向代理方案,核心功能包括:
- 统一代理支持:通过 Nginx 同时提供 HTTP 和 TCP 反向代理;
- 真实客户端信息透传:使用标准代理头(如 X-Real-IP、X-Forwarded-For)确保后端服务获取原始请求信息;
- 跨域控制(CORS):允许所有域名跨域请求,但仅限 IP 白名单中的客户端;
- 独立 IP 白名单:通过单独的 geo 配置文件实现访问控制,默认拒绝所有非白名单 IP,支持动态更新无需重启;
- 全局限制策略:统一设置最大上传文件大小(10MB)和响应空闲超时(60秒);
- 按需缓存与维护模式:仅对 /api/ 接口启用缓存,并支持通过文件开关快速切换维护页面。
整套方案采用配置分离、职责清晰、Docker 友好的设计,兼顾安全性、可维护性与高性能,适用于企业级生产环境。
📁 整体目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| /etc/nginx/ ├── nginx.conf ├── conf.d/ │ ├── http.conf │ ├── db.stream │ └── maintenance.conf ├── security/ │ ├── blacklist.conf │ ├── whitelist.conf │ ├── rate-limiting.conf │ ├── security_rules.conf │ ├── security_checks.conf │ ├── blacklist.ip │ └── whitelist.ip ├── proxy_params ├── cache_params └── cors_params
|
1. 统一配置
1.1 TCP和HTTP配置(/etc/nginx/nginx.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| stream { include /etc/nginx/conf.d/*.stream; }
http { include /etc/nginx/mime.types; default_type application/octet-stream;
sendfile on; tcp_nopush on; tcp_nodelay on; gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain application/json text/css application/javascript; keepalive_timeout 65; keepalive_requests 1000; types_hash_max_size 2048; server_tokens off; client_header_buffer_size 1k; large_client_header_buffers 4 8k;
proxy_connect_timeout 30s; proxy_send_timeout 300s; proxy_read_timeout 300s;
proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; proxy_temp_file_write_size 8k; client_max_body_size 500M; client_body_buffer_size 1m; client_body_temp_path /tmp/nginx_body; client_body_timeout 300s; proxy_request_buffering on;
set $block_access 0; set $block_reason "";
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'rt=$request_time uct="$upstream_connect_time" ' 'uht="$upstream_header_time" urt="$upstream_response_time"';
log_format security 'time=$time_iso8601 client=$realip_remote_addr ' 'xff="$http_x_forwarded_for" method=$request_method ' 'uri="$request_uri" status=$status ' 'user_agent="$http_user_agent" ' 'blocked=$block_access block_reason=$block_reason ' 'rate_limit=$rate_limit_zone cache_status=$upstream_cache_status';
log_format json_combined escape=json '{"time":"$time_iso8601",' '"remote_addr":"$realip_remote_addr",' '"x_forwarded_for":"$http_x_forwarded_for",' '"method":"$request_method",' '"uri":"$request_uri",' '"status":$status,' '"body_bytes_sent":$body_bytes_sent,' '"request_time":$request_time,' '"http_referrer":"$http_referer",' '"http_user_agent":"$http_user_agent",' '"is_whitelisted":$whitelist_ip,' '"is_blacklisted":$blacklist_ip}';
access_log /var/log/nginx/access.log main buffer=32k flush=5s; access_log /var/log/nginx/security.log security; access_log /var/log/nginx/audit/audit-$(date +%Y-%m-%d).log json_combined; error_log /var/log/nginx/error.log warn;
set_real_ip_from 10.0.0.0/8; set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on;
include proxy_params;
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m use_temp_path=off; proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:100m max_size=10g inactive=365d use_temp_path=off;
limit_conn_zone $binary_remote_addr zone=per_ip:10m; limit_conn_zone $server_name zone=per_server:10m; include /etc/nginx/security/*.conf;
include /etc/nginx/conf.d/*.conf; }
|
💡 关键配置:
- TCP 代理独立在
stream 块(不能与 HTTP 混合)
- 全局上传大小/超时设置在
http 块(避免重复配置)
- 缓存路径全局定义,独立配置文件只指定作用域
- 缓存路径
/var/cache/nginx 需在 Docker 容器中挂载(-v /host/cache:/var/cache/nginx)
1.2 传递客户端的真实信息配置(/etc/nginx/proxy_params)
1 2 3 4 5 6 7 8 9
| 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; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Server $hostname;
|
1.3 HTTP接口缓存配置(/etc/nginx/cache_params)
1 2 3 4 5 6 7
| proxy_cache api_cache; proxy_cache_valid 200 302 1d; proxy_cache_valid 404 1m; proxy_cache_use_stale error timeout updating; proxy_cache_background_update on; proxy_cache_lock on;
|
💡 缓存机制:
- 200/302 响应缓存 1 天(86400 秒)
- 404 响应缓存 1 分钟
- 缓存过期后,若后端返回 200 则更新缓存
1.4 CORS跨域控制配置/etc/nginx/cors_params)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-SinceCache-Control,Content-Type,Authorization' always;
if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-WithIf-Modified-Since,Cache-Control,Content-Type,Authorization' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; }
|
💡 关键配置:
always 参数确保响应头始终添加(避免缓存干扰)
2. 独立代理配置文件(/etc/nginx/conf.d/)
✅ 2.1 HTTP 代理(http.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 80; server_name api.example.com;
location / { if ($rate_limit_exempt != 1) { limit_req zone=$rate_limit_zone burst=20 nodelay; limit_req_status 429; } proxy_pass http://backend:8080; include cache_params; } }
|
✅ 2.2 TCP代理(db.conf)
1 2 3 4 5 6 7 8 9 10 11
| upstream mysql_backend { server db:3306; }
server { listen 3306; proxy_pass mysql_backend; proxy_connect_timeout 1s; proxy_timeout 10s; } }
|
✅ 2.3 维护界面(maintenance.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| server { set $maintenance_mode off; if ($maintenance_mode = on) { return 503; } error_page 503 @maintenance; location @maintenance { root /usr/share/nginx/html; try_files /maintenance.html =503; } location /api/ { if ($maintenance_mode = on) { add_header Content-Type application/json; return 503 '{"status":503,"message":"系统维护中,请稍后再试"}'; } proxy_pass http://backend-service:8080; } }
|
3. 安全配置文件(/etc/nginx/security/)
✅ 3.1 限流控制(rate-limiting.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
limit_req_zone $binary_remote_addr zone=global_limit:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s; limit_req_zone $binary_remote_addr zone=login_limit:10m rate=3r/m; limit_req_zone $binary_remote_addr zone=search_limit:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=static_limit:10m rate=500r/s;
map $request_uri $rate_limit_zone { default global_limit; ~*/api/v1/ api_limit; ~*/api/v2/ api_limit; ~*/auth/login login_limit; ~*/oauth/token login_limit; ~*/user/login login_limit; ~*/search search_limit; ~*/query search_limit; ~*/find search_limit; ~*\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ static_limit; ~*/admin/ global_limit; ~*/system/ global_limit; }
map $whitelist_ip $rate_limit_exempt { 0 0; 1 1; }
limit_conn per_ip 10; limit_conn per_server 1000;
|
✅ 3.2 黑白名单控制(blacklist.conf和whitelist.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
geo $blacklist_ip { default 0; include /etc/nginx/security/blacklist.ip; 10.0.0.0/8 1; 192.168.1.100 1; 172.16.0.0/12 1; }
geo $whitelist_ip { default 0; include /etc/nginx/security/whitelist.ip; 10.0.0.0/8 1; 192.168.1.100 1; 172.16.0.0/12 1; }
map $request_uri $whitelist_path { default 0; /health 1; /status 1; /ping 1; /metrics 1; /nginx-status 1; ~*\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|mp4|webm|mp3)$ 1; /api/v1/public/ 1; /api/v1/docs 1; /swagger-ui 1; /robots.txt 1; /sitemap.xml 1; /favicon.ico 1; }
|
1 2 3 4 5 6 7 8 9 10
|
203.0.113.1 1; 198.51.100.0/24 1; 10.20.30.40 1;
|
✅ 3.3 综合安全控制(blacklist.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
map $http_user_agent $bad_bot { default 0; ~*(ahrefs|mj12bot|semrushbot|masscan) 1; ~*(sqlmap|nmap|nikto|dirbuster) 1; ~*(nessus|openvas|acunetix|nessus) 1; ~*(spammer|scraper|harvest|collector) 1; ~*[0-9]{10}bot 1; ~*bot[0-9]{10} 1; "" 1; ~*(baiduspider|googlebot|bingbot|yandexbot|duckduckbot) 0; ~*(slurp|twitterbot|facebookexternalhit|linkedinbot) 0; ~*(Mozilla|Chrome|Safari|Firefox|Edge|Opera) 0; ~*(curl|wget|python-requests|PostmanRuntime) 0; }
map $request_uri $bad_request { default 0; ~*(\.\./|\.\.\\|/%2e%2e|/%252e%252e) 1; ~*(/etc/passwd|/etc/shadow|/proc/self|/etc/hosts) 1; ~*(\.git/|\.svn/|\.hg/|\.bzr/|\.DS_Store) 1; ~*(\.env|config\.php|settings\.py|web\.config) 1; ~*(phpmyadmin|adminer|wp-admin|/admin/login|/manager/html) 1; ~*(union\s+select.*from) 1; ~*(sleep\(|benchmark\(|waitfor\s+delay) 1; ~*(load_file\(|into\s+outfile|into\s+dumpfile) 1; ~*(select\s+.*from.*where.*=.*\() 1; ~*(<script|javascript:|onload=|onerror=|alert\(|confirm\(|prompt\() 1; ~*(\|\s*sh|\|\s*bash|\|\s*cmd|\;\s*rm\s+|\;\s*wget\s+) 1; ~*\.(asp|aspx|jsp|php|pl|py|sh|cgi|exe|dll|bat|cmd)$ 1; ~*(/w00tw00t|test.cgi|xmlrpc.php|/vendor/phpunit) 1; }
map $block_reason $block_error_page { default /errors/403.html; "blacklist_ip" /errors/403.html; "bad_bot" /errors/444.html; "bad_request" /errors/403.html; "rate_limit" /errors/429.html; "whitelist_only" /errors/403.html; }
map "$whitelist_ip:$maintenance_mode" $access_allowed { "~^1:.+" 1; default 0; }
map $maintenance_mode $is_maintenance { default 0; "on" 1; "yes" 1; "true" 1; "1" 1; }
|
✅ 3.4 安全检查(security-checks.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
set $block_reason ""; set $block_access 0;
if ($whitelist_path = 1) { set $block_access 0; return 200; }
if ($access_allowed = 0) { set $block_access 1; if ($whitelist_ip = 0) { set $block_reason "whitelist_only"; } if ($maintenance_mode = "on" && $whitelist_ip = 0) { set $block_reason "maintenance_mode"; return 503; } }
if ($whitelist_ip != 1) { if ($blacklist_ip = 1) { set $block_access 1; set $block_reason "blacklist_ip"; access_log /var/log/nginx/blocked.log security; return 403; } }
if ($bad_bot = 1) { set $block_access 1; set $block_reason "bad_bot"; access_log /var/log/nginx/bad_bots.log combined; return 444; }
if ($bad_request = 1) { set $block_access 1; set $block_reason "bad_request"; access_log /var/log/nginx/attack.log combined; return 403; }
|
🐳 Docker 部署关键配置
1. docker-compose.yml 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| version: '3.8'
services: nginx: build: . container_name: nginx-proxy restart: unless-stopped ports: - "80:80" - "443:443" environment: - NGINX_ENV=production - BACKEND_SERVICE=backend:8080 - MAINTENANCE_MODE=${MAINTENANCE_MODE:-off} - API_KEY_WHITELIST=${API_KEY_WHITELIST:-} - INTERNAL_NETWORK=172.16.0.0/12 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./conf.d:/etc/nginx/conf.d:ro - ./security:/etc/nginx/security:ro - ./templates:/etc/nginx/templates:ro - ./ssl:/etc/nginx/ssl:ro - ./html:/usr/share/nginx/html:ro - ./logs:/var/log/nginx - ./scripts:/scripts:ro - dynamic_data:/etc/nginx/dynamic networks: - proxy-network healthcheck: test: ["CMD", "/scripts/health-check.sh"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3"
backend: image: ${BACKEND_IMAGE:-your-backend:latest} container_name: backend-app restart: unless-stopped environment: - VIRTUAL_HOST=app.example.com - VIRTUAL_PORT=8080 networks: - proxy-network expose: - "8080"
networks: proxy-network: driver: bridge ipam: config: - subnet: 172.16.0.0/12
volumes: dynamic_data:
|
2. nginx容器Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| FROM nginx:1.24-alpine
RUN apk add --no-cache \ curl \ jq \ certbot \ certbot-nginx \ bash \ gettext \ openssl \ geoip \ geoip-dev \ libmaxminddb \ libmaxminddb-dev \ && rm -rf /var/cache/apk/*
RUN apk add --no-cache nginx-mod-http-geoip2
RUN mkdir -p \ /etc/nginx/security \ /etc/nginx/dynamic \ /etc/nginx/templates \ /usr/share/nginx/html/errors \ /scripts \ /var/log/nginx/audit
COPY nginx.conf /etc/nginx/ COPY conf.d/ /etc/nginx/conf.d/ COPY security/ /etc/nginx/security/ COPY templates/ /etc/nginx/templates/ COPY ssl/ /etc/nginx/ssl/ COPY html/ /usr/share/nginx/html/ COPY scripts/ /scripts/
RUN chmod +x /scripts/*.sh \ && chown -R nginx:nginx /var/log/nginx \ && chmod 600 /etc/nginx/security/.htpasswd \ && chmod 644 /etc/nginx/security/*.map \ && chmod 755 /etc/nginx/dynamic
RUN if [ ! -f /etc/nginx/ssl/cert.pem ]; then \ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/key.pem \ -out /etc/nginx/ssl/cert.pem \ -subj "/C=CN/ST=State/L=City/O=Organization/CN=localhost"; \ fi
RUN if [ ! -f /etc/nginx/ssl/dhparam.pem ]; then \ openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048; \ fi
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD /scripts/health-check.sh
ENTRYPOINT ["/scripts/entrypoint.sh"]
|
🛠️ 生产环境验证建议
测试跨域:
1
| curl -H "Origin: https://frontend.example.com" -X OPTIONS http://api.example.com/api/test
|
应返回 204 No Content 且包含 CORS 头
测试维护模式:
1 2 3 4 5 6
| export MAINTENANCE_MODE=on
curl -I http://example.com
|
测试缓存:
1 2 3 4 5
| curl -I http://api.example.com/api/data
curl -I http://api.example.com/api/data
|
✅ 总结
| 功能 |
位置 |
作用范围 |
Docker 适配关键点 |
| TCP 代理 |
nginx.conf (stream) |
全局 |
独立于 HTTP,端口映射 3306:3306 |
| 上传大小/超时 |
nginx.conf (http) |
全局 |
全局生效,避免重复配置 |
| HTTP 代理 |
http_proxy.conf |
按域名生效 |
通过 Docker 网络解析服务名 |
| 跨域控制 |
cors.conf |
仅 /api/ |
always 参数确保头始终添加 |
| 接口缓存 |
cache.conf |
仅 /api/ |
缓存路径挂载到主机,避免容器重启丢失 |
| 维护界面 |
maintenance.conf |
仅根路径 |
通过文件开关,无需重启 Nginx |
💡 为什么这样设计?
- 全局配置:TCP 和基础参数(上传/超时)放在全局,避免重复且易于全局调整
- 独立文件:每个功能模块独立配置,符合“单一职责原则”
- Docker 适配:
- 所有挂载路径(
/var/cache/nginx, /var/www/maintenance)在主机定义
- 服务名通过 Docker 网络自动解析(
backend:8080)
- 维护模式通过文件开关,无需修改 Nginx 配置