Nginx配置踩坑记录
Nginx用了很多年了,从简单的静态站点到复杂的反向代理,踩过不少坑。记录一下常见配置和遇到的问题。
基础配置
配置文件结构
Nginx主配置文件通常位于/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
| user nginx; worker_processes auto;
events { worker_connections 1024; }
http { include mime.types; default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log;
sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
|
核心指令
| 指令 |
上下文 |
说明 |
示例 |
| server |
http |
定义虚拟主机 |
server { ... } |
| location |
server |
定义请求处理规则 |
location / { ... } |
| listen |
server |
监听端口 |
listen 80; |
| server_name |
server |
服务器名称 |
server_name example.com; |
| root |
location |
网站根目录 |
root /var/www/html; |
| proxy_pass |
location |
反向代理地址 |
proxy_pass http://backend; |
虚拟主机配置
单站点配置
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
| server { listen 80; server_name example.com www.example.com;
root /var/www/example.com; index index.html index.htm;
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 30d; add_header Cache-Control "public, immutable"; }
location / { try_files $uri $uri/ /index.html; }
location ~ /\. { deny all; access_log off; log_not_found off; } }
|
多虚拟主机
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
| server { listen 80; server_name example.com; root /var/www/example.com; index index.html; }
server { listen 80; server_name blog.example.com; root /var/www/blog; index index.php;
location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
server { listen 80; server_name api.example.com;
location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
|
反向代理
基础反向代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server { listen 80; server_name app.example.com;
location / { proxy_pass http://localhost:8080;
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_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } }
|
WebSocket代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server { listen 80; server_name ws.example.com;
location /socket.io/ { proxy_pass http://localhost:3000; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400; } }
|
代理第三方CDN资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 80; server_name myapp.example.com;
location ^~ /third_image/ { add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
proxy_pass https://media.licdn.com/; proxy_set_header Referer ""; proxy_hide_header X-Frame-Options; } }
|
负载均衡
负载均衡算法
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
| upstream backend { server 192.168.1.10:8080 weight=5; server 192.168.1.11:8080 weight=5;
ip_hash;
least_conn;
server 192.168.1.12:8080 backup; server 192.168.1.13:8080 down; }
server { listen 80; server_name lb.example.com;
location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
|
负载均衡策略对比
| 策略 |
指令 |
适用场景 |
| 轮询 |
默认 |
无状态服务,服务器性能相近 |
| 加权轮询 |
weight= |
服务器性能不均衡 |
| IP哈希 |
ip_hash |
需要会话保持 |
| 最少连接 |
least_conn |
长连接服务 |
| 一致性哈希 |
hash $request_uri |
缓存场景 |
HTTPS配置
HTTP跳转HTTPS
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
| server { listen 80; server_name example.com www.example.com;
return 301 https://$server_name$request_uri; }
server { listen 443 ssl http2; server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key;
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;
root /var/www/example.com; index index.html; }
|
自动升级不安全请求
1 2 3 4 5 6 7 8 9 10 11 12
| server { listen 443 ssl; server_name example.com;
add_header Content-Security-Policy "upgrade-insecure-requests" always;
location / { root /var/www/example.com; try_files $uri $uri/ /index.html; } }
|
错误页面处理
自定义错误页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| server { listen 80; server_name example.com;
error_page 404 /404.html; error_page 500 502 503 504 /50x.html;
location = /404.html { root /var/www/errors; internal; }
location = /50x.html { root /var/www/errors; internal; }
location / { root /var/www/example.com; try_files $uri $uri/ =404; } }
|
代理后端错误页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| server { listen 80; server_name app.example.com;
proxy_intercept_errors on;
location / { proxy_pass http://backend; proxy_set_header Host $host; }
error_page 404 = @notfound; location @notfound { return 301 https://www.baidu.com; } }
|
URL重写与重定向
rewrite指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server { listen 80; server_name example.com;
location = /play { proxy_pass http://example.com/play.html; }
location = /old-page { return 301 /new-page; }
location /products/ { rewrite ^/products/(.*)$ /items/$1 last; } }
|
rewrite标志
| 标志 |
说明 |
| last |
重写后重新匹配location |
| break |
重写后停止处理 |
| redirect |
302临时重定向 |
| permanent |
301永久重定向 |
跨域处理(CORS)
基础跨域配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| server { listen 80; server_name api.example.com;
location / { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 1728000 always;
if ($request_method = 'OPTIONS') { return 204; }
proxy_pass http://localhost:3000; } }
|
动态跨域配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| map $http_origin $cors_origin { default ""; "~^https?://localhost(:[0-9]+)?$" $http_origin; "~^https?://.*\.example\.com$" $http_origin; "https://trusted-site.com" $http_origin; }
server { listen 80; server_name api.example.com;
location / { add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
if ($request_method = 'OPTIONS') { return 204; }
proxy_pass http://localhost:3000; } }
|
性能优化
静态文件优化
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 { listen 80; server_name static.example.com; root /var/www/static;
gzip on; gzip_vary on; gzip_types text/plain text/css application/json application/javascript text/xml; gzip_min_length 1000;
location ~* \.(jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; }
location ~* \.(css|js)$ { expires 30d; add_header Cache-Control "public"; access_log off; }
location ~* \.html$ { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate"; } }
|
连接优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| events { worker_processes auto; worker_connections 4096; use epoll; multi_accept on; }
http { sendfile on; tcp_nopush on; tcp_nodelay on;
keepalive_timeout 30; keepalive_requests 1000;
client_max_body_size 50m; client_body_buffer_size 128k; client_header_buffer_size 1k;
open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; }
|
安全加固
安全响应头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 443 ssl; server_name example.com;
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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / { root /var/www/example.com; index index.html; } }
|
访问控制
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
| server { listen 80; server_name admin.example.com;
allow 192.168.1.0/24; allow 10.0.0.0/8; deny all;
location /admin { auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; try_files $uri $uri/ =404; }
location /api/ { limit_req zone=api_limit burst=10 nodelay; limit_conn addr_limit 10; proxy_pass http://backend; } }
http { limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_conn_zone $binary_remote_addr zone=addr_limit:10m; }
|
坑1:location匹配顺序
Nginx location匹配顺序很重要:
= 精确匹配
^~ 前缀匹配(匹配后不再检查正则)
~ 和 ~* 正则匹配
- 普通前缀匹配
1 2 3 4 5
| location = /exact { location ^~ /static { location ~ \.php$ { location / { }
|
坑2:proxy_pass斜杠问题
1 2 3 4 5 6 7 8 9
| location /api/ { proxy_pass http://backend/; }
location /api/ { proxy_pass http://backend; }
|
坑3:配置重载不生效
有时候改了配置重载没效果:
1 2 3 4 5 6 7 8
| nginx -t
sudo systemctl restart nginx
sudo nginx -s reload
|
总结
Nginx配置常见坑点:
- location匹配顺序要注意,精确匹配放前面
- proxy_pass斜杠有无区别大
- HTTPS配置要配全,包括中间证书
- 静态文件缓存要合理设置过期时间
- 跨域配置要注意预检请求(OPTIONS)处理
- 配置修改后要restart,reload有时候不生效
Nginx配置看着简单,但细节很多。建议每次修改后都用nginx -t检查语法。