V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NGINX
NGINX Trac
3rd Party Modules
Security Advisories
CHANGES
OpenResty
ngx_lua
Tengine
在线学习资源
NGINX 开发从入门到精通
NGINX Modules
ngx_echo
shiyuu
V2EX  ›  NGINX

一个 Nginx 反向代理问题

  •  
  •   shiyuu · 302 天前 · 4163 次点击
    这是一个创建于 302 天前的主题,其中的信息可能已经有所发展或是发生改变。

    家里的内网有多个应用,现在 nginx 部署在一台单独的服务器上 192.168.2.180 现在想改造一下访问方式变成访问:

    https://192.168.2.180:9444/chat 可以访问到部署的 chatgpt: http://192.168.2.4:50021

    https://192.168.2.180:9444/pve 可以访问到 PVE 虚拟机 https://192.168.2.2:8006/

    https://192.168.2.180:9444/adguard/ 可以访问到 adguard 的应用 http://192.168.2.200/

    但是按照下面的配置,访问到的页面不全,页面各种缺失。 是不是这样的方法不适用??

    worker_processes auto;
    events {
      worker_connections 1024;
    }
    
    http {
      server {
        listen 9444 ssl;
        server_name 192.168.2.180;
        ssl_certificate /etc/nginx/ssl.crt;
        ssl_certificate_key /etc/nginx/ssl.key;
        location /chat/ {
          proxy_pass http://192.168.2.4:50021/;
          proxy_set_header Host $proxy_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        location /pve/ {
          proxy_pass https://192.168.2.2:8006/;
          proxy_set_header Host $proxy_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Accept-Encoding "";
          sub_filter_types *;
        }
        location /adguard/ {
          proxy_pass http://192.168.2.200/;
          proxy_set_header Host $proxy_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Accept-Encoding "";
          sub_filter_types *;
        }
      }
    }
    
    32 条回复    2023-06-01 14:34:04 +08:00
    busier
        1
    busier  
       302 天前
    不要随便转换路径!

    本来是 / ,你非要转换成 /chat/ 、 /pve/ 、 /adguard/

    页面 html 以及 javascript 对资源的引用又不会自动转换!
    shiyuu
        2
    shiyuu  
    OP
       302 天前
    @busier
    去掉了 /也是一样的不行

    比如 /chat 这个目录,加载这些页面的元素就

    会变成 https://192.168.2.4:9444/assets/index-44aaddda.js

    而不是 https://192.168.2.4:9444/chat/assets/index-44aaddda.js
    exiaohao
        4
    exiaohao  
       302 天前   ❤️ 2
    `location` 不能这样写,3 楼大佬正解

    一句话解释
    1) 楼主要理解 `^/some-path$` 和 `^/some-path/*` 的区别
    2) 另外就是访问到比如 /chat 后,对真实的后段怎么把 /chat 去掉
    icaolei
        5
    icaolei  
       302 天前
    得看部署的项目本身是否支持动态路径,如果不支持的话只能考虑重写了。
    chenluo0429
        6
    chenluo0429  
       302 天前 via Android   ❤️ 1
    应该说是绝大部分前端项目都不支持动态路径,资源往往是以基于根目录的绝对路径引用的。
    匹配未知目录 rewrite 到特定目录也只能处理一个目录下的服务,期望多个二级目录来切分服务,除非你愿意 fork 代码去修改,否则是无法实现你的目的。
    二级域名可能更合适一点
    blankmiss
        7
    blankmiss  
       302 天前   ❤️ 1
    坑定不适用 为什么不搞个二级域名
    laoyutang
        8
    laoyutang  
       302 天前 via Android
    不行的,你这玩法我之前就试过了,项目前端的 publicpath 你根本控制不了,除非你找源码来改。考虑下用 server_name 分流
    qwertty01
        9
    qwertty01  
       302 天前
    可以参考我得配置,用域名,https 也能搞定

    ```
    server {
    listen 443 ssl;
    # listen [::]:443;
    server_name test.test.com;
    ssl_certificate /etc/nginx/certs/test.com.pem;
    ssl_certificate_key /etc/nginx/certs/test.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    access_log /var/log/nginx/https.access.log main;

    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    }

    error_page 404 /404.html;

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }

    upstream registry {
    server 192.168.31.123:5000;
    }
    upstream image {
    server 192.168.31.222:8080;
    }
    upstream esxi {
    server 192.168.31.77:443;
    }
    upstream rmi {
    server 192.168.31.88:443;
    }



    server {
    listen 443 ssl;
    # listen [::]:443;
    server_name registry.test.com;
    ssl_certificate /etc/nginx/certs/test.com.pem;
    ssl_certificate_key /etc/nginx/certs/test.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    access_log /var/log/nginx/https.access.log main;

    location / {
    proxy_pass http://registry;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }

    server {
    listen 443 ssl;
    # listen [::]:443;
    server_name image.test.com;
    ssl_certificate /etc/nginx/certs/test.com.pem;
    ssl_certificate_key /etc/nginx/certs/test.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    access_log /var/log/nginx/https.access.log main;

    location / {
    proxy_pass http://image;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }

    server {
    listen 443 ssl;
    # listen [::]:443;
    server_name esxi.test.com;
    ssl_certificate /etc/nginx/certs/test.com.pem;
    ssl_certificate_key /etc/nginx/certs/test.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    access_log /var/log/nginx/host.access.log main;

    location / {
    proxy_pass https://esxi;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }


    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    error_page 404 /404.html;
    }

    server {
    listen 443 ssl;
    # listen [::]:443;
    server_name rmi.test.com;
    ssl_certificate /etc/nginx/certs/test.com.pem;
    ssl_certificate_key /etc/nginx/certs/test.com.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    access_log /var/log/nginx/host.access.log main;

    location / {
    proxy_pass https://rmi;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }


    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    error_page 404 /404.html;
    }

    ```
    awinds
        10
    awinds  
       302 天前
    用不同端口转发不同应用吧
    JayZXu
        11
    JayZXu  
       302 天前
    用子路径的形式,目前大多数前端都不支持
    js 和静态资源的调用路径在打包之前就得定义好。

    建议使用不同端口号对应不同服务,
    或者用 server_name 绑定不同域名,本地改 hosts 来实现基于 server_name 的转发
    yujizmq
        12
    yujizmq  
       302 天前
    @shiyuu 虽然这个方法有点脏,但应该可以解决,参考:

    sub_filter_once off;
    sub_filter_types text/html text/css application/javascript;
    sub_filter ' href="/' ' href="/chat/';
    sub_filter ' src="/' ' src="/chat/';
    sub_filter ' action="/' ' action="/chat/';
    sub_filter '"assets/' '"chat/assets/';
    sub_filter '"assets/' '"chat/assets/';
    sub_filter '"/assets/' '"/chat/assets/';
    # #sub_filter 'baseURL:"/api"' 'baseURL:"/chat/api"';
    sub_filter 'url:"/' 'url:"/chat/';
    sub_filter '/sw.js' '/chat/sw.js';
    jifengg
        13
    jifengg  
       302 天前
    楼主,建议你别用这种二级目录的方式,因为你转发的都基本上不是自己编译打包的项目,太难通用了。

    建议通过域名来转发。
    如果自己拥有一个域名,可以把不同的二级域名解析到 192.168.2.180 ,nginx“分别配置 server”,写好 server_name ,进行转发;
    如果没有,在机器本地 hosts 随便配置个域名指向 192.168.2.180 也是 ok 的。
    clf
        14
    clf  
       302 天前
    你没法保证三个服务客户端( web 网页)请求的 url 都能正常分流。

    比如第一个应用如果前端路由打包的时候 base 就是 / ,那么请求的 js 资源可能都是 /assist/xxx.js ,如果 3 个项目都是 /assist 的前缀,你根本没法分流。更别提调用的后端接口了。
    adoal
        15
    adoal  
       302 天前
    现在流行的前后端分离项目,最终部署出来的前端静态资源都是“编译”过的,项目内引用路径是写死的,放到反代子路径下,如果你不能自己重新 build 的话,那除了用 sub_filter 来做 dirty hack 之外真没啥好办法。

    其实在 good old CGI 时代,前后端不分离的系统,写得体贴的系统会根据 web server 或者反代传过来的 SCRIPT_NAME + PATH_INFO 来自适应地“意识到自己运行在子路径下”从而调整页面内路径引用的生成规则。缺点时静态资源的路径每次都要计算,性能有影响,当然对大部分系统来说没必要担心这个性能。
    adoal
        16
    adoal  
       302 天前
    adoal
        17
    adoal  
       302 天前
    再比如 Flask 框架官方文档的解决办法 https://flask.palletsprojects.com/en/2.0.x/deploying/fastcgi/
    Macv1994
        18
    Macv1994  
       302 天前
    二级域名不就行了吗?
    huajia2005
        19
    huajia2005  
       302 天前
    只是本地访问的话,改 host 配二级域名,然后用 server_name 进行转发,大部分项目而且不是自己的项目的话,静态资源都会有路径问题
    IvanLi127
        20
    IvanLi127  
       302 天前
    这操作不是完全通用,很多项目不一定支持子目录部署,因为没做适配。你想搞这个的话,就三种情况:

    - 项目使用相对路径,直接反向代理配置就好了
    - 项目支持在编译时通过环境变量配置 BASE 路径,拉源码配一下重新构建前端项目
    - 项目直接硬编码路径了,那就得拉源码一个个改了,改完提 PR❤️

    我也想把我部署的服务都弄成子目录形式,这样 frp 比较方便,现在用的 frp 便宜是便宜,就是一个通道只能绑三个域名 QAQ
    lovelylain
        21
    lovelylain  
       302 天前
    子目录访问要求被反代的应用要么使用相对路径,要么本身有做支持,否则建议换域名或者端口。
    我也有追求所有应用都通过 homeassistant 以子目录访问:
    adguardhome: 相对路径
    nodered: 相对路径
    zigbee2mqtt: 相对路径
    frigate: 本身有做适配,传 X-Ingress-Path 头
    openwrt 里的 netdata: 相对路径
    openwrt 里的 ttyd: 相对路径
    openwrt: 最开始换了域名反代,后来用 sub_filter 等解决了
    虽然大部分都实现了,但还是建议对于非相对路径的直接换域名不折腾。
    yxisenx
        22
    yxisenx  
       302 天前
    懒得麻烦就用二级域名
    luhuisicnu
        23
    luhuisicnu  
       302 天前
    我们有这样的使用案例,统一一个域名做入口,根据 path 来区分业务,转发到内网的其他域名下,大概像这样:
    server {
    listen 80;
    server_name api.test.com;

    location /server1/ {
    rewrite /server1/(.*) /$1 break;
    proxy_pass http://1.1.1.1:80;
    }

    location /server2/ {
    rewrite /server2/(.*) /$1 break;
    proxy_set_header Host server2.local.test.com;
    proxy_pass http://2.2.2.2:80;
    }

    location /server3/ {
    proxy_set_header Host server3.local.test.com;
    proxy_pass http://3.3.3.3:80/;
    }
    }
    server1 是不带域名的,server2 带域名转发,所以要设置内部域名的 header ,server3 是 server2 的另一种写法。转发后的 path ,是不带 server1, server2 ,server3 的。可以试试。
    luhuisicnu
        24
    luhuisicnu  
       302 天前
    重新排版试试
    我们有这样的使用案例,统一一个域名做入口,根据 path 来区分业务,转发到内网的其他域名下,大概像这样:
    ```
    server {
    listen 80;
    server_name api.test.com;

    location /server1/ {
    rewrite /server1/(.*) /$1 break;
    proxy_pass http://1.1.1.1:80;
    }

    location /server2/ {
    rewrite /server2/(.*) /$1 break;
    proxy_set_header Host server2.local.test.com;
    proxy_pass http://2.2.2.2:80;
    }

    location /server3/ {
    proxy_set_header Host server3.local.test.com;
    proxy_pass http://3.3.3.3:80/;
    }
    }
    ```
    server1 是不带域名的,server2 带域名转发,所以要设置内部域名的 header ,server3 是 server2 的另一种写法。转发后的 path ,是不带 server1, server2 ,server3 的。可以试试。
    shiyuu
        25
    shiyuu  
    OP
       302 天前
    @qwertty01 你的抄了一下可以用。看来只能用域名来搞了,这样可以在路由器少映射很多端口。阿里的免费证书不支持泛域名,得每个域名都申请一个证书,很麻烦,兄弟怎么解决?
    vivisidea
        26
    vivisidea  
       302 天前
    @shiyuu 内网如果有 dns 的话,可以自己加个 dns 解析,或者设备不多的话,手动每个设备加个 hosts

    我用 ikuai 软路由,内部搞了个 .lan 域名,比如 nas.lan 就是群辉,router.lan 就是路由器
    qwertty01
        27
    qwertty01  
       302 天前
    @shiyuu #25 我因为是自己搞着玩得,用的是 mkcert 生成的泛域名证书,然后让浏览器信任一下就可以了

    https://www.jianshu.com/p/7cb5c2cffaaa
    shiyuu
        28
    shiyuu  
    OP
       302 天前
    @qwertty01 不过我现在还遇到个问题,抄你的这么写了 4 个域名对应的系统。
    pve.xx.com
    nas.xx.com
    adguard.xx.com
    route.xx.com
    用对应域名都能正常访问到对应的系统了。

    但是有个情况,另外还有一些域名比如 alist.xx.com ,我 nginx 上都还没配置,直接访问的话会居然能访问到 pve 的系统上,我没设置 error_page ,是不是也要配置一个 error_page 。

    upstream pve {
    server 192.168.2.2:8006;
    }
    upstream nas {
    server 192.168.2.4:5000;
    }
    upstream adguard {
    server 192.168.2.200;
    }
    upstream route {
    server 192.168.2.1;
    }
    qwertty01
        29
    qwertty01  
       302 天前
    @shiyuu #28 这个我就不太清楚了,你看一下解析配置的有没有问题,或者看一下 access.log
    shiyuu
        30
    shiyuu  
    OP
       301 天前
    @qwertty01 知道怎么弄了, “添加一个新的 server 块,用于处理所有未知域名。我们使用 default_server 参数将其指定为默认监听器,并将 server_name 设置为 _。在 location 规则中,我们返回一个 404 错误,告诉客户端该域名未配置。如果需要,您可以将此规则更改为一个自定义的重定向或其他行为。”
    mk0114
        31
    mk0114  
       301 天前
    泛域名证书用 certbot 吧,就是三个月要申请一次,或者你的域名解析商能提供 api 的话,可以做自动化更新证书。
    bingfengfeifei
        32
    bingfengfeifei  
       301 天前
    优雅的方法就是域名,12 楼的方法也可以解决问题。
    我是使用了 12 楼的这种方法,但是不一定每一个页面都能成功。这个方法是修改 WEB 返回内容,将返回的资源绝对路径加上你的转发前缀。
    有些页面他的请求 URL 是写到 JS 里面的,掺杂在代码里面,非常难以替换。
    而且有些页面会有跳转重定向,在 location 字段里面跳转,也需要特殊处理。没有通用的方法
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3253 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 12:04 · PVG 20:04 · LAX 05:04 · JFK 08:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.