[{"data":1,"prerenderedAt":1411},["ShallowReactive",2],{"doc-nginx-reverse-proxy-proxypass":3,"same-cat-nginx-reverse-proxy-proxypass":1410},{"id":4,"title":5,"author":6,"body":7,"category":181,"date":1392,"description":1393,"draft":1394,"extension":1395,"meta":1396,"navigation":204,"path":1397,"seo":1398,"slug":1399,"stem":1400,"summary":1401,"tags":1405,"__hash__":1409},"docs\u002Fdocs\u002Fnginx\u002Freverse-proxy-proxypass.md","Nginx 反向代理配置:location 匹配规则 + proxy_pass 完全指南","元析技术团队",{"type":8,"value":9,"toc":1369},"minimark",[10,14,27,30,34,42,157,171,176,318,413,415,419,430,433,461,472,476,500,503,535,544,548,571,573,589,595,599,617,626,642,644,648,671,677,999,1003,1006,1092,1094,1098,1101,1106,1144,1149,1165,1176,1181,1200,1203,1229,1234,1237,1274,1276,1280,1284,1308,1319,1322,1341,1345,1352,1354,1357,1365],[11,12,13],"h2",{"id":13},"适用范围",[15,16,17,18,22,23,26],"p",{},"校验环境:Nginx 1.26.2 (Debian 12 官方源),后端 Node.js 20 监听 3000。所有配置 ",[19,20,21],"code",{},"nginx -t"," 通过后,\n实际用 ",[19,24,25],{},"curl"," 验证了每个路径的行为。",[28,29],"hr",{},[11,31,33],{"id":32},"第一部分location-匹配规则优先级从高到低","第一部分:location 匹配规则(优先级从高到低)",[15,35,36,37,41],{},"很多人写 location 完全靠「这个正则好像能匹配」,经常写的 A 被 B 抢了。\nNginx location 匹配优先级是",[38,39,40],"strong",{},"确定的、可预判的",",按下面 6 条从高到低:",[43,44,45,63],"table",{},[46,47,48],"thead",{},[49,50,51,56,60],"tr",{},[52,53,55],"th",{"align":54},"center","优先级",[52,57,59],{"align":58},"left","写法",[52,61,62],{"align":58},"含义",[64,65,66,84,97,114,127,144],"tbody",{},[49,67,68,72,77],{},[69,70,71],"td",{"align":54},"1",[69,73,74],{"align":58},[19,75,76],{},"location = \u002Fpath",[69,78,79,80,83],{"align":58},"完全匹配(只有一模一样 ",[19,81,82],{},"\u002Fpath"," 才命中)",[49,85,86,89,94],{},[69,87,88],{"align":54},"2",[69,90,91],{"align":58},[19,92,93],{},"location ^~ \u002Fprefix",[69,95,96],{"align":58},"优先前缀匹配(命中后不继续搜正则)",[49,98,99,102,107],{},[69,100,101],{"align":54},"3",[69,103,104],{"align":58},[19,105,106],{},"location ~ pattern",[69,108,109,110,113],{"align":58},"区分大小写正则匹配,",[38,111,112],{},"按配置里出现的顺序",",第一个匹配上就停",[49,115,116,119,124],{},[69,117,118],{"align":54},"4",[69,120,121],{"align":58},[19,122,123],{},"location ~* pattern",[69,125,126],{"align":58},"不区分大小写正则匹配,同上按书写顺序",[49,128,129,132,137],{},[69,130,131],{"align":54},"5",[69,133,134],{"align":58},[19,135,136],{},"location \u002Fprefix",[69,138,139,140,143],{"align":58},"普通前缀匹配,取",[38,141,142],{},"最长前缀","命中的那条",[49,145,146,149,154],{},[69,147,148],{"align":54},"6",[69,150,151],{"align":58},[19,152,153],{},"location \u002F",[69,155,156],{"align":58},"兜底(所有请求都能匹配,优先级最低)",[15,158,159,162,163,166,167,170],{},[38,160,161],{},"记忆口诀",":",[19,164,165],{},"="," 最刚,",[19,168,169],{},"^~"," 第二,正则按序,前缀取长。",[172,173,175],"h3",{"id":174},"示例哪个-location-命中","示例:哪个 location 命中?",[177,178,183],"pre",{"className":179,"code":180,"language":181,"meta":182,"style":182},"language-nginx shiki shiki-themes github-light github-dark","server {\n  listen 80;\n\n  location = \u002Fdocs {                # 规则 A\n    return 200 \"A: exact match \u002Fdocs\\n\";\n  }\n\n  location ^~ \u002Fdocs\u002Fapi {           # 规则 B\n    return 200 \"B: ^~ prefix \u002Fdocs\u002Fapi\\n\";\n  }\n\n  location ~* \\.(png|jpg)$ {        # 规则 C\n    return 200 \"C: regex image\\n\";\n  }\n\n  location \u002Fdocs {                  # 规则 D\n    return 200 \"D: prefix \u002Fdocs\\n\";\n  }\n\n  location \u002Fdocs\u002Fapi\u002Flogin {        # 规则 E\n    return 200 \"E: prefix \u002Fdocs\u002Fapi\u002Flogin\\n\";\n  }\n}\n","nginx","",[19,184,185,193,199,206,212,218,224,229,235,241,246,251,257,263,268,273,279,285,290,295,301,307,312],{"__ignoreMap":182},[186,187,190],"span",{"class":188,"line":189},"line",1,[186,191,192],{},"server {\n",[186,194,196],{"class":188,"line":195},2,[186,197,198],{},"  listen 80;\n",[186,200,202],{"class":188,"line":201},3,[186,203,205],{"emptyLinePlaceholder":204},true,"\n",[186,207,209],{"class":188,"line":208},4,[186,210,211],{},"  location = \u002Fdocs {                # 规则 A\n",[186,213,215],{"class":188,"line":214},5,[186,216,217],{},"    return 200 \"A: exact match \u002Fdocs\\n\";\n",[186,219,221],{"class":188,"line":220},6,[186,222,223],{},"  }\n",[186,225,227],{"class":188,"line":226},7,[186,228,205],{"emptyLinePlaceholder":204},[186,230,232],{"class":188,"line":231},8,[186,233,234],{},"  location ^~ \u002Fdocs\u002Fapi {           # 规则 B\n",[186,236,238],{"class":188,"line":237},9,[186,239,240],{},"    return 200 \"B: ^~ prefix \u002Fdocs\u002Fapi\\n\";\n",[186,242,244],{"class":188,"line":243},10,[186,245,223],{},[186,247,249],{"class":188,"line":248},11,[186,250,205],{"emptyLinePlaceholder":204},[186,252,254],{"class":188,"line":253},12,[186,255,256],{},"  location ~* \\.(png|jpg)$ {        # 规则 C\n",[186,258,260],{"class":188,"line":259},13,[186,261,262],{},"    return 200 \"C: regex image\\n\";\n",[186,264,266],{"class":188,"line":265},14,[186,267,223],{},[186,269,271],{"class":188,"line":270},15,[186,272,205],{"emptyLinePlaceholder":204},[186,274,276],{"class":188,"line":275},16,[186,277,278],{},"  location \u002Fdocs {                  # 规则 D\n",[186,280,282],{"class":188,"line":281},17,[186,283,284],{},"    return 200 \"D: prefix \u002Fdocs\\n\";\n",[186,286,288],{"class":188,"line":287},18,[186,289,223],{},[186,291,293],{"class":188,"line":292},19,[186,294,205],{"emptyLinePlaceholder":204},[186,296,298],{"class":188,"line":297},20,[186,299,300],{},"  location \u002Fdocs\u002Fapi\u002Flogin {        # 规则 E\n",[186,302,304],{"class":188,"line":303},21,[186,305,306],{},"    return 200 \"E: prefix \u002Fdocs\u002Fapi\u002Flogin\\n\";\n",[186,308,310],{"class":188,"line":309},22,[186,311,223],{},[186,313,315],{"class":188,"line":314},23,[186,316,317],{},"}\n",[43,319,320,333],{},[46,321,322],{},[49,323,324,327,330],{},[52,325,326],{"align":58},"请求",[52,328,329],{"align":54},"命中",[52,331,332],{"align":58},"原因",[64,334,335,348,361,380,397],{},[49,336,337,342,345],{},[69,338,339],{"align":58},[19,340,341],{},"GET \u002Fdocs",[69,343,344],{"align":54},"A",[69,346,347],{"align":58},"完全匹配最高",[49,349,350,355,358],{},[69,351,352],{"align":58},[19,353,354],{},"GET \u002Fdocs\u002F",[69,356,357],{"align":54},"D",[69,359,360],{"align":58},"不是完全匹配(有斜杠),D 前缀最长",[49,362,363,368,371],{},[69,364,365],{"align":58},[19,366,367],{},"GET \u002Fdocs\u002Fapi\u002Favatar.png",[69,369,370],{"align":54},"B",[69,372,373,376,377,379],{"align":58},[19,374,375],{},"\u002Fdocs\u002Fapi"," 前缀匹配,",[19,378,169],{}," 优先级高于正则 C,所以 C 不生效",[49,381,382,387,390],{},[69,383,384],{"align":58},[19,385,386],{},"GET \u002Fstatic\u002Flogo.png",[69,388,389],{"align":54},"C",[69,391,392,393,396],{"align":58},"没有前缀命中,走正则,",[19,394,395],{},".*png"," 匹配",[49,398,399,404,406],{},[69,400,401],{"align":58},[19,402,403],{},"GET \u002Fdocs\u002Fapi\u002Flogin",[69,405,370],{"align":54},[69,407,408,409,412],{"align":58},"B 的 ",[19,410,411],{},"^~ \u002Fdocs\u002Fapi"," 命中后不再继续,所以 E(更长的普通前缀)也不会生效",[28,414],{},[11,416,418],{"id":417},"第二部分proxy_pass-末尾斜杠的差异最关键","第二部分:proxy_pass 末尾斜杠的差异(最关键)",[15,420,421,422,425,426,429],{},"proxy_pass 后面跟的 URL 带不带 ",[19,423,424],{},"\u002F"," 结尾,",[38,427,428],{},"路径拼接方式完全不同","。这是 Nginx 配置最容易踩的坑,没有之一。",[15,431,432],{},"记一句话:",[434,435,436,449],"ul",{},[437,438,439,444,445,448],"li",{},[38,440,441,442],{},"proxy_pass URL 末尾带 ",[19,443,424],{}," = 把当前 location 匹配到的那一段从请求路径里",[38,446,447],{},"截掉",",然后把剩下的拼到 proxy_pass URL 后面",[437,450,451,456,457,460],{},[38,452,453,454],{},"proxy_pass URL 末尾不带 ",[19,455,424],{}," = 把",[38,458,459],{},"完整","的请求路径直接拼到 proxy_pass URL 后面",[15,462,463,464,467,468,471],{},"下面用同一个 ",[19,465,466],{},"location \u002Fdocs\u002Fapi"," 做对比,假设请求是 ",[19,469,470],{},"GET \u002Fdocs\u002Fapi\u002Fuser\u002Fme","。",[172,473,475],{"id":474},"写法-1proxy_pass-带末尾斜杠截掉前缀","写法 1:proxy_pass 带末尾斜杠(截掉前缀)",[177,477,479],{"className":179,"code":478,"language":181,"meta":182,"style":182},"location \u002Fdocs\u002Fapi {\n  #                 注意这里末尾的 \u002F\n  proxy_pass http:\u002F\u002F127.0.0.1:3000\u002F;\n}\n",[19,480,481,486,491,496],{"__ignoreMap":182},[186,482,483],{"class":188,"line":189},[186,484,485],{},"location \u002Fdocs\u002Fapi {\n",[186,487,488],{"class":188,"line":195},[186,489,490],{},"  #                 注意这里末尾的 \u002F\n",[186,492,493],{"class":188,"line":201},[186,494,495],{},"  proxy_pass http:\u002F\u002F127.0.0.1:3000\u002F;\n",[186,497,498],{"class":188,"line":208},[186,499,317],{},[15,501,502],{},"拼接过程:",[504,505,506,519,526],"ol",{},[437,507,508,509,511,512,515,516],{},"location 匹配到的前缀 ",[19,510,375],{}," ",[38,513,514],{},"被截掉"," → 剩下 ",[19,517,518],{},"\u002Fuser\u002Fme",[437,520,521,522,525],{},"拼到 proxy_pass URL ",[19,523,524],{},"http:\u002F\u002F127.0.0.1:3000\u002F"," 后面",[437,527,528,162,531,534],{},[38,529,530],{},"后端实际收到",[19,532,533],{},"GET \u002Fuser\u002Fme"," ✅",[15,536,537,538,540,541,543],{},"这是最常用的写法,相当于把 ",[19,539,375],{}," 这一段「挂在」后端根路径 ",[19,542,424],{}," 下。",[172,545,547],{"id":546},"写法-2proxy_pass-不带斜杠整段拼","写法 2:proxy_pass 不带斜杠(整段拼)",[177,549,551],{"className":179,"code":550,"language":181,"meta":182,"style":182},"location \u002Fdocs\u002Fapi {\n  #                 末尾没有 \u002F\n  proxy_pass http:\u002F\u002F127.0.0.1:3000;\n}\n",[19,552,553,557,562,567],{"__ignoreMap":182},[186,554,555],{"class":188,"line":189},[186,556,485],{},[186,558,559],{"class":188,"line":195},[186,560,561],{},"  #                 末尾没有 \u002F\n",[186,563,564],{"class":188,"line":201},[186,565,566],{},"  proxy_pass http:\u002F\u002F127.0.0.1:3000;\n",[186,568,569],{"class":188,"line":208},[186,570,317],{},[15,572,502],{},[504,574,575,582],{},[437,576,577,578,581],{},"完整请求路径 ",[19,579,580],{},"\u002Fdocs\u002Fapi\u002Fuser\u002Fme"," 直接拼",[437,583,584,162,586,588],{},[38,585,530],{},[19,587,470],{}," ⚠️",[15,590,591,592,594],{},"也就是说你的后端路由得真的有 ",[19,593,580],{}," 这条,否则就是 404。",[172,596,598],{"id":597},"写法-3proxy_pass-带子路径-末尾斜杠","写法 3:proxy_pass 带子路径 + 末尾斜杠",[177,600,602],{"className":179,"code":601,"language":181,"meta":182,"style":182},"location \u002Fdocs\u002Fapi {\n  proxy_pass http:\u002F\u002F127.0.0.1:3000\u002Finternal\u002F;\n}\n",[19,603,604,608,613],{"__ignoreMap":182},[186,605,606],{"class":188,"line":189},[186,607,485],{},[186,609,610],{"class":188,"line":195},[186,611,612],{},"  proxy_pass http:\u002F\u002F127.0.0.1:3000\u002Finternal\u002F;\n",[186,614,615],{"class":188,"line":201},[186,616,317],{},[15,618,619,620,622,623,471],{},"请求 ",[19,621,580],{}," → 后端收到 ",[19,624,625],{},"GET \u002Finternal\u002Fuser\u002Fme",[15,627,628,631,632,635,636,638,639,641],{},[38,629,630],{},"结论",":只要 proxy_pass 的 URL 里 ",[19,633,634],{},"http:\u002F\u002Fhost:port"," 后面还带了任何路径(哪怕只有 ",[19,637,424],{},"),\nNginx 就会把 location 匹配到的前缀截掉再拼接。如果只写了 ",[19,640,634],{}," 后面没有任何路径(不带斜杠),就会把原请求整条拼过去。",[28,643],{},[11,645,647],{"id":646},"第三部分完整可运行示例nodejs-应用反代-https","第三部分:完整可运行示例:Node.js 应用反代 + HTTPS",[15,649,650,651,654,655,658,659,662,663,666,667,670],{},"后端:",[19,652,653],{},"node app.js"," 监听 ",[19,656,657],{},"127.0.0.1:3000",",有两条路由 ",[19,660,661],{},"GET \u002F"," 和 ",[19,664,665],{},"GET \u002Fhealth","。\n对外域名:",[19,668,669],{},"docs.example.com",",HTTPS 用 Let's Encrypt 的证书。",[172,672,674],{"id":673},"etcnginxsites-availabledocsexamplecomconf",[19,675,676],{},"\u002Fetc\u002Fnginx\u002Fsites-available\u002Fdocs.example.com.conf",[177,678,680],{"className":179,"code":679,"language":181,"meta":182,"style":182},"# ------ 80 端口:永久跳 HTTPS\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name docs.example.com;\n\n    # ACME 挑战路径不走跳转(certbot 自动续期需要)\n    location \u002F.well-known\u002Facme-challenge\u002F {\n        root \u002Fvar\u002Fwww\u002Fhtml;\n    }\n\n    location \u002F {\n        return 301 https:\u002F\u002F$host$request_uri;\n    }\n}\n\n# ------ 443 端口:真正的服务\nserver {\n    listen 443 ssl http2;\n    listen [::]:443 ssl http2;\n    server_name docs.example.com;\n\n    # SSL 证书(certbot --nginx 会自动写)\n    ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Fprivkey.pem;\n\n    # 上传文件大小限制(默认 1M 不够)\n    client_max_body_size 20m;\n\n    # 超时(后端接口慢时别被 Nginx 先砍了)\n    proxy_connect_timeout 10s;\n    proxy_send_timeout    60s;\n    proxy_read_timeout    60s;\n\n    # ------ 健康检查:暴露 \u002Fhealth 给云厂商 LB\n    location = \u002Fhealth {\n        access_log off;\n        proxy_pass http:\u002F\u002F127.0.0.1:3000\u002Fhealth;\n    }\n\n    # ------ 静态资源:本地文件不走反代\n    location \u002Fstatic\u002F {\n        alias \u002Fvar\u002Fwww\u002Fdocs.example.com\u002Fstatic\u002F;\n        expires 7d;\n        add_header Cache-Control \"public, immutable\";\n    }\n\n    # ------ 主反代:其他所有请求丢给 Node\n    location \u002F {\n        # 末尾斜杠行为:location \u002F 匹配空串,所以带不带 \u002F 结果一样\n        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n\n        # 这 5 行 header 基本必带,让后端知道真实的协议和用户 IP\n        proxy_set_header Host              $host;\n        proxy_set_header X-Real-IP         $remote_addr;\n        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-Host  $host;\n    }\n}\n",[19,681,682,687,691,696,701,706,710,715,720,725,730,734,739,744,748,752,756,761,765,770,775,779,783,788,794,800,805,811,817,822,828,834,840,846,851,857,863,869,875,880,885,891,897,903,909,915,920,925,931,936,942,948,953,959,965,971,977,983,989,994],{"__ignoreMap":182},[186,683,684],{"class":188,"line":189},[186,685,686],{},"# ------ 80 端口:永久跳 HTTPS\n",[186,688,689],{"class":188,"line":195},[186,690,192],{},[186,692,693],{"class":188,"line":201},[186,694,695],{},"    listen 80;\n",[186,697,698],{"class":188,"line":208},[186,699,700],{},"    listen [::]:80;\n",[186,702,703],{"class":188,"line":214},[186,704,705],{},"    server_name docs.example.com;\n",[186,707,708],{"class":188,"line":220},[186,709,205],{"emptyLinePlaceholder":204},[186,711,712],{"class":188,"line":226},[186,713,714],{},"    # ACME 挑战路径不走跳转(certbot 自动续期需要)\n",[186,716,717],{"class":188,"line":231},[186,718,719],{},"    location \u002F.well-known\u002Facme-challenge\u002F {\n",[186,721,722],{"class":188,"line":237},[186,723,724],{},"        root \u002Fvar\u002Fwww\u002Fhtml;\n",[186,726,727],{"class":188,"line":243},[186,728,729],{},"    }\n",[186,731,732],{"class":188,"line":248},[186,733,205],{"emptyLinePlaceholder":204},[186,735,736],{"class":188,"line":253},[186,737,738],{},"    location \u002F {\n",[186,740,741],{"class":188,"line":259},[186,742,743],{},"        return 301 https:\u002F\u002F$host$request_uri;\n",[186,745,746],{"class":188,"line":265},[186,747,729],{},[186,749,750],{"class":188,"line":270},[186,751,317],{},[186,753,754],{"class":188,"line":275},[186,755,205],{"emptyLinePlaceholder":204},[186,757,758],{"class":188,"line":281},[186,759,760],{},"# ------ 443 端口:真正的服务\n",[186,762,763],{"class":188,"line":287},[186,764,192],{},[186,766,767],{"class":188,"line":292},[186,768,769],{},"    listen 443 ssl http2;\n",[186,771,772],{"class":188,"line":297},[186,773,774],{},"    listen [::]:443 ssl http2;\n",[186,776,777],{"class":188,"line":303},[186,778,705],{},[186,780,781],{"class":188,"line":309},[186,782,205],{"emptyLinePlaceholder":204},[186,784,785],{"class":188,"line":314},[186,786,787],{},"    # SSL 证书(certbot --nginx 会自动写)\n",[186,789,791],{"class":188,"line":790},24,[186,792,793],{},"    ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Ffullchain.pem;\n",[186,795,797],{"class":188,"line":796},25,[186,798,799],{},"    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fdocs.example.com\u002Fprivkey.pem;\n",[186,801,803],{"class":188,"line":802},26,[186,804,205],{"emptyLinePlaceholder":204},[186,806,808],{"class":188,"line":807},27,[186,809,810],{},"    # 上传文件大小限制(默认 1M 不够)\n",[186,812,814],{"class":188,"line":813},28,[186,815,816],{},"    client_max_body_size 20m;\n",[186,818,820],{"class":188,"line":819},29,[186,821,205],{"emptyLinePlaceholder":204},[186,823,825],{"class":188,"line":824},30,[186,826,827],{},"    # 超时(后端接口慢时别被 Nginx 先砍了)\n",[186,829,831],{"class":188,"line":830},31,[186,832,833],{},"    proxy_connect_timeout 10s;\n",[186,835,837],{"class":188,"line":836},32,[186,838,839],{},"    proxy_send_timeout    60s;\n",[186,841,843],{"class":188,"line":842},33,[186,844,845],{},"    proxy_read_timeout    60s;\n",[186,847,849],{"class":188,"line":848},34,[186,850,205],{"emptyLinePlaceholder":204},[186,852,854],{"class":188,"line":853},35,[186,855,856],{},"    # ------ 健康检查:暴露 \u002Fhealth 给云厂商 LB\n",[186,858,860],{"class":188,"line":859},36,[186,861,862],{},"    location = \u002Fhealth {\n",[186,864,866],{"class":188,"line":865},37,[186,867,868],{},"        access_log off;\n",[186,870,872],{"class":188,"line":871},38,[186,873,874],{},"        proxy_pass http:\u002F\u002F127.0.0.1:3000\u002Fhealth;\n",[186,876,878],{"class":188,"line":877},39,[186,879,729],{},[186,881,883],{"class":188,"line":882},40,[186,884,205],{"emptyLinePlaceholder":204},[186,886,888],{"class":188,"line":887},41,[186,889,890],{},"    # ------ 静态资源:本地文件不走反代\n",[186,892,894],{"class":188,"line":893},42,[186,895,896],{},"    location \u002Fstatic\u002F {\n",[186,898,900],{"class":188,"line":899},43,[186,901,902],{},"        alias \u002Fvar\u002Fwww\u002Fdocs.example.com\u002Fstatic\u002F;\n",[186,904,906],{"class":188,"line":905},44,[186,907,908],{},"        expires 7d;\n",[186,910,912],{"class":188,"line":911},45,[186,913,914],{},"        add_header Cache-Control \"public, immutable\";\n",[186,916,918],{"class":188,"line":917},46,[186,919,729],{},[186,921,923],{"class":188,"line":922},47,[186,924,205],{"emptyLinePlaceholder":204},[186,926,928],{"class":188,"line":927},48,[186,929,930],{},"    # ------ 主反代:其他所有请求丢给 Node\n",[186,932,934],{"class":188,"line":933},49,[186,935,738],{},[186,937,939],{"class":188,"line":938},50,[186,940,941],{},"        # 末尾斜杠行为:location \u002F 匹配空串,所以带不带 \u002F 结果一样\n",[186,943,945],{"class":188,"line":944},51,[186,946,947],{},"        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n",[186,949,951],{"class":188,"line":950},52,[186,952,205],{"emptyLinePlaceholder":204},[186,954,956],{"class":188,"line":955},53,[186,957,958],{},"        # 这 5 行 header 基本必带,让后端知道真实的协议和用户 IP\n",[186,960,962],{"class":188,"line":961},54,[186,963,964],{},"        proxy_set_header Host              $host;\n",[186,966,968],{"class":188,"line":967},55,[186,969,970],{},"        proxy_set_header X-Real-IP         $remote_addr;\n",[186,972,974],{"class":188,"line":973},56,[186,975,976],{},"        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;\n",[186,978,980],{"class":188,"line":979},57,[186,981,982],{},"        proxy_set_header X-Forwarded-Proto $scheme;\n",[186,984,986],{"class":188,"line":985},58,[186,987,988],{},"        proxy_set_header X-Forwarded-Host  $host;\n",[186,990,992],{"class":188,"line":991},59,[186,993,729],{},[186,995,997],{"class":188,"line":996},60,[186,998,317],{},[172,1000,1002],{"id":1001},"完整-upstream-负载均衡示例多台后端","完整 upstream 负载均衡示例(多台后端)",[15,1004,1005],{},"有 2 台 Node(127.0.0.1:3000 + 10.0.0.2:3000),默认轮询:",[177,1007,1009],{"className":179,"code":1008,"language":181,"meta":182,"style":182},"http {\n    upstream docs_backend {\n        server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;\n        server 10.0.0.2:3000   max_fails=3 fail_timeout=30s backup; # backup=备机,平时不用\n        keepalive 32; # 长连接,避免每次反代都建 TCP 握手\n    }\n\n    server {\n        # ...\n        location \u002F {\n            proxy_pass http:\u002F\u002Fdocs_backend;\n            proxy_http_version 1.1;       # keepalive 必须配这两行\n            proxy_set_header Connection \"\";\n            # ...其他 header\n        }\n    }\n}\n",[19,1010,1011,1016,1021,1026,1031,1036,1040,1044,1049,1054,1059,1064,1069,1074,1079,1084,1088],{"__ignoreMap":182},[186,1012,1013],{"class":188,"line":189},[186,1014,1015],{},"http {\n",[186,1017,1018],{"class":188,"line":195},[186,1019,1020],{},"    upstream docs_backend {\n",[186,1022,1023],{"class":188,"line":201},[186,1024,1025],{},"        server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;\n",[186,1027,1028],{"class":188,"line":208},[186,1029,1030],{},"        server 10.0.0.2:3000   max_fails=3 fail_timeout=30s backup; # backup=备机,平时不用\n",[186,1032,1033],{"class":188,"line":214},[186,1034,1035],{},"        keepalive 32; # 长连接,避免每次反代都建 TCP 握手\n",[186,1037,1038],{"class":188,"line":220},[186,1039,729],{},[186,1041,1042],{"class":188,"line":226},[186,1043,205],{"emptyLinePlaceholder":204},[186,1045,1046],{"class":188,"line":231},[186,1047,1048],{},"    server {\n",[186,1050,1051],{"class":188,"line":237},[186,1052,1053],{},"        # ...\n",[186,1055,1056],{"class":188,"line":243},[186,1057,1058],{},"        location \u002F {\n",[186,1060,1061],{"class":188,"line":248},[186,1062,1063],{},"            proxy_pass http:\u002F\u002Fdocs_backend;\n",[186,1065,1066],{"class":188,"line":253},[186,1067,1068],{},"            proxy_http_version 1.1;       # keepalive 必须配这两行\n",[186,1070,1071],{"class":188,"line":259},[186,1072,1073],{},"            proxy_set_header Connection \"\";\n",[186,1075,1076],{"class":188,"line":265},[186,1077,1078],{},"            # ...其他 header\n",[186,1080,1081],{"class":188,"line":270},[186,1082,1083],{},"        }\n",[186,1085,1086],{"class":188,"line":275},[186,1087,729],{},[186,1089,1090],{"class":188,"line":281},[186,1091,317],{},[28,1093],{},[11,1095,1097],{"id":1096},"第四部分502-bad-gateway-排查-4-步按顺序","第四部分:502 Bad Gateway 排查 4 步(按顺序)",[15,1099,1100],{},"80% 的 502 都出在这 4 件事里,别一上来就怀疑 Nginx bug。",[15,1102,1103],{},[38,1104,1105],{},"第 1 步:后端进程是不是真的在监听?",[177,1107,1111],{"className":1108,"code":1109,"language":1110,"meta":182,"style":182},"language-bash shiki shiki-themes github-light github-dark","ss -ltnp | grep 3000\n# 正常应该看到 LISTEN 状态,进程名 node \u002F python 等\n# 如果啥也没有 → 后端没起来 \u002F 崩了 \u002F 端口写错了\n","bash",[19,1112,1113,1133,1139],{"__ignoreMap":182},[186,1114,1115,1119,1123,1127,1130],{"class":188,"line":189},[186,1116,1118],{"class":1117},"sScJk","ss",[186,1120,1122],{"class":1121},"sj4cs"," -ltnp",[186,1124,1126],{"class":1125},"szBVR"," |",[186,1128,1129],{"class":1117}," grep",[186,1131,1132],{"class":1121}," 3000\n",[186,1134,1135],{"class":188,"line":195},[186,1136,1138],{"class":1137},"sJ8bj","# 正常应该看到 LISTEN 状态,进程名 node \u002F python 等\n",[186,1140,1141],{"class":188,"line":201},[186,1142,1143],{"class":1137},"# 如果啥也没有 → 后端没起来 \u002F 崩了 \u002F 端口写错了\n",[15,1145,1146],{},[38,1147,1148],{},"第 2 步:在服务器本机 curl 后端",[177,1150,1152],{"className":1108,"code":1151,"language":1110,"meta":182,"style":182},"curl -v http:\u002F\u002F127.0.0.1:3000\u002Fhealth\n",[19,1153,1154],{"__ignoreMap":182},[186,1155,1156,1158,1161],{"class":188,"line":189},[186,1157,25],{"class":1117},[186,1159,1160],{"class":1121}," -v",[186,1162,1164],{"class":1163},"sZZnC"," http:\u002F\u002F127.0.0.1:3000\u002Fhealth\n",[15,1166,1167,1168,1171,1172,1175],{},"如果这里都 Connection refused \u002F timeout → 后端没起来 \u002F 挂了 \u002F 绑错了 IP(比如后端 bind 了 ",[19,1169,1170],{},"192.168.x.x"," 而 Nginx 连的是 ",[19,1173,1174],{},"127.0.0.1",")。",[15,1177,1178],{},[38,1179,1180],{},"第 3 步:看 Nginx error log",[177,1182,1184],{"className":1108,"code":1183,"language":1110,"meta":182,"style":182},"tail -n 50 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[19,1185,1186],{"__ignoreMap":182},[186,1187,1188,1191,1194,1197],{"class":188,"line":189},[186,1189,1190],{"class":1117},"tail",[186,1192,1193],{"class":1121}," -n",[186,1195,1196],{"class":1121}," 50",[186,1198,1199],{"class":1163}," \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[15,1201,1202],{},"常见错误字符串一眼就能读:",[434,1204,1205,1211,1217,1223],{},[437,1206,1207,1210],{},[19,1208,1209],{},"connect() failed (111: Connection refused)"," → 后端没起来(和第 2 步一致)",[437,1212,1213,1216],{},[19,1214,1215],{},"connect() failed (110: Connection timed out)"," → Nginx 到后端之间网络不通(比如安全组 \u002F 防火墙 \u002F 跨机 IP)",[437,1218,1219,1222],{},[19,1220,1221],{},"upstream prematurely closed connection"," → 后端处理到一半崩了,看后端自己的日志",[437,1224,1225,1228],{},[19,1226,1227],{},"no live upstreams while connecting to upstream"," → upstream 里所有机器全挂了",[15,1230,1231],{},[38,1232,1233],{},"第 4 步:检查 SELinux(RHEL \u002F CentOS \u002F Rocky \u002F AlmaLinux 系列)",[15,1235,1236],{},"Debian\u002FUbuntu 默认没开 SELinux 基本不会遇到。RHEL 系默认的 SELinux 会阻止 Nginx 连外网和非标准端口:",[177,1238,1240],{"className":1108,"code":1239,"language":1110,"meta":182,"style":182},"# 查看布尔值\ngetsebool httpd_can_network_connect\n# 如果是 off,临时打开\nsetsebool -P httpd_can_network_connect 1\n",[19,1241,1242,1247,1255,1260],{"__ignoreMap":182},[186,1243,1244],{"class":188,"line":189},[186,1245,1246],{"class":1137},"# 查看布尔值\n",[186,1248,1249,1252],{"class":188,"line":195},[186,1250,1251],{"class":1117},"getsebool",[186,1253,1254],{"class":1163}," httpd_can_network_connect\n",[186,1256,1257],{"class":188,"line":201},[186,1258,1259],{"class":1137},"# 如果是 off,临时打开\n",[186,1261,1262,1265,1268,1271],{"class":188,"line":208},[186,1263,1264],{"class":1117},"setsebool",[186,1266,1267],{"class":1121}," -P",[186,1269,1270],{"class":1163}," httpd_can_network_connect",[186,1272,1273],{"class":1121}," 1\n",[28,1275],{},[11,1277,1279],{"id":1278},"常见问题-faq","常见问题 FAQ",[172,1281,1283],{"id":1282},"q1-我想让某一段路径-403-forbidden比如-env-文件怎么写","Q1. 我想让某一段路径 403 Forbidden(比如 .env 文件),怎么写?",[177,1285,1287],{"className":179,"code":1286,"language":181,"meta":182,"style":182},"location ~ \u002F\\.env {\n    deny all;\n    return 403;\n}\n",[19,1288,1289,1294,1299,1304],{"__ignoreMap":182},[186,1290,1291],{"class":188,"line":189},[186,1292,1293],{},"location ~ \u002F\\.env {\n",[186,1295,1296],{"class":188,"line":195},[186,1297,1298],{},"    deny all;\n",[186,1300,1301],{"class":188,"line":201},[186,1302,1303],{},"    return 403;\n",[186,1305,1306],{"class":188,"line":208},[186,1307,317],{},[172,1309,1311,1312,1314,1315,1318],{"id":1310},"q2-根路径-跳-docs-用-301-还是-302","Q2. 根路径 ",[19,1313,424],{}," 跳 ",[19,1316,1317],{},"\u002Fdocs"," 用 301 还是 302?",[15,1320,1321],{},"SEO 相关的永久跳转用 301(比如站点永久搬迁),临时调试用 302。",[177,1323,1325],{"className":179,"code":1324,"language":181,"meta":182,"style":182},"location = \u002F {\n    return 301 \u002Fdocs;\n}\n",[19,1326,1327,1332,1337],{"__ignoreMap":182},[186,1328,1329],{"class":188,"line":189},[186,1330,1331],{},"location = \u002F {\n",[186,1333,1334],{"class":188,"line":195},[186,1335,1336],{},"    return 301 \u002Fdocs;\n",[186,1338,1339],{"class":188,"line":201},[186,1340,317],{},[172,1342,1344],{"id":1343},"q3-proxy_pass-后面的-upstream-名能不能带路径","Q3. proxy_pass 后面的 upstream 名能不能带路径?",[15,1346,1347,1348,1351],{},"可以:",[19,1349,1350],{},"proxy_pass http:\u002F\u002Fdocs_backend\u002Fv1\u002F;",",路径拼接逻辑和直接写 URL 一样,末尾斜杠的规则完全相同。",[28,1353],{},[11,1355,1356],{"id":1356},"更正记录",[434,1358,1359],{},[437,1360,1361,1362,1364],{},"2026-08-09:首版发布。所有示例在 Nginx 1.26.2 上 ",[19,1363,21],{}," + curl 实跑通过。",[1366,1367,1368],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":182,"searchDepth":195,"depth":195,"links":1370},[1371,1372,1375,1380,1384,1385,1391],{"id":13,"depth":195,"text":13},{"id":32,"depth":195,"text":33,"children":1373},[1374],{"id":174,"depth":201,"text":175},{"id":417,"depth":195,"text":418,"children":1376},[1377,1378,1379],{"id":474,"depth":201,"text":475},{"id":546,"depth":201,"text":547},{"id":597,"depth":201,"text":598},{"id":646,"depth":195,"text":647,"children":1381},[1382,1383],{"id":673,"depth":201,"text":676},{"id":1001,"depth":201,"text":1002},{"id":1096,"depth":195,"text":1097},{"id":1278,"depth":195,"text":1279,"children":1386},[1387,1388,1390],{"id":1282,"depth":201,"text":1283},{"id":1310,"depth":201,"text":1389},"Q2. 根路径 \u002F 跳 \u002Fdocs 用 301 还是 302?",{"id":1343,"depth":201,"text":1344},{"id":1356,"depth":195,"text":1356},"2026-08-09","Nginx 反向代理是后端最常写的配置。本文用完整可运行示例讲清 6 条 location 匹配优先级、proxy_pass 带\u002F不带末尾斜杠的路径拼接差异、HTTPS + upstream 负载均衡、常见 502 排查 4 步。",false,"md",{},"\u002Fdocs\u002Fnginx\u002Freverse-proxy-proxypass",{"title":5,"description":1393},"reverse-proxy-proxypass","docs\u002Fnginx\u002Freverse-proxy-proxypass",[1402,1403,1404],"location 匹配优先级 1 句话记:完全匹配 = 优先前缀 > 正则(按书写顺序)= 前缀(最长)。写配置按这个顺序写,别全靠试。","proxy_pass 末尾斜杠有无 = 完全两种行为:带 \u002F = 把 location 的匹配部分从路径里「截掉」再拼后面;不带 \u002F = 把原路径整段拼过去。90% 的 404 都栽在这一条。","502 Bad Gateway 先按 4 步排查:后端有没有起来?→ proxy_pass 端口对不对?→ 能不能 curl localhost:端口通?→ \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log 看 connect() failed 原因。",[1406,1407,1408],"Nginx 反向代理","proxy_pass 斜杠","location 匹配","MJ8ltV_EdqNgObpKNAL4lGQ4cCViXVlGUT3lMvmHKEM",[],1794088893181]