| tags |
name |
description |
triggers |
version |
|
|
nginx |
Nginx configuration — reverse proxy, SSL/TLS, load balancing, caching, and security hardening. |
| nginx |
| 反向代理 |
| reverse proxy |
| 负载均衡 |
| ssl |
| letsencrypt |
| certbot |
|
1.0.0 |
Nginx
Directory Layout (Debian/Ubuntu)
/etc/nginx/
nginx.conf # main config
sites-available/ # site configs
sites-enabled/ # enabled sites (symlinks)
conf.d/ # extra config snippets
snippets/ # reusable config snippets
ssl/ # SSL certificates
Basic Site Config
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Reverse Proxy (API / Web App)
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
# Reverse proxy to local app
location / {
proxy_pass http://127.0.0.1:3000;
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-Port $server_port;
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Load Balancing (Upstream)
upstream backend {
least_conn; # least connections algorithm
server 10.0.0.2:3000 weight=3; # weight param
server 10.0.0.3:3000 weight=1;
server 10.0.0.4:3000 backup; # backup server
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Path-based Routing
server {
listen 443 ssl http2;
server_name example.com;
location /api/ {
proxy_pass http://127.0.0.1:8000/; # trailing slash strips /api/
}
location /blog/ {
proxy_pass http://127.0.0.1:8080/;
}
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Rate Limiting
# Define rate limit zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
}
}
Caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
location /api/ {
proxy_cache api_cache;
proxy_cache_valid 200 5m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://127.0.0.1:8000;
}
}
Security Hardening
# Hide nginx version
server_tokens off;
# Security headers
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;
add_header Content-Security-Policy "default-src 'self'; script-src 'self';" always;
# Block sensitive paths
location ~ /\. {
deny all;
}
Commands
# Test config (always do this before reload)
nginx -t
# Reload (no downtime)
sudo nginx -s reload
# Stop / restart
sudo nginx -s stop
sudo nginx -s restart
# Full syntax check including included files
nginx -T
Certbot (Let's Encrypt)
# Install
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renew (already set up by package, but verify)
sudo certbot renew --dry-run
# Check renewal timer
sudo systemctl list-timers | grep certbot
Troubleshooting
| Problem |
Fix |
nginx: [emerg] could not build |
Run nginx -t to see syntax error with line number |
connect() failed |
Backend service not running — check curl localhost:PORT/health |
| SSL errors |
Verify cert/key paths, check openssl x509 -in crt -text -noout |
| 502 Bad Gateway |
Proxy pass endpoint unreachable, check firewall/security groups |
| Redirect loop |
proxy_set_header X-Forwarded-Proto mismatch |
| Slow downloads |
Check send_timeout, large client header buffers |
Access Log Analytics
Custom Log Format
For real-time request analytics, define a custom log_format outside the server block (at the http level or before any server block):
# 在 http 块或 conf 顶部定义
log_format analytics '$http_x_real_ip - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
server {
...
access_log /var/log/nginx/app_access.log analytics;
}
字段说明:
| 字段 |
说明 |
使用场景 |
$remote_addr |
客户端 IP |
基础统计 |
$http_x_real_ip |
上游设置的客户端真实 IP |
有反向代理时用(如 nginx → gunicorn → app) |
$http_x_forwarded_for |
代理链 IP 列表 |
多层代理追踪 |
$request |
"METHOD /path HTTP/1.1" |
路由统计 |
$status |
HTTP 状态码 |
错误率监控 |
$body_bytes_sent |
响应体大小 |
流量统计 |
$request_time |
请求处理耗时(秒) |
性能监控 |
Python Log Parser (Flask API)
配合 Flask 路由,读取 nginx 或 gunicorn access log 解析统计数据:
import re, os, json
from datetime import datetime
@app.route('/api/stats')
def api_stats():
access_log = '/var/log/nginx/app_access.log'
if not os.path.exists(access_log):
return jsonify({'error': 'no log file'})
with open(access_log) as f:
lines = f.readlines()
total = len(lines)
today_str = datetime.now().strftime('%d/%b/%Y')
today_lines = [l for l in lines if today_str in l]
# 按天统计
daily = {}
for line in lines:
m = re.search(r'\[(\d+)/(\w+)/(\d{4})', line)
if m:
day = f'{m.group(3)}-{m.group(2)}-{m.group(1)}'
daily[day] = daily.get(day, 0) + 1
# 按 API 路径统计
api_calls = {}
for line in lines:
m = re.search(r'"(?:GET|POST|PUT|DELETE) (/\S+)', line)
if m:
path = m.group(1)
api_calls[path] = api_calls.get(path, 0) + 1
return jsonify({
'total_requests': total,
'daily': dict(sorted(daily.items())),
'today': {'requests': len(today_lines)},
'api_calls': dict(sorted(api_calls.items())),
})
⚠️ 常见陷阱
$remote_addr 在反向代理下不真实: nginx 代理到 gunicorn 时 $remote_addr 可能是 127.0.0.1。必须在上游(第一个 nginx)设 proxy_set_header X-Real-IP $remote_addr,日志记录用 $http_x_real_ip。
- gunicorn 自己的 access_log: 如果 gunicorn 也配了
--access-logfile,那里记录的是 proxy 到 app 的流量。两套日志要区分用途:nginx 日志看外部流量,gunicorn 日志看内部请求。
- 文件轮转: access_log 文件应当配置 logrotate,防止单文件无限增长撑满磁盘。
Useful Snippets
Basic auth protection
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
Generate .htpasswd
# Create (first user)
sudo htpasswd -c /etc/nginx/.htpasswd admin
# Add user
sudo htpasswd /etc/nginx/.htpasswd anotheruser
Force HTTPS
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
Gzip compression
gzip on;
gzip_types text/plain application/json application/javascript text/css;
gzip_min_length 1000;