Nginx 作为高性能的 HTTP 和反向代理服务器,是前端工程化部署中不可或缺的一环。本文将从核心指令解析、企业级最佳实践、复杂场景实战及常见问题排查四个维度,深度梳理 Nginx 的配置逻辑。
一、核心指令深度解析 1. 路径映射:root 与 alias 的终极区别 这是 Nginx 配置中最容易混淆的两个指令。
root :追加模式。最终路径 = root 路径 + location 路径。
alias :替换模式。最终路径 = alias 路径(丢弃 location 匹配的部分)。
示例对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 location /t/ { root /www/root/html/; }location /t/ { alias /www/root/html/new_t/; }
注意 :alias 路径末尾通常需要加 /,且在正则匹配的 location 中使用 alias 需要捕获组支持,较为复杂,建议优先使用 root。
2. 路由回退:try_files (SPA 核心) try_files 是单页应用(SPA)解决 “刷新 404” 问题的关键。
语法 :try_files file ... uri;
执行逻辑 : 按顺序检查文件是否存在,如果都不存在,则重定向到最后一个参数指定的 URI(通常是 index.html)。
1 2 3 4 5 6 7 8 location / { root /data/dist; index index.html; try_files $uri $uri / /index.html; }
3. 流量控制:location 匹配规则与优先级 Nginx 的 location 匹配遵循特定优先级,而非配置文件的物理顺序。
优先级顺序(由高到低):
= :精确匹配(location = /img/test.png)
^~ :前缀匹配,匹配成功后停止 正则匹配(location ^~ /static/)
~ :区分大小写的正则匹配
~* :不区分大小写的正则匹配
/ :通用匹配(通常放在最后作为兜底)
示例:
1 2 3 4 5 location = / { ... } location = /login { ... } location ^~ /static/ { ... } location ~ \.(gif|jpg)$ { ... } location / { ... }
4. URL 重写:rewrite 机制 rewrite 用于修改请求 URI,常用于伪静态、老旧 URL 兼容等。
语法 :rewrite regex replacement [flag];
Flag 参数详解 :
last :停止当前 rewrite 规则,用重写后的 URI 重新发起 新一轮 location 匹配(类似内部重定向)。
break :停止当前 rewrite 规则,不再 发起新匹配,直接使用当前资源路径(常用于静态资源)。
redirect :返回 302 临时重定向(浏览器 URL 会变)。
permanent :返回 301 永久重定向(浏览器 URL 会变)。
二、企业级应用最佳实践 1. 性能优化:Gzip 压缩 开启 Gzip 可以显著减少传输体积,提升首屏加载速度。
1 2 3 4 5 6 7 8 http { gzip on ; gzip_min_length 1k ; gzip_comp_level 6 ; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; gzip_vary on ; gzip_disable "MSIE [1-6]\." ; }
2. 浏览器缓存策略 针对 SPA 应用,通常采用 “HTML 协商缓存,静态资源强缓存” 的策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server { location / { root /app/dist; index index.html; try_files $uri $uri / /index.html; if ($request_filename ~* .*\.(html|htm)$) { add_header Cache-Control "no-cache, no-store, must-revalidate" ; } } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { root /app/dist; expires 30d ; add_header Cache-Control "public, max-age=2592000" ; } }
3. 反向代理与跨域配置 前端开发常遇到的跨域问题,在生产环境通常通过 Nginx 反向代理解决。
1 2 3 4 5 6 7 8 9 10 11 12 13 location /api/ { proxy_pass http://backend-service: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 ; add_header 'Access-Control-Allow-Origin' '*' ; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' ; }
三、复杂场景实战案例 1. 微前端部署方案 微前端架构下,通常有一个主应用和多个子应用。
场景 :
主应用:domain.com/
子应用 A:domain.com/sub/app-a/
子应用 B:domain.com/sub/app-b/
配置示例 :
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 domain.com; location / { root /data/main-app; index index.html; try_files $uri $uri / /index.html; } location ^~ /sub/app-a/ { alias /data/sub-apps/app-a/; index index.html; try_files $uri $uri / /sub/app-a/index.html; } location ^~ /sub/app-b/ { alias /data/sub-apps/app-b/; index index.html; try_files $uri $uri / /sub/app-b/index.html; } }
2. 内容替换 (sub_filter) 在反向代理时,有时需要修改响应内容(如替换域名、插入脚本)。
1 2 3 4 5 6 7 8 location / { proxy_pass http://target-site.com; proxy_set_header Accept-Encoding "" ; sub_filter_types text/html; sub_filter 'target-site.com' 'my-domain.com' ; sub_filter_once off ; }
四、常见问题排查 (Troubleshooting) 1. 403 Forbidden
原因 1 :Nginx 启动用户无权限访问 root 或 alias 指定的目录。
解决 :修改 nginx.conf 中的 user 指令,或修改目录权限 (chmod/chown)。
原因 2 :目录缺少索引文件(如 index.html)且 autoindex 为 off。
解决 :确保目录下有 index.html 或开启 autoindex on。
2. 502 Bad Gateway
原因 :后端服务(如 Node.js, Java, PHP-FPM)未启动或挂掉。
原因 :proxy_pass 配置的地址或端口错误。
3. 504 Gateway Time-out
原因 :后端服务处理请求时间过长,超过了 Nginx 等待的时间。
解决 :优化后端接口性能,或增加 Nginx 超时配置:
1 2 3 proxy_connect_timeout 300s ;proxy_read_timeout 300s ;proxy_send_timeout 300s ;
4. 刷新页面 404
原因 :SPA 应用路由由前端接管,刷新时 Nginx 找不到对应的物理文件。
解决 :配置 try_files $uri $uri/ /index.html;。
sub_filter 过滤及替换响应内容 Accept-Encoding 设为空值,以禁用压缩,是因为 sub_filter 只能处理未经压缩的内容;又 sub_filter 一般只替换 text/html 且仅工作一次,不符合需求,故对配置稍做微调。
1 2 3 4 5 6 7 8 location / { ... proxy_set_header Accept-Encoding '' ; sub_filter_types *; sub_filter_once off; sub_filter '查找内容:源站域名' '替换为:反代站域名' ; ... }
rewrite 和 location 表面看 rewrite 和 location 功能有点像,都能实现跳转,主要区别在于 rewrite 是在同一域名内更改获取资源的路径,而 location 是对一类路径做控制访问或反向代理,可以 proxy_pass 到其他机器。很多情况下 rewrite 也会写在 location 里,它们的执行顺序是:
执行 server 块的 rewrite 指令(这里的块指的是 server 关键字后{}包围的区域,其它 xx 块类似) 执行 location 匹配 执行选定的 location 中的 rewrite 指令 如果其中某步 URI 被重写,则重新循环执行 1-3,直到找到真实存在的文件; 如果循环超过 10 次,则返回 500 Internal Server Error 错误。
匹配不到的话,重定向到 @fallback,然后重写到百度。
location 优先级介绍 location 匹配方式有以下几种
location = /path/a/exact.png {}: 精确匹配
location ^~ /path/a/ {}: 优先前缀匹配(符合最长匹配原则)
location ~ /Path?/ {} : 区分大小写正则匹配(首次匹配原则)
location ~* /path?/ {} : 不区分大小写正则匹配(首次匹配原则)
location /path/a/test.png {} : 前缀匹配(符合最长匹配原则)
location 优先级匹配与配置的先后顺序无关,优先级排列顺序如下
精确匹配 > 优先前缀匹配 > 区分大小写正则匹配=不区分大小写正则匹配 > 前缀匹配
rewrite 指令 语法:rewrite regex replacement [flag]; 作用域:server 、location、if 功能:如果一个 URI 匹配指定的正则表达式 regex,URI 就按照 replacement 重写。
rewrite 按配置文件中出现的顺序执行。可以使用 flag 标志来终止指令的进一步处理。
如果 replacement 以 http:// 、 https:// 或 $ scheme 开始,将不再继续处理,这个重定向将返回给客户端。 示例:第一种情况,重写的字符串带 http://
1 2 3 4 5 6 location ^~ /redirect { rewrite ^/(.*)$ http://www.$1 .com; return 200 "ok" ; }
在浏览器中输入 127.0.0.1:8080/redirect/baidu ,则临时重定向到 www.baidu.com 后面的 return 指令将没有机会执行了。
1 2 3 4 location ^~ /redirect { rewrite ^/(.*)$ www.$1 .com; return 200 "ok" ; }
发送请求如下 curl 127.0.0.1:8080/redirect/baidu ok 此处没有带 http:// 所以只是简单的重写。请求的 URI 由 /test1/baidu 重写为 www.baidu.com 因为会顺序执行 rewrite 指令,所以 下一步执行 return 指令,响应后返回 ok
flag 有四种参数可以选择:
last 停止处理后续 rewrite 指令集,然后对当前重写的新 URI 在 rewrite 指令集上重新查找。 break 停止处理后续 rewrite 指令集,并不再重新查找,但是当前 location 内剩余非 rewrite 语句和 location 外的 非 rewrite 语句可以执行。 redirect 如果 replacement 不是以 http:// 或 https:// 开始,返回 302 临时重定向 permanent 返回 301 永久重定向 示例 1:
rewrite 后面没有任何 flag 时就顺序执行 当 location 中没有 rewrite 模块指令可被执行时 就重写发起新一轮 location 匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 location / { rewrite ^/test1 /test2; rewrite ^/test2 /test3; }location = /test2 { return 200 "/test2" ; }location = /test3 { return 200 "/test3" ; }
发送如下请求 curl 127.0.0.1:8080/test1 /test3 如果正则表达 regex 式中包含 “}” 或 “;”,那么整个表达式需要用双引号或单引号包围。
示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 location / { rewrite ^/test1 /test2; rewrite ^/test2 /test3 last ; rewrite ^/test3 /test4; proxy_pass http://www.baidu.com; }location = /test2 { return 200 "/test2" ; }location = /test3 { return 200 "/test3" ; }location = /test4 { return 200 "/test4" ; }
发送如下请求 curl 127.0.0.1:8080/test1 返回 /test3
当如果将上面的 location / 改成如下代码
1 2 3 4 5 6 7 8 9 location / { rewrite ^/test1 /test2; rewrite ^/test2 /more/index.html break ; rewrite /more/index\.html /test4; proxy_pass https://www.baidu.com; }
发送请求 127.0.0.1:8080/test1 代理到 https://www.baidu.com
rewrite 后的请求参数: 如果替换字符串 replacement 包含新的请求参数,则在它们之后附加先前的请求参数。如果你不想要之前的参数,则在替换字符串 replacement 的末尾放置一个问号,避免附加它们。
由于最后加了个 ?,原来的请求参数将不会被追加到 rewrite 之后的 URI 后面* rewrite ^/users/(.*)$ /show?user=$1? last;
正则表达式
1 location = /login { ... }
2、^~ 开头
表示 URL 以某个常规字符串开头,不区分大小写,并关闭正则匹配,当搜索到这个普通匹配模式后,将不再继续搜索正则匹配模式。
1 location ^~ /static / { ... }
3、~ 开头
表示区分大小写的正则匹配,以 gif、jpg、js、css 结尾
1 location ~ \.(gif|jpg |png |js |css )$ { ... }
4、~* 开头
表示不区分大小写的正则匹配,以 .png 结尾
1 location ~* \.png $ { ... }
各符号优先级:
= > ^~ > |* > /
.表示除\n 之外的任意字符
*表示匹配 0-无穷
+表示匹配 1-无穷
墙代理+nginx 图片服务 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 server { listen 80 listen [::]:80 server_name blowsysun.top return 301 https://$server_name :443 $request_uri } server { listen 443 ssl http2 listen [::]:443 ssl http2 server_name blowsysun.top charset utf-8 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4 ssl_ecdh_curve secp384r1 ssl_prefer_server_ciphers on ssl_session_cache shared:SSL:10 m ssl_session_timeout 10 m ssl_session_tickets off ssl_certificate /etc/v2ray/blowsysun.top .pem ssl_certificate_key /etc/v2ray/blowsysun.top .key location / { root /usr/share/nginx/html proxy_ssl_server_name on proxy_pass https://86817 .com/ proxy_set_header Accept-Encoding '' sub_filter "86817.com" "blowsysun.top" sub_filter_once off } location = /robots.txt {} location /images/ { root /usr/share/nginx/ autoindex on 改为on 后,显示的文件时间为文件的服务器时间 } location /nEmeUFJM0hbD { proxy_redirect off proxy_pass http://127.0 .0.1 :22768 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_set_header X-Forwarded-For $proxy_add_x_forwarded_for } }
项目 1 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 worker_processes auto;worker_rlimit_nofile 65535 ;error_log logs/error .log;pid /var/run/nginx.pid;daemon off ;events { worker_connections 2048 ; }http { log_format main '$remote_addr - $remote_user [$time_local ] "$request " ' '$status $body_bytes_sent "$http_referer " ' '"$http_user_agent " "$http_x_forwarded_for " "$http_referer "' ; access_log logs/access.log main; client_max_body_size 100m ; sendfile on ; tcp_nopush on ; tcp_nodelay on ; keepalive_timeout 65 ; types_hash_max_size 2048 ; gzip on ; gzip_disable "msie6" ; gzip_vary on ; gzip_proxied any; gzip_comp_level 6 ; gzip_buffers 16 8k ; gzip_http_version 1 .1 ; gzip_min_length 256 ; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss application/javascript text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; proxy_connect_timeout 300s ; proxy_send_timeout 300s ; proxy_read_timeout 300s ; send_timeout 300s ; fastcgi_connect_timeout 300 ; fastcgi_send_timeout 300 ; fastcgi_read_timeout 300 ; include /usr/local/openresty/nginx/mime.types; default_type application/octet-stream; include servers/*.conf ; server { listen 80 default_server; listen [::]:80 default_server; server_name _; location ~ .+/env.config.js$ { rewrite ^(.*)$ /env.config.js break ; root /usr/local/openresty/nginx/html; } location ~ .+/static_res/ { rewrite ^.*(\/static_res\/.*)$ $1 break ; root /usr/local/openresty/nginx/html; } location ~ .+/assets_res/ { rewrite ^.*(\/assets_res\/.*)$ $1 break ; root /usr/local/openresty/nginx/html; } location ~ .+/assets/ { rewrite ^.*(\/assets\/.*)$ $1 break ; root /usr/local/openresty/nginx/html; } location / { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /usr/local/openresty/nginx/html/; index index.html; try_files $uri $uri / /index.html; if ($request_filename ~* .*.(html|htm)$) { expires -1s ; } if ($request_filename ~* .*.(gif|jpg|jpeg|png|svg)$) { expires 30d ; } if ($request_filename ~ .*.(js|css)$) { expires 12h ; } } location /xadmin/ { proxy_pass http://xxx-backend:8090; rewrite ^/xadmin/(.*)$ /xadmin/$1 break ; } location /platform/ { proxy_pass http://xxx-backend:8080; rewrite ^/platform/(.*)$ /platform/$1 break ; } location /ping { return 200 "oK" ; } error_page 404 /404 .html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } }
项目 2 - 微前端 独立部署 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 user root;worker_processes auto;events { worker_connections 1024 ; }http { include mime.types; default_type application/octet-stream; sendfile on ; tcp_nopush on ; tcp_nodelay on ; keepalive_timeout 65 ; types_hash_max_size 2048 ; server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; location ^~ /zyx/portal/doc/ { alias /data/zyx_web/; sendfile on ; autoindex on ; autoindex_exact_size on ; autoindex_localtime on ; charset utf-8 ,gbk; } location ^~/zyx { alias /data/zyx_web/zyx-web-main; index index.html; try_files $uri $uri / /zyx/index.html; } location ^~/zyx/sub/enterprise { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /data/zyx_web/zyx-web-government-enterprise-through; index index.html; try_files $uri $uri / /zyx/sub/enterprise/index.html; } location ^~/zyx/sub/portal { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /data/zyx_web/zyx-web-portal; index index.html; try_files $uri $uri / /zyx/sub/portal/index.html; } location ^~/zyx/sub/tag { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /data/zyx_web/zyx-web-tag; index index.html; try_files $uri $uri / /zyx/sub/tag/index.html; } } }
项目 3 - 微前端 合并部署 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 108 user root;worker_processes auto;worker_rlimit_nofile 65535 ;error_log logs/error .log;pid /var/run/nginx.pid;daemon off ;events { worker_connections 2048 ; }http { log_format main '$remote_addr - $remote_user [$time_local ] "$request " ' '$status $body_bytes_sent "$http_referer " ' '"$http_user_agent " "$http_x_forwarded_for " "$http_referer "' ; access_log logs/access.log main; client_max_body_size 100m ; sendfile on ; tcp_nopush on ; tcp_nodelay on ; keepalive_timeout 65 ; types_hash_max_size 2048 ; gzip on ; gzip_static on ; gzip_disable "msie6" ; gzip_vary on ; gzip_proxied any; gzip_comp_level 6 ; gzip_buffers 16 8k ; gzip_http_version 1 .0 ; gzip_min_length 20 ; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss application/javascript text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; proxy_connect_timeout 300s ; proxy_send_timeout 300s ; proxy_read_timeout 300s ; send_timeout 300s ; fastcgi_connect_timeout 300 ; fastcgi_send_timeout 300 ; fastcgi_read_timeout 300 ; include /usr/local/openresty/nginx/mime.types; default_type application/octet-stream; include servers/*.conf ; server { listen 80 default_server; listen [::]:80 default_server; server_name _; location ~ .+/env.config.js$ { rewrite ^(.*)$ /env.config.js break ; root /usr/local/openresty/nginx/html; } location ^~ /zyx_dev { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; proxy_hide_header Host; alias /usr/local/openresty/nginx/html/zyx-web-main; index index.html; try_files $uri $uri / /index.html; if ($request_filename ~* .*.(html|htm)$) { expires -1s ; } if ($request_filename ~* .*.(gif|jpg|jpeg|png|svg)$) { expires 30d ; } if ($request_filename ~ .*.(js|css)$) { expires 12h ; } } location /zyx_dev/sub/portal { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /usr/local/openresty/nginx/html/zyx-web-portal; index index.html; try_files $uri $uri / /zyx/sub/portal/index.html; } location /zyx_dev/sub/enterprise { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /usr/local/openresty/nginx/html/zyx-web-government-enterprise-through; index index.html; try_files $uri $uri / /zyx/sub/enterprise/index.html; } location /zyx_dev/sub/tag { add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0' ; alias /usr/local/openresty/nginx/html/zyx-web-tag; index index.html; try_files $uri $uri / /zyx/sub/tag/index.html; } error_page 404 /404 .html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } }