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 反向代理问题

  •  1
     
  •   shiyuu · 2023-05-30 22:36:45 +08:00 · 5053 次点击
    这是一个创建于 580 天前的主题,其中的信息可能已经有所发展或是发生改变。

    家里的内网有多个应用,现在 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  
       2023-05-30 22:45:09 +08:00
    不要随便转换路径!

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

    页面 html 以及 javascript 对资源的引用又不会自动转换!
    shiyuu
        2
    shiyuu  
    OP
       2023-05-30 22:57:49 +08:00
    @busier
    去掉了 /也是一样的不行

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

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

    而不是 https://192.168.2.4:9444/chat/assets/index-44aaddda.js
    SKYNE
        3
    SKYNE  
       2023-05-30 23:14:21 +08:00   ❤️ 4
    exiaohao
        4
    exiaohao  
       2023-05-30 23:19:45 +08:00   ❤️ 2
    `location` 不能这样写,3 楼大佬正解

    一句话解释
    1) 楼主要理解 `^/some-path$` 和 `^/some-path/*` 的区别
    2) 另外就是访问到比如 /chat 后,对真实的后段怎么把 /chat 去掉
    icaolei
        5
    icaolei  
       2023-05-31 01:05:11 +08:00
    得看部署的项目本身是否支持动态路径,如果不支持的话只能考虑重写了。
    chenluo0429
        6
    chenluo0429  
       2023-05-31 01:35:40 +08:00 via Android   ❤️ 1
    应该说是绝大部分前端项目都不支持动态路径,资源往往是以基于根目录的绝对路径引用的。
    匹配未知目录 rewrite 到特定目录也只能处理一个目录下的服务,期望多个二级目录来切分服务,除非你愿意 fork 代码去修改,否则是无法实现你的目的。
    二级域名可能更合适一点
    blankmiss
        7
    blankmiss  
       2023-05-31 08:50:57 +08:00   ❤️ 1
    坑定不适用 为什么不搞个二级域名
    laoyutang
        8
    laoyutang  
       2023-05-31 09:14:37 +08:00 via Android
    不行的,你这玩法我之前就试过了,项目前端的 publicpath 你根本控制不了,除非你找源码来改。考虑下用 server_name 分流
    qwertty01
        9
    qwertty01  
       2023-05-31 09:23:50 +08:00
    可以参考我得配置,用域名,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  
       2023-05-31 09:40:07 +08:00
    用不同端口转发不同应用吧
    JayZXu
        11
    JayZXu  
       2023-05-31 09:55:39 +08:00
    用子路径的形式,目前大多数前端都不支持
    js 和静态资源的调用路径在打包之前就得定义好。

    建议使用不同端口号对应不同服务,
    或者用 server_name 绑定不同域名,本地改 hosts 来实现基于 server_name 的转发
    yujizmq
        12
    yujizmq  
       2023-05-31 10:06:36 +08:00
    @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  
       2023-05-31 10:22:54 +08:00
    楼主,建议你别用这种二级目录的方式,因为你转发的都基本上不是自己编译打包的项目,太难通用了。

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

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

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

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

    我也想把我部署的服务都弄成子目录形式,这样 frp 比较方便,现在用的 frp 便宜是便宜,就是一个通道只能绑三个域名 QAQ
    lovelylain
        21
    lovelylain  
       2023-05-31 14:23:37 +08:00
    子目录访问要求被反代的应用要么使用相对路径,要么本身有做支持,否则建议换域名或者端口。
    我也有追求所有应用都通过 homeassistant 以子目录访问:
    adguardhome: 相对路径
    nodered: 相对路径
    zigbee2mqtt: 相对路径
    frigate: 本身有做适配,传 X-Ingress-Path 头
    openwrt 里的 netdata: 相对路径
    openwrt 里的 ttyd: 相对路径
    openwrt: 最开始换了域名反代,后来用 sub_filter 等解决了
    虽然大部分都实现了,但还是建议对于非相对路径的直接换域名不折腾。
    yxisenx
        22
    yxisenx  
       2023-05-31 15:01:43 +08:00
    懒得麻烦就用二级域名
    luhuisicnu
        23
    luhuisicnu  
       2023-05-31 15:32:11 +08:00
    我们有这样的使用案例,统一一个域名做入口,根据 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  
       2023-05-31 15:34:46 +08:00
    重新排版试试
    我们有这样的使用案例,统一一个域名做入口,根据 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
       2023-05-31 16:29:33 +08:00
    @qwertty01 你的抄了一下可以用。看来只能用域名来搞了,这样可以在路由器少映射很多端口。阿里的免费证书不支持泛域名,得每个域名都申请一个证书,很麻烦,兄弟怎么解决?
    vivisidea
        26
    vivisidea  
       2023-05-31 16:39:07 +08:00
    @shiyuu 内网如果有 dns 的话,可以自己加个 dns 解析,或者设备不多的话,手动每个设备加个 hosts

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

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