ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

Nginx 配置实战:反向代理、负载均衡、HTTPS 一次讲清

Nginx 配置实战:反向代理、负载均衡、HTTPS 一次讲清 开场你的 Node.js 应用跑在127.0.0.1:3000产品说用户访问api.example.com能用就行。你打开 Nginx 配置文件复制一段网上的配置reload结果要么 404 要么 502。这个场景我见过不止一次。Nginx 配置的语法看起来很简单文档也写得不算差但location和proxy_pass那几个斜杠能把人卡半天。不是你不行是这两个东西的行为确实有坑。这篇文章不是入门科普是给那些已经部署过应用、知道 Nginx 能当反向代理、但配着配着就出问题的人写的。读完之后你配一个新的 Nginx 反代站点基本能做到一次过不用反复 502。一、Nginx 在你架构里干什么先搞清楚 Nginx 站在哪里。外部请求先到 NginxNginx 再把请求转发给你后头的应用。客户端不知道你后端用的是 Node、Java 还是 Python它只跟 Nginx 说话。Nginx 在这中间扮演的是反向代理的角色。这里插一句正向代理和反向代理的区别只需要记一句话就够了——正向代理是替客户端做事你上 Google 是让代理帮你拿反向代理是替服务端做事客户端以为自己在直接访问服务端但其实是服务端派的代理在接单。这个区别面试会问但配 Nginx 的时候不需要想太多。除了反向代理Nginx 还能干这些事静态文件服务。你的 React/Vue 构建产物直接丢到服务器目录Nginx 配个root就能跑不用走 Node 服务。负载均衡。多台后端机器搭一个 upstream 块Nginx 自动把流量分散过去。SSL 终结。HTTPS 在 Nginx 这一层解密后端跑明文 HTTP减少后端的加密计算负担。限流。limit_req_zone和limit_conn_zone配一配能防住一部分爬虫和 CC 攻击。我的观点是小项目PV 百万以下、单台机器能扛住的直接用 Nginx 反代加静态托管就够了。别一上来就想着上 Kong、Traefik 或者 Istio那些东西的学习成本和运维成本不低你的业务还没到那个量级之前Nginx 能解决大部分问题。二、配置文件长什么样Nginx 的配置文件有一个固定的层级结构全局块 ← 启动用户、工作进程数等全局设置 events ← 工作连接相关配置 http ← HTTP 全局配置可包含多个 server server ← 一个虚拟主机 location ← 匹配 URL 路径实际文件里长这样user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/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; sendfile on; keepalive_timeout 65; server { listen 80; server_name example.com; location / { root /usr/share/nginx/html; index index.html; } } }配置文件路径在主流 Linux 发行版里通常是/etc/nginx/nginx.conf自定义的站点配置放在/etc/nginx/conf.d/或者通过include conf.d/*.conf引入。Ubuntu/Debian 系统还有sites-available/sites-enabled这套目录用a2ensite命令管理其实底层也是 include。配完之后有两个命令必须记住nginx-t# 测试配置文件语法有错会报错nginx-sreload# 热重载不中断现有连接踩坑点很多人改完配置直接nginx -s reload结果配置文件有语法错误Nginx 启动失败旧配置也被你 reload 掉了线上直接 502。正确做法永远是nginx -t确认配置无误之后再nginx -s reload。这条规矩没有例外。三、反向代理最常用的玩法反向代理是 Nginx 最核心的用法。一段最小可用的反代配置长这样server { listen 80; server_name api.example.com; 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_pass后面那个斜杠的问题你必须搞清楚。proxy_pass 斜杠不带 / 和带 / 有什么区别这是高频踩坑点。两种写法路径拼接规则完全不同写法一不带斜杠location /api/ { proxy_pass http://127.0.0.1:3000; # 不带斜杠 }请求/api/users→ 转发到http://127.0.0.1:3000/api/users写法二带斜杠location /api/ { proxy_pass http://127.0.0.1:3000/; # 带斜杠 }请求/api/users→ 转发到http://127.0.0.1:3000/users规则是带/意味着 Nginx 会把 location 匹配上的那部分路径从转发目标里去掉。两种写法差了api这几个字符够让你的后端应用收到 404 了。写法三proxy_pass 指向一个 URIlocation /api/ { proxy_pass http://127.0.0.1:3000/v2/; # 带路径替换 }请求/api/users→ 转发到http://127.0.0.1:3000/v2/usersproxy_pass后面跟了路径等于做了一次路径替换。这种写法适合你的后端 API 路径和前端路径不一致的场景。三个 Header 为什么要设proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;这三个 Header 不是可选项是必选项。Host $host让后端知道请求的是哪个域名。不然你后端可能有虚拟主机多个站点混布的时候就会拿错配置。X-Real-IP把客户端的真实 IP 传给后端。Nginx 拿到的是客户端 IP$remote_addr就是这个值。不然你的应用日志里全是127.0.0.1。X-Forwarded-For如果请求已经经过一层代理这个 Header 记录了整条链路上的 IP 链。$proxy_add_x_forwarded_for会在已有值后面追加当前 IP防止 IP 伪造。如果你的后端是 Java/Spring通常还要加一个X-Forwarded-Proto来让后端知道前端是走 https 还是 http否则你用request.getScheme()会拿到http而不是实际协议。四、location 匹配规则重点很多人配 Nginx 出问题80% 死在 location 匹配规则上。location 的匹配方式有四种每种有自己的优先级匹配类型语法说明精确匹配location /path只匹配这一个路径前缀匹配location ^~ /path匹配以此开头的路径不继续正则匹配正则匹配location ~ /path或location ~* /path~区分大小写~*不区分普通前缀location /path通用匹配最不优先匹配优先级从高到低精确匹配 前缀匹配^~ 正则匹配 普通前缀。Nginx 在匹配 location 的时候是按这个顺序来遍历的。一旦在某个优先级上命中就不会再往下看了。举例说明假设你配了这样一组 locationlocation / { return 200 精确匹配 /\n; } location ^~ /static/ { return 200 前缀匹配 /static/\n; } location ~ \.php$ { return 200 正则匹配 .php$\n; } location / { return 200 普通前缀 /\n; }不同请求会命中哪个请求/→ 命中精确匹配 /请求/static/css/app.css→ 命中前缀匹配^~ /static/请求/index.php→ 命中正则匹配~ .php$正则优先级高于普通前缀请求/about→ 命中普通前缀/这里有一个坑正则匹配是按配置文件里出现的顺序逐个匹配的一旦匹配上就停不会再看后面的。所以正则的顺序很重要。实战建议能用前缀匹配就别用正则。location /api/、location /static/、location /health这些前缀匹配已经能覆盖大部分场景了。正则只有在需要模式匹配的时候才用比如location ~ \.(jpg|png|gif)$匹配特定后缀而且写的时候要注意顺序靠前的正则优先。精确匹配用于特殊路径。比如健康检查接口/health、根路径/这类固定路径用精确匹配最稳妥不会被其他规则误伤。五、负载均衡一台不够就上多台当单台后端扛不住流量的时候就要上负载均衡了。Nginx 通过upstream块定义一组后端服务器然后用proxy_pass指向这个 upstream。基本配置upstream backend { server 10.0.0.2:3000 weight3; server 10.0.0.3:3000 weight2; server 10.0.0.4:3000 backup; } 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }几种负载均衡策略轮询默认每个请求依次分发给下一台机器不做任何额外判断。适合后端机器配置一致、请求处理时间相近的场景。权重weight数字越大分到的请求越多。上面配置里10.0.0.2权重 310.0.0.3权重 2所以 2 的请求量大约是 3 的 1.5 倍。适合机器配置不一致的场景配置好的机器权重设高一些。ip_hash同一个客户端 IP 的请求永远落到同一台后端机器。这个策略用于解决会话粘性——如果你的后端应用没有独立的 session 存储比如没用 Redis多台机器之间 session 不共享用户登录后会话就丢了。ip_hash能绑住同一个 IP 的请求。least_conn优先把请求发给当前连接数最少的机器适合请求处理时间差异较大的场景。自动故障转移upstream backend { server 10.0.0.2:3000; server 10.0.0.3:3000; } server { location / { proxy_pass http://backend; proxy_next_upstream error timeout http_500 http_502 http_503; } }proxy_next_upstream的意思是当指定的情况发生时后端报错、连接超时、返回 5xx自动把请求转发给 upstream 里下一台可用的服务器。这样做的好处是对用户来说请求仍然可能成功不需要你手动去修后端机器。踩坑ip_hash 和后端扩缩容这是用 ip_hash 时必须知道的问题。ip_hash 的原理是把 IP 映射到后端服务器的编号上。后端机器数量不变的时候这个映射是稳定的。但如果你在不停机的情况下加机器或者减机器原来的哈希映射就全乱了——原来落在 A 机器的请求现在可能落到 B 机器而 B 机器上没有对应的 session用户就掉线了。所以用 ip_hash 的时候后端扩缩容要小心。如果你的应用对会话要求高最好把 session 放到 Redis 等独立存储里然后负载均衡策略用轮询或者 least_conn这样无论后端怎么增减都不影响已有会话。六、HTTPS现在标配HTTP 明文传输的时代早就该结束了。浏览器现在会给非 HTTPS 站点标不安全很多 API 不支持Mixed ContentSEO 也受影响。新项目直接上 HTTPS别等出问题了再补。Let’s Encrypt 免费证书Let’s Encrypt 提供的证书免费、自动化、三个月有效期用 certbot 工具申请非常方便# Ubuntu/Debiansudoaptinstallcertbot python3-certbot-nginxsudocertbot--nginx-dapi.example.com# 按提示操作certbot 自动申请证书并写入 Nginx 配置certbot 会自动修改你的 Nginx 配置加好listen 443 ssl和证书路径还会顺手配好 HTTP 跳 HTTPS 的重定向。手动配 HTTPS如果不用 certbot 或者 certbot 不支持你的环境手动配也不复杂server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers on; 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; } } # HTTP 强制跳 HTTPS server { listen 80; server_name api.example.com; return 301 https://$host$request_uri; }ssl_protocols里禁用了 TLS 1.0 和 1.1这两个版本有已知漏洞已经不安全了。如果你的业务不需要兼容特别老的客户端就别开它们。踩坑证书到期忘了续Let’s Encrypt 证书有效期是 90 天如果忘了续站点直接变成不安全的。有些项目当时用 certbot 配了但自动续期没跑起来时间一长证书过期了才发现。装完 certbot 之后确认一下自动续期有没有配好sudocertbot renew --dry-run--dry-run会模拟一次续期不实际执行。如果这条命令报错了说明自动续期没配好。正常情况下 certbot 会装一个 cron 任务或者 systemd timer 自动续期但你得确认它确实在跑。七、静态文件与缓存对于前端项目静态资源没必要走 Node/Python 服务让 Nginx 直接处理更快、更省资源。server { listen 80; server_name example.com; # 静态资源Nginx 直接服务 location /static/ { root /var/www/myapp; expires 30d; # 缓存 30 天 add_header Cache-Control public, immutable; } # 上传文件或用户内容 location /uploads/ { root /var/www/myapp; expires 7d; } # 剩下的走反代 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; } }root指令指定文件根目录。请求/static/css/app.css时Nginx 会去找/var/www/myapp/static/css/app.css。expires设置缓存过期时间30d表示 30 天。这个头让浏览器缓存这些文件用户第二次访问的时候就不发请求了页面加载速度明显快。踩坑location 写得宽把静态请求也反代了这个问题非常常见。你配了一个通用的location /做反代然后在后端应用里发现某些静态文件请求报了 404但你把文件放到服务器上就是找不到。原因就是你的location /反代把请求转发给了后端服务但后端服务的路由里根本没有这些路径。解决方案是把静态请求用更精确的 location 单独处理放在location /之前这样它们就不会落入反代的范围。# 静态资源要放在普通前缀之前匹配 location /static/ { root /var/www/myapp; } location /uploads/ { root /var/www/myapp; } location / { proxy_pass http://127.0.0.1:3000; }Nginx location 匹配是按优先级来的普通前缀按最长匹配原则处理所以/static/会比/更长、更优先。但如果你是用正则匹配或者其他方式顺序就很重要了。八、常见故障排查Nginx 出问题的时候大部分人第一反应是我配置是不是写错了然后开始一条条对着文档看。实际上大多数问题不需要那么复杂先看日志。# 实时查看错误日志tail-f/var/log/nginx/error.log# 查看访问日志tail-f/var/log/nginx/access.log错误日志里会记录具体报了什么错、哪个文件第几行有问题。这些信息比你对着配置猜要准确得多。502 Bad Gateway这是最常见的错误通常意味着 Nginx 成功收到了请求但转发给后端的时候后端没有响应。原因排查顺序后端服务没启动。curl http://127.0.0.1:3000试一下确认后端在跑。upstream 地址写错了。检查proxy_pass指向的地址和端口对不对。后端服务挂了或者卡住了。看后端日志看进程状态看 CPU 和内存占用。后端响应超时。proxy_connect_timeout和proxy_read_timeout默认 60 秒如果后端处理时间太长Nginx 会放弃等待。403 ForbiddenNginx 返回 403通常是两个原因root路径权限不对。Nginx 的 worker 进程用户通常是nginx或www-data对目标目录没有读权限。检查目录权限ls -la /var/www/myapp确认有 rx 权限。index 文件不存在。你配了index index.html但目录里没有这个文件。404404 的原因比较多逐一排查location 没匹配到请求路径。用nginx -T大写 T可以把完整配置输出到终端检查哪个 location 命中了。proxy_pass斜杠问题。见第三节路径拼接规则导致后端收到了它不认识的路径。后端服务没有这个路由。确认后端应用里确实注册了这个路径。结尾Nginx 配置没有多难。语法就那么几种配置块就那么几层。但配站点能不能一次过靠的不是记住多少语法而是搞清楚location匹配优先级和proxy_pass斜杠行为这两个高频坑。这篇文章里讲到的那些坑我自己和周围的同事基本都踩过proxy_pass 少个斜杠 404、正则顺序写反匹配错、reload 前没先 -t 导致半夜报警。踩过一遍之后印象就深了。改配置之前nginx -t这是铁律。记住这条你已经比很多人少踩一半的坑了。
返回列表