前言
本文记录一个已经落地的真实部署:Vue3 前端系统原本通过 http://119.45.157.27/IPESAgent/ 对外访问,需求方认为 HTTP 不安全,希望统一改成 https://www.jintanfuture.com/IPESAgent/ 。项目不能迁移到官网服务器,域名和证书又由需求方管理,于是最终采用了两台服务器、两层 Nginx、一个 HTTPS 入口的方案。
这不是把 URL 里的 http 替换成 https 那么简单。真实链路同时牵涉证书放置位置、二级路径、Vue3 构建基础路径、Nginx location 优先级、接口前缀、SPA 路由回退、Cookie、SSE、iframe 和服务器间链路安全。其中最隐蔽的故障是:入口 HTML 能正常返回,页面却一直停在"正在加载系统资源,请耐心等待"。最后定位到官网已有的 JS/CSS 正则规则截获了业务系统静态资源。
读完本文,可以直接获得两端 Nginx 的可用配置模板,也能掌握一套分层排查方法。示例里的公网 IP、域名和路径来自本次项目;目录、端口与证书路径需要按自己的服务器替换。
关键词: Vue3、HTTPS、Nginx、反向代理、SSL 证书、二级目录、双服务器、SPA、iframe、Mixed Content
原系统地址为:
text
http://119.45.157.27/IPESAgent/login?redirect=/home
最终希望用户访问:
text
https://www.jintanfuture.com/IPESAgent/
两个地址背后不是同一台服务器:
119.45.157.27:我们的阿里云服务器,负责运行 IPESAgent 前端及对应接口代理;www.jintanfuture.com:需求方官网服务器,已经拥有域名和 HTTPS 证书;- 需求方服务器不保存 IPESAgent 的前端文件,而是通过 Nginx 按路径把请求转发给阿里云服务器。
最终链路如下:
text
浏览器
│
│ HTTPS:用户只看到 www.jintanfuture.com
▼
需求方官网 Nginx(443,安装域名证书,TLS 终止)
│
│ HTTP 或 HTTPS:服务器到服务器的反向代理
▼
阿里云 Nginx(119.45.157.27)
├── /IPESAgent/ → Vue3 静态资源
└── /IPESAgent-api/ → 后端服务
本次问题最终解决后,以下地址可以正常访问:
text
https://www.jintanfuture.com/IPESAgent/
这套方案的关键不是简单地"把 http 改成 https",而是同时处理好以下六件事:
- HTTPS 证书由哪一层服务器持有;
/IPESAgent/二级路径如何转发并保持不丢失;- Vue3 构建基础路径是否与部署路径一致;
- JS、CSS 等静态资源是否被其他 Nginx 正则规则截获;
/IPESAgent-api/是否仍然通过同源 HTTPS 地址访问;- SPA 路由、重定向、Cookie、SSE/WebSocket 和 iframe 是否兼容。
浏览器只和需求方官网建立 HTTPS 连接;需求方 Nginx 再把 /IPESAgent/ 与 /IPESAgent-api/ 转发给阿里云源站。 这是全文最重要的边界。如图1所示,对外域名、TLS 终止点和真实业务源站属于三层不同概念,不要把它们混成"给前端加一个证书"。

图1 双服务器 Nginx 反向代理与 HTTPS 域名接入
一、项目背景与之前篇衔接
1.1 之前篇采用 IIS 与 Nginx 双层架构
之前篇《Server 16,Windows Server 环境下 Vue3 项目部署实战:IIS + Nginx 配置 IPAgent 二级目录完整教程》讨论的是本地图书馆服务器上的部署方式:
text
浏览器 HTTPS
→ IIS(监听 443、安装证书、完成 TLS 解密)
→ 本机 Nginx(接收 IIS 转发过来的 HTTP)
→ /IPAgent/、/IPWeave/ 等不同系统
1.2 本文改为两台服务器协同
本文场景与之前篇有两个明显差异:
- 之前是同一台 Windows Server 上的 IIS + Nginx 双层架构;
- 本文是两台 Linux/云服务器之间的 Nginx 反向代理,官网与业务系统分别部署。
虽然部署环境不同,但核心思想一致:**HTTPS 证书配置在真正接收浏览器 TLS 连接的最外层入口,后续再由代理服务器按照路径分发请求。**之前篇解决的是"同一台 Windows Server 如何让 IIS 与 Nginx 分工",本文解决的是"官网服务器如何代理另一台阿里云服务器上的 Vue3 系统"。两篇文章放在一起看,能把 TLS 终止、路径分发和源站部署三件事拆开理解。
1.3 本次改造的验收口径
仅仅能打开登录页不算完成。这个项目的验收口径包括:
https://www.jintanfuture.com/IPESAgent/能直接打开并刷新任意前端路由;- 登录、退出、重新登录和超时跳转都不能离开
www.jintanfuture.com; - JS、CSS、字体、图片和接口请求全部通过 HTTPS 发起;
/IPESAgent-api/、上传下载、SSE 或 WebSocket 能按业务需要工作;<iframe src="/IPESAgent/"></iframe>在需求方官网内可以正常加载;http://119.45.157.27/IPESAgent/是否继续保留,由源站安全策略决定,但它不再是对用户公布的入口。
"首页出现了"只能证明 index.html 到达浏览器,不能证明 Vue 应用、接口和登录态已经正常。 后文的 Loading 故障正是这个差异造成的。
二、HTTP、HTTPS、域名与证书
2.1 HTTP 的特点
HTTP 是浏览器与服务器交换请求和响应的应用层协议。传统 HTTP 通常使用 80 端口,传输内容本身不经过 TLS 加密。
如果登录名、Token、Cookie 或业务数据通过普通 HTTP 在不可信网络上传输,中间节点存在窃听或篡改数据的可能。因此,需求方提出"HTTP 不安全"是合理的。
2.2 HTTPS 不是另一套业务协议
如图2所示,HTTPS 没有改变上层业务请求的语义,而是在 HTTP 与传输层之间增加 TLS 保护。浏览器地址栏里的锁标识只能说明当前连接通过了证书与加密校验,不能替代应用自身的身份认证和权限控制。

图2 HTTP 明文传输与 HTTPS TLS 加密对比
HTTPS 可以理解为:
text
HTTPS = HTTP + TLS
业务层仍然是 HTTP,只是在 HTTP 报文进入网络之前,先通过 TLS 建立安全通道。TLS 主要解决三个问题:
| 能力 | 作用 |
|---|---|
| 加密性 | 防止传输内容被直接窃听 |
| 完整性 | 防止报文在传输途中被静默篡改 |
| 身份认证 | 通过证书确认当前连接的服务器身份 |
浏览器访问 https://www.jintanfuture.com 时,会先校验服务器证书,再协商 TLS 版本、加密套件和会话密钥,之后才发送 HTTP 请求。
2.3 为什么 HTTPS 页面不能直接嵌套 HTTP 系统
如果官网使用 HTTPS,却在页面中这样嵌入:
html
<iframe src="http://119.45.157.27/IPESAgent/"></iframe>
那么 HTTPS 页面正在加载一个 HTTP iframe,这属于 Mixed Content(混合内容)。现代浏览器通常会阻止这类不安全的活动内容。即使某个浏览器暂时允许,登录、Cookie、跳转和接口请求也可能出现异常。
因此不能仅仅把 HTTP 地址放进 iframe,而应让浏览器始终访问同源 HTTPS 地址:
html
<iframe src="/IPESAgent/"></iframe>
浏览器最终请求的是:
text
https://www.jintanfuture.com/IPESAgent/
至于 Nginx 在服务器内部如何把请求转到 119.45.157.27,浏览器并不知道,也不需要知道。
2.4 IP 地址能不能直接改成 HTTPS
工程上最常见、最稳妥的做法是准备一个可控制的域名:
传统项目中最稳妥的做法是:
- 准备一个自己可控制的域名;
- 将域名通过 DNS 的 A 记录解析到服务器公网 IP;
- 为该域名申请 TLS 证书;
- 在 Nginx、IIS、负载均衡或 CDN 上配置证书;
- 将 80 端口请求重定向到 443。
证书校验的是客户端正在访问的主机身份。证书保护的是域名或 IP,而不是服务器上的 /IPESAgent/ 路径。也就是说,一张有效的 www.jintanfuture.com 证书可以同时覆盖:
text
https://www.jintanfuture.com/
https://www.jintanfuture.com/IPESAgent/
https://www.jintanfuture.com/another-system/
不需要为每个二级路径分别申请证书。
2.5 IP 证书不是绝对不可能
过去,大多数免费证书服务只给域名签发证书,所以工程实践中经常简化为"必须有域名,IP 不能申请证书"。
截至 2026 年,Let's Encrypt 已经向公众提供 IP 地址证书,但其证书周期较短,对自动签发、自动续期和客户端工具版本提出了更高要求。一些商业 CA 也可能提供 IP 证书服务。
因此更准确的表述是:
公网 IP 并非技术上绝对不能申请证书,但域名证书在兼容性、续期、迁移、运维、品牌识别和 HSTS 等方面仍然更适合常规生产系统。
对于本文项目,需求方已有 www.jintanfuture.com 和有效证书,直接复用官网 HTTPS 入口显然比单独为 119.45.157.27 申请短周期 IP 证书更合理。
2.6 域名解析是否必须指向阿里云服务器
不一定。
如果采用"单服务器部署",域名应解析到运行 IPESAgent 的服务器。
如果采用本文的"双服务器反向代理",www.jintanfuture.com 应继续解析到需求方官网服务器。需求方 Nginx 收到 /IPESAgent/ 后,再在服务端转发到 119.45.157.27。域名不需要直接解析到阿里云服务器。
三、二级路径与部署架构
3.1 /IPESAgent/ 不是二级域名
以下两个概念容易混淆:
text
ipesagent.jintanfuture.com # 子域名或二级域名
www.jintanfuture.com/IPESAgent/ # 同一域名下的二级路径
本文使用的是第二种方案,即二级路径部署。其优点是:
- 可以复用官网域名和证书;
- iframe 使用相对地址后可保持同源;
- 浏览器不需要跨域访问阿里云 IP;
- 官网可以通过一个 Nginx
server块管理多个业务系统。
它对前端构建路径和 Nginx URI 转发要求也更严格:Vue 项目必须知道自己部署在 /IPESAgent/,而不是网站根路径 /。
3.2 一台服务器时
如果官网和新系统在同一台服务器,可以直接由一个 Nginx 完成:
text
www.jintanfuture.com/ → 官网目录
www.jintanfuture.com/IPESAgent/ → IPESAgent dist 目录
www.jintanfuture.com/IPESAgent-api/ → 本机后端端口
证书只配置一次,Nginx 通过不同 location 区分系统。
3.3 两台服务器时
如图3所示,单服务器方案由同一个 Nginx 读取官网文件与 IPESAgent 文件;双服务器方案则由官网 Nginx 充当统一入口,业务文件仍留在阿里云。两者对浏览器暴露的 URL 可以完全相同,差别只存在于服务器内部。

图3 单服务器部署与双服务器反向代理对比
本文就是两台服务器:
| 服务器 | 职责 | 对外地址 |
|---|---|---|
| 需求方官网服务器 | 域名、证书、官网、统一入口、反向代理 | https://www.jintanfuture.com |
| 我方阿里云服务器 | IPESAgent 静态文件及接口入口 | http://119.45.157.27 |
此时需要两端 Nginx 配合:
- 官网 Nginx:识别
/IPESAgent/和/IPESAgent-api/,转给阿里云; - 阿里云 Nginx:真正提供静态资源、SPA 回退和后端接口代理。
3.4 本次实际访问链路
如图4所示,浏览器、官网入口和阿里云源站是三个观察点。排障时必须确认请求在哪一层消失,不能只盯着浏览器里的 Loading 文案。

图4 需求方官网与阿里云源站的完整访问链路
假设用户访问:
text
https://www.jintanfuture.com/IPESAgent/login?redirect=/home
完整处理流程为:
- DNS 将
www.jintanfuture.com解析到需求方官网服务器; - 浏览器与需求方 Nginx 的 443 端口建立 TLS 连接;
- 需求方 Nginx 使用
www.jintanfuture.com证书完成身份校验和解密; - Nginx 的
location ^~ /IPESAgent/命中该请求; - 请求 URI 原样代理到
http://119.45.157.27/IPESAgent/login?redirect=/home; - 阿里云 Nginx 返回 Vue 的
index.html; - 浏览器继续通过官网域名请求 JS、CSS、图片和接口;
/IPESAgent-api/请求同样由需求方 Nginx 转发到阿里云;- 对浏览器而言,整个过程始终是
https://www.jintanfuture.com同源访问。
#mermaid-svg-7DTIrgRJzmBPo3aj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7DTIrgRJzmBPo3aj .error-icon{fill:#552222;}#mermaid-svg-7DTIrgRJzmBPo3aj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7DTIrgRJzmBPo3aj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7DTIrgRJzmBPo3aj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7DTIrgRJzmBPo3aj .marker.cross{stroke:#333333;}#mermaid-svg-7DTIrgRJzmBPo3aj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7DTIrgRJzmBPo3aj p{margin:0;}#mermaid-svg-7DTIrgRJzmBPo3aj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster-label text{fill:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster-label span{color:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster-label span p{background-color:transparent;}#mermaid-svg-7DTIrgRJzmBPo3aj .label text,#mermaid-svg-7DTIrgRJzmBPo3aj span{fill:#333;color:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj .node rect,#mermaid-svg-7DTIrgRJzmBPo3aj .node circle,#mermaid-svg-7DTIrgRJzmBPo3aj .node ellipse,#mermaid-svg-7DTIrgRJzmBPo3aj .node polygon,#mermaid-svg-7DTIrgRJzmBPo3aj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7DTIrgRJzmBPo3aj .rough-node .label text,#mermaid-svg-7DTIrgRJzmBPo3aj .node .label text,#mermaid-svg-7DTIrgRJzmBPo3aj .image-shape .label,#mermaid-svg-7DTIrgRJzmBPo3aj .icon-shape .label{text-anchor:middle;}#mermaid-svg-7DTIrgRJzmBPo3aj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7DTIrgRJzmBPo3aj .rough-node .label,#mermaid-svg-7DTIrgRJzmBPo3aj .node .label,#mermaid-svg-7DTIrgRJzmBPo3aj .image-shape .label,#mermaid-svg-7DTIrgRJzmBPo3aj .icon-shape .label{text-align:center;}#mermaid-svg-7DTIrgRJzmBPo3aj .node.clickable{cursor:pointer;}#mermaid-svg-7DTIrgRJzmBPo3aj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7DTIrgRJzmBPo3aj .arrowheadPath{fill:#333333;}#mermaid-svg-7DTIrgRJzmBPo3aj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7DTIrgRJzmBPo3aj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7DTIrgRJzmBPo3aj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7DTIrgRJzmBPo3aj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7DTIrgRJzmBPo3aj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7DTIrgRJzmBPo3aj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster text{fill:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj .cluster span{color:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7DTIrgRJzmBPo3aj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7DTIrgRJzmBPo3aj rect.text{fill:none;stroke-width:0;}#mermaid-svg-7DTIrgRJzmBPo3aj .icon-shape,#mermaid-svg-7DTIrgRJzmBPo3aj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7DTIrgRJzmBPo3aj .icon-shape p,#mermaid-svg-7DTIrgRJzmBPo3aj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7DTIrgRJzmBPo3aj .icon-shape .label rect,#mermaid-svg-7DTIrgRJzmBPo3aj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7DTIrgRJzmBPo3aj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7DTIrgRJzmBPo3aj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7DTIrgRJzmBPo3aj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTPS :443
/IPESAgent/
/IPESAgent-api/
静态文件
接口代理
浏览器
需求方官网 Nginx
阿里云 Nginx
Vue3 dist
后端服务
3.5 开始配置前的检查清单
不要一上来就修改 Nginx。先确认以下条件:
www.jintanfuture.com已正确解析到需求方服务器;- 官网的 HTTPS 证书有效,证书域名覆盖
www.jintanfuture.com; - 需求方服务器可以访问
http://119.45.157.27/IPESAgent/; - 需求方服务器可以访问
http://119.45.157.27/IPESAgent-api/; - 阿里云安全组和系统防火墙允许需求方服务器访问;
- Vue3 的生产环境基础路径为
/IPESAgent/; - 前端接口地址使用
/IPESAgent-api/等相对路径,不要写死 HTTP IP; - 两台服务器时间正确,便于分析日志和证书问题;
- 修改前已备份两端 Nginx 配置。
可以先在需求方服务器执行:
bash
curl -I http://119.45.157.27/IPESAgent/
curl -I http://119.45.157.27/IPESAgent-api/
如果这一步都连接不上,先检查阿里云安全组、系统防火墙、源站 Nginx 和后端端口。此时修改官网 Nginx 没有意义。
四、Vue3 与两端 Nginx 配置
4.1 设置 Vite 基础路径
Vue3 + Vite 项目部署在二级路径时,应设置:
js
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
base: '/IPESAgent/'
})
构建后的资源地址才会类似:
text
/IPESAgent/assets/index-xxxx.js
/IPESAgent/assets/index-xxxx.css
如果仍然是 /assets/index-xxxx.js,浏览器会到官网根目录寻找资源,页面通常会白屏或一直停留在 Loading。
4.2 Vue Router 使用正确的 history base
推荐让路由使用 Vite 自动生成的基础路径:
js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
4.3 接口必须使用同源相对路径
生产环境中不要继续写:
text
http://119.45.157.27/IPESAgent-api/
而应使用:
text
/IPESAgent-api/
这样浏览器会自动请求:
text
https://www.jintanfuture.com/IPESAgent-api/
再由官网 Nginx 代理到阿里云服务器,从根本上避免混合内容和浏览器跨域问题。
4.4 重新构建并部署
bash
npm run build:prod
将新的 dist 部署到阿里云服务器配置的目录。只改 Nginx 而不重新构建前端,旧资源路径仍可能导致页面失败。
4.5 阿里云源站 Nginx 配置( 完整配置 )
下面给出一个与本文路径结构一致的源站模板。静态目录和后端端口应替换为自己的实际值。
nginx
user www-data;
worker_processes auto;
pid /usr/local/nginx-1.30.0/logs/nginx.pid;
events {
worker_connections 2048;
}
http {
include /usr/local/nginx-1.30.0/conf/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# =========================
# 文件上传大小
# =========================
client_max_body_size 100m;
# =========================
# 所有代理请求统一 5 分钟
# =========================
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# =========================
# 关闭代理缓冲
# =========================
proxy_buffering off;
# =========================
# 日志
# =========================
access_log /usr/local/nginx-1.30.0/logs/access.log;
error_log /usr/local/nginx-1.30.0/logs/error.log warn;
# ============================================================
# 唯一 server 块:通过 80 端口提供 HTTP 服务
# server_name 同时支持 IP 和域名,以便官网反向代理请求匹配
# IPWeave 配置原封不动
# ============================================================
server {
listen 80;
listen [::]:80;
server_name 119.45.157.27 www.jintanfuture.com;
root /usr/local/nginx-1.30.0/html;
index index.html;
# =========================
# IPESAgent 前端
# =========================
location /IPESAgent/ {
try_files $uri $uri/ /IPESAgent/index.html;
}
# =========================
# IPESAgent 后端 API
# 后端服务:8040
# =========================
location /IPESAgent-api/ {
proxy_pass http://127.0.0.1:8040/;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
}
# =========================
# IPWeave 前端
# =========================
location /IPWeave/ {
try_files $uri $uri/ /IPWeave/index.html;
}
# =========================
# IPWeave 后端 API,后端服务:8090
# =========================
location /IPWeave-api/ {
proxy_pass http://127.0.0.1:8090/;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
}
}
}
上面使用了 $connection_upgrade,需要在 Nginx 的 http {} 层定义:
nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
如果源站既可能被外层 HTTPS 代理访问,也需要保留直接 HTTP 调试能力,再在同一个 http {} 中加入:
nginx
map $http_x_forwarded_proto $forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}
如果后端本身就要求保留 /IPESAgent-api/ 前缀,则不要在 proxy_pass 后写 /:
nginx
location ^~ /IPESAgent-api/ {
proxy_pass http://127.0.0.1:8080;
}
proxy_pass 是否带 URI 会影响路径替换,这是二级路径代理中最容易写错的地方之一。
4.6 需求方官网 Nginx 配置( server 块 )
4.6.1 推荐的完整核心模板
下面配置重点展示 HTTPS、官网原有业务以及两个代理路径如何共存。证书路径、网站根目录、PHP 配置和日志路径应以需求方服务器实际环境为准。
nginx
server
{
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
http3 on;
server_name www.jintanfuture.com jintanfuture.com;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/www.jintanfuture.com;
#CERT-APPLY-CHECK--START
# 用于 SSL 证书申请时的文件验证相关配置,请勿删除
include /www/server/panel/vhost/nginx/well-known/www.jintanfuture.com.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/www.jintanfuture.com/*.conf;
#SSL-START
#error_page 404/404.html;
ssl_certificate /www/server/panel/vhost/cert/www.jintanfuture.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/www.jintanfuture.com/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443"; h3-25=":443"; h3-T050=":443"; h3-Q050=":443"; h3-Q049=":443"; h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
quic_retry on;
quic_gso on;
ssl_early_data on;
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START
error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START
include enable-php-82.conf;
#PHP-INFO-END
#REWRITE-START
include /www/server/panel/vhost/rewrite/www.jintanfuture.com.conf;
#REWRITE-END
# ============================================================
# IPESAgent 前端系统反向代理
# 精确地址缺少尾斜杠时,统一跳转到带尾斜杠的地址
# ============================================================
location = /IPESAgent {
return 301 https://www.jintanfuture.com/IPESAgent/;
}
# 必须使用 ^~,防止 JS/CSS/图片请求被后面的正则 location 抢走
location ^~ /IPESAgent/ {
proxy_pass http://119.45.157.27/IPESAgent/;
proxy_http_version 1.1;
# 让源服务器正确命中 119.45.157.27 对应的虚拟主机
proxy_set_header Host $proxy_host;
# 保留真实访问域名、协议和客户端 IP
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# 将源服务器返回的 HTTP/IP 跳转改写成需求方 HTTPS 域名
proxy_redirect http://119.45.157.27/ https://www.jintanfuture.com/;
# 如果源服务器 Cookie 显式绑定了 IP,则改写为需求方域名
proxy_cookie_domain 119.45.157.27 www.jintanfuture.com;
}
# ============================================================
# IPESAgent 后端接口反向代理
# ============================================================
location = /IPESAgent-api {
return 301 https://www.jintanfuture.com/IPESAgent-api/;
}
location ^~ /IPESAgent-api/ {
proxy_pass http://119.45.157.27/IPESAgent-api/;
proxy_http_version 1.1;
proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 支持文件上传
client_max_body_size 200m;
# 支持智能体流式接口/SSE
proxy_buffering off;
proxy_cache off;
proxy_connect_timeout 60s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
proxy_redirect http://119.45.157.27/ https://www.jintanfuture.com/;
proxy_cookie_domain 119.45.157.27 www.jintanfuture.com;
}
# 禁止访问敏感文件
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$
{
return 404;
}
# 禁止访问敏感目录
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
return 404;
}
# SSL 证书验证目录
location ~ \.well-known {
allow all;
}
# 禁止在证书验证目录放入敏感文件
if ($uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$") {
return 403;
}
# 官网自身图片缓存规则
# 因为 /IPESAgent/ 使用了 ^~,不会再被此规则抢走
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
# 官网自身 JS/CSS 缓存规则
# 因为 /IPESAgent/ 使用了 ^~,不会再被此规则抢走
location ~ .*\.(js|css)?$ {
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/www.jintanfuture.com.log;
error_log /www/wwwlogs/www.jintanfuture.com.error.log;
}
4.6.2 为什么 proxy_pass 不带路径
当前需求是把 URI 原样转发:
text
/IPESAgent/assets/app.js
↓
http://119.45.157.27/IPESAgent/assets/app.js
所以使用:
nginx
location ^~ /IPESAgent/ {
proxy_pass http://119.45.157.27;
}
最直观,不容易误删前缀。
下面这种写法在 location 与上游 URI 完全相同时通常也能得到相同结果:
nginx
location ^~ /IPESAgent/ {
proxy_pass http://119.45.157.27/IPESAgent/;
}
但不要误写成:
nginx
location ^~ /IPESAgent/ {
proxy_pass http://119.45.157.27/;
}
最后一种配置会把匹配到的 /IPESAgent/ 替换成 /,请求路径可能变成:
text
http://119.45.157.27/assets/app.js
如果源站资源实际位于 /IPESAgent/assets/,页面就会加载失败。
五、Loading、502 与尾斜杠排障
5.1 Loading 页面为何一直不消失
如图5所示 ,故障前后只差一个 ^~,请求归属却完全不同。这个问题容易误判成 Vue 权限守卫、permission.js 白名单或登录跳转失败;实际上,入口脚本根本没有被正确加载,权限代码还没有执行机会。

图5 JS/CSS 被正则 location 截获导致 Loading 的根因
原官网配置中虽然已经存在:
nginx
location /IPESAgent/ {
proxy_pass http://119.45.157.27/IPESAgent/;
}
但官网还存在类似的正则规则:
nginx
location ~ .*\.(js|css)?$ {
expires 12h;
}
当浏览器请求:
text
/IPESAgent/assets/index-xxxx.js
普通前缀 location /IPESAgent/ 会先被记住,随后 Nginx 仍然继续检查正则 location。由于 URL 以 .js 结尾,最终可能由官网本地的 JS/CSS 规则处理,而不是反向代理到阿里云。
结果就是:
index.html能返回,所以浏览器看到了 Loading 页面;- 真正启动 Vue 应用的 JS、CSS 返回 404、错误内容或旧缓存;
- JavaScript 没有执行,Loading 自然不会消失。
解决办法是使用:
nginx
location ^~ /IPESAgent/ {
proxy_pass http://119.45.157.27;
}
^~ 的含义是:当前前缀一旦命中,就不再继续匹配后面的正则 location。这样 /IPESAgent/ 下的 HTML、JS、CSS、图片和字体都会稳定地走到阿里云源站。
除此之外,还要同时排查以下问题:
- Vue 的
base是否还是/; - 前端是否把接口地址写死成
http://119.45.157.27; /IPESAgent-api/是否遗漏代理;Host $host是否导致源站命中错误的虚拟主机;- 源站是否返回了指向 HTTP IP 的绝对重定向;
- 浏览器是否缓存了旧
index.html; - 接口是否使用 SSE,但代理缓冲没有关闭。
5.2 Host $host 怎样与源站配合
需求方 Nginx 中的 $host 是:
text
www.jintanfuture.com
如果需求方这样配置:
nginx
proxy_set_header Host $host;
阿里云 Nginx 收到的 Host 也是 www.jintanfuture.com。推荐让源站明确接受这个主机名:
nginx
server_name 119.45.157.27 www.jintanfuture.com;
这样既能让源站命中正确的虚拟主机,也有利于应用按照用户真实访问的域名生成绝对地址。
如果暂时不能修改源站的 server_name,需求方代理可以退而使用:
nginx
proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-Host $host;
此时 $proxy_host 为 119.45.157.27,可以命中源站 IP 虚拟主机;X-Forwarded-Host 仍把用户真实访问域名交给后端。采用该方式后,需要重点检查绝对重定向和 Cookie Domain,避免响应再次暴露 HTTP IP 地址。
5.3 尾斜杠为什么有时必须写
先区分本节的两个项目:IPESAgent 是今檀项目 ,IPAgent 是知产项目。下文用今檀项目说明一次真实的跳转故障,并用知产项目的测试结果进行对照。
今檀项目部署在 /IPESAgent/ 子路径下,建议统一使用带尾斜杠的规范地址:
text
https://www.jintanfuture.com/IPESAgent/
/IPESAgent 与 /IPESAgent/ 是两个不同的请求路径 。如果页面使用 ./assets/app.js 之类的相对资源地址 ,浏览器会根据当前 URL 是否以 / 结尾,计算出不同的资源路径。因此,需求方服务器为不带斜杠的入口配置了明确跳转(配置重点):
nginx
location = /IPESAgent {
return 301 https://www.jintanfuture.com/IPESAgent/$is_args$args;
}
其中,location = /IPESAgent 只精确匹配不带尾斜杠的地址;301 表示永久重定向;$is_args$args 用于保留原 URL 的查询参数。跳转后的 /IPESAgent/ 再由原有的项目代理规则处理。
5.3.1 一次实际排查:为什么补了斜杠仍出现 502?
本地 24 服务器也部署了今檀项目。用户通过公网 HTTPS 访问,但该服务器的 Nginx 实际监听内部 HTTP 8082。此前使用的配置是:
nginx
location = /IPESAgent {
return 301 /IPESAgent/;
}
为了查看浏览器实际收到的跳转地址,在 Windows 命令提示符中执行:
powershell
curl.exe -k -sS -D - -o NUL "https://itsgpt.njust.edu.cn/IPESAgent"
结果虽然返回 301 Moved Permanently ,但关键的 Location 响应头是:
text
Location: http://itsgpt.njust.edu.cn:8082/IPESAgent/
**问题不在于 Nginx 没有补斜杠,而在于它把浏览器跳转到了内部 HTTP 8082 地址。**浏览器继续访问这个错误地址后,进入了校园网的 502 错误页。
为了对照,再检查知产项目 不带斜杠时的响应,并通过 -L 验证跳转后的页面:
powershell
# 知产项目:查看第一次响应的 Location
curl.exe -k -sS -D - -o NUL "https://itsgpt.njust.edu.cn/IPAgent"
# 知产项目:-L 表示跟随跳转,查看最终响应
curl.exe -k -L -sS -D - -o NUL "https://itsgpt.njust.edu.cn/IPAgent"
知产项目返回的跳转地址是正确的公网 HTTPS 地址:
text
Location: https://itsgpt.njust.edu.cn/IPAgent/
跟随跳转后,完整命令输出最终得到 200 OK。

图中三个命令的排查结果。上方是今檀项目 IPESAgent ,其 Location 错误地指向内部 HTTP :8082;中间是知产项目 IPAgent ,其 Location 正确指向公网 HTTPS 地址;下方使用 -L 跟随知产项目的跳转。截图只显示了下方命令的前一段响应,完整输出的最终结果为 200 OK。
因此,本地 24 服务器上的今檀项目应明确写出它自己的公网 HTTPS 跳转地址:
nginx
location = /IPESAgent {
return 301 https://itsgpt.njust.edu.cn/IPESAgent/$is_args$args;
}
5.3.2 服务端已经修好,普通浏览器仍打不开怎么办?
本次修改后,知产项目的 Location 已正确 ,curl -L 最终返回 200 OK ,无痕模式也能访问,但 Chrome 普通模式仍一度失败。基本可以确定普通模式保留了此前错误的 301 跳转缓存;无痕窗口使用独立的浏览数据。
可以在 Chrome 普通模式中按以下步骤处理:
- 按
Ctrl + Shift + Delete,时间范围选 "所有时间"。 - 勾选 **"缓存的图片和文件"**并清除。可以先不勾选 Cookie,避免退出其他网站的登录。
- 重新打开标签页,输入完整地址
https://itsgpt.njust.edu.cn/IPAgent,不要选择地址栏中可能保留的旧地址。具体入口可参考 Chrome 清除浏览数据说明。
如果仍有问题,按 F12 → Network(网络)→ 勾选 Disable cache(停用缓存) ,保持开发者工具打开后重新访问,检查第一条请求的 Location 。该选项只在开发者工具打开期间生效,参见 Chrome DevTools 网络面板说明。
排查时不能只看是否返回 301,还要核对 Location 中的协议、域名、端口和项目路径。以后调试其他项目的跳转规则,可以 先用 302 临时跳转验证,确认正确后再改为 301 永久跳转,减少浏览器保存错误跳转地址的可能。
六、TLS 终止、iframe 与上线验证
6.1 能否不配置证书直接访问多个系统
6.1.1 内层 Nginx 可以不配证书,最外层不能没有
如图6所示,证书不一定出现在业务 Nginx 配置里。只要更外层的 IIS、负载均衡、CDN 或网关已经完成 TLS 终止,内层就可以接收转发后的 HTTP;但用户访问的 HTTPS 入口必然需要一个可信证书。

图6 IIS 完成 TLS 终止后由 Nginx 按路径分发
之前本地图书馆 IP 后缀为 24 的服务器采用 IIS + Nginx 双层架构:
text
浏览器
→ https://itsgpt.njust.edu.cn
→ IIS 监听 443,安装 itsgpt.njust.edu.cn 证书
→ IIS 把解密后的 HTTP 转给本机 Nginx
→ Nginx 按 /IPAgent/、/IPWeave/ 分发
所以在那份 Nginx 配置中看不到:
nginx
ssl_certificate ...;
ssl_certificate_key ...;
并不代表整个系统没有证书,而是证书安装在 IIS 上。IIS 完成 SSL Termination(TLS 终止) 后,Nginx 接收到的已经是本机 HTTP 请求。
同一个域名下的以下路径都由同一张域名证书保护:
text
https://itsgpt.njust.edu.cn/
https://itsgpt.njust.edu.cn/IPAgent/
https://itsgpt.njust.edu.cn/IPWeave/
证书只关心 itsgpt.njust.edu.cn,不关心后面的路径。
6.1.2 删除 IIS 证书会发生什么
如果仍然要求用户通过 https://itsgpt.njust.edu.cn 访问,就不能删掉最外层证书。删除后,443 无法提供可信的 HTTPS 服务,浏览器会连接失败或提示证书错误。
如果改成纯 HTTP,技术上可以不使用证书,但会失去加密、身份认证和完整性保护,也会重新遇到 iframe 混合内容、Secure Cookie 和安全上下文限制,因此不适合生产登录系统。
6.1.3 本文双服务器方案中的证书位置
当前方案中的证书位于需求方官网 Nginx:
text
浏览器 HTTPS
→ www.jintanfuture.com 的 Nginx 和证书
→ HTTP 反代到 119.45.157.27
因此,阿里云 Nginx 可以暂时只监听 HTTP,但这只保证"浏览器到需求方服务器"这一段加密。
6.2 当前方案是否属于端到端加密
需要如实区分两段链路:
text
浏览器 ──HTTPS──> 需求方服务器 ──HTTP──> 阿里云服务器
第一段有 TLS,第二段如果经过公网 HTTP,仍然是明文传输。因此:
- 对浏览器而言,地址安全、不会出现混合内容;
- 对服务器间链路而言,还不是严格意义上的端到端加密。
生产环境可按安全要求选择以下方案:
- 推荐: 为阿里云源站再准备源站域名和 HTTPS 证书,官网使用
proxy_pass https://源站域名; - 两台服务器通过 VPN、专线、云企业网或 WireGuard 连接,代理走私网地址;
- 至少在阿里云安全组中仅允许需求方服务器公网 IP 访问源站 80 端口;
- 使用双向 TLS,对两端服务器都做身份验证;
- 对登录 Token、敏感数据和管理接口进行额外审计。
如果源站升级为 HTTPS,可参考:
nginx
location ^~ /IPESAgent/ {
proxy_pass https://origin.example.com;
proxy_ssl_server_name on;
proxy_ssl_name origin.example.com;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ca-bundle.crt;
}
不要为了"看起来是 HTTPS"而关闭证书验证,否则服务器间链路仍可能受到中间人攻击。
6.3 iframe 嵌入时还需检查什么
配置完成后,需求方官网可以使用:
html
<iframe
src="/IPESAgent/"
style="width: 100%; height: 100vh; border: 0"
></iframe>
它与官网同源,因此通常具有以下优势:
- 不属于 HTTPS 页面嵌套 HTTP;
- 不需要浏览器直接跨域访问阿里云 IP;
X-Frame-Options: SAMEORIGIN仍允许同源 iframe;- Cookie 不会因为第三方 iframe 策略被直接限制;
- 接口可以使用同源相对路径。
但仍需检查:
- 响应头是否设置了
X-Frame-Options: DENY; - CSP 是否包含不允许当前页面的
frame-ancestors; - 登录后是否跳转到了写死的 IP、HTTP 地址或其他域名;
- Cookie 的
Domain、Path、Secure是否正确; - 页面是否使用
window.top强制跳出 iframe; - 空闲超时重新登录是否跳到了错误的 SSO 地址。
如果 iframe 标签配置了 sandbox,还要按实际功能授予最小必要权限,例如:
html
<iframe
src="/IPESAgent/"
sandbox="allow-same-origin allow-scripts allow-forms allow-downloads allow-popups"
></iframe>
缺少 allow-scripts、allow-forms 或 allow-same-origin 时,即使 Nginx 完全正确,也可能出现无法登录、脚本不执行或存储不可用。
如果未来改成跨域 iframe,应优先使用 CSP 精确授权:
http
Content-Security-Policy: frame-ancestors https://www.jintanfuture.com;
不要使用允许任意来源嵌入的宽松配置。
6.4 修改配置后的上线顺序
6.4.1 检查语法
两台服务器分别执行:
bash
nginx -t
看到以下信息才说明语法通过:
text
syntax is ok
test is successful
6.4.2 平滑重载
bash
nginx -s reload
或:
bash
systemctl reload nginx
不要在没有验证配置的情况下直接停止生产 Nginx。
6.4.3 分层验证
第一层,验证阿里云源站:
bash
curl -I http://119.45.157.27/IPESAgent/
第二层,验证官网代理:
bash
curl -I https://www.jintanfuture.com/IPESAgent/
第三层,验证无尾斜杠跳转:
bash
curl -I https://www.jintanfuture.com/IPESAgent
预期返回 301,并带有:
text
Location: /IPESAgent/
第四层,从浏览器开发者工具检查:
index.html是否为 200;- JS/CSS 是否为 200,且响应类型正确;
- 请求地址是否全部为 HTTPS;
- 接口是否走
/IPESAgent-api/; - 是否存在 CORS、Mixed Content、CSP 或 Cookie 报错;
- 登录后最终路由是否为
/IPESAgent/home或项目实际路径。
6.4.4 查看两端日志
需求方服务器:
bash
tail -f /www/wwwlogs/www.jintanfuture.com.log
tail -f /www/wwwlogs/www.jintanfuture.com.error.log
阿里云服务器:
bash
tail -f /var/log/nginx/ipesagent_access.log
tail -f /var/log/nginx/ipesagent_error.log
如果官网日志出现请求,而阿里云日志没有,问题在官网代理或网络层;如果阿里云已收到请求但返回 404,则重点检查源站路径和 try_files。
6.5 常见状态码与排查方向
| 现象 | 常见原因 | 排查重点 |
|---|---|---|
| 301/302 循环 | 两端重复强制 HTTPS,或错误识别 X-Forwarded-Proto |
检查跳转目标和代理头 |
| 403 | 文件权限、目录权限、Nginx 安全规则 | error.log、运行用户、敏感目录规则 |
| 404 | 静态文件路径错误、前缀被剥离、SPA 未回退 | base、proxy_pass、try_files |
| 502 | 上游无法连接、Host 命中错误虚拟主机、后端未启动 | 端口、防火墙、源站日志、Host |
| 504 | 上游处理超时 | proxy_read_timeout、后端耗时 |
| 页面一直 Loading | JS/CSS 失败、资源被正则 location 截获、旧缓存 | Network 面板、^~、资源响应类型 |
| 登录成功但不跳转 | Token/Cookie 未保存、接口地址错误、路由 base 错误 | Application、Network、Console |
| SSE 一次性返回 | Nginx 缓冲了流式响应 | proxy_buffering off、X-Accel-Buffering |
| iframe 空白或被拒绝 | Mixed Content、X-Frame-Options、CSP | Console 和响应头 |
特别注意:浏览器显示的 Loading 文案只是静态 HTML 已经加载的证明,不代表 Vue 应用已经正常启动。
6.6 缓存、Cookie 与绝对重定向
6.6.1 避免缓存旧的 index.html
Vue 构建后的 JS/CSS 通常带 hash,可以长期缓存;index.html 则不应长期缓存。否则发布新版本后,浏览器可能继续读取旧 HTML,而旧 HTML 引用的资源已经被删除。
推荐策略:
text
index.html:no-cache
带 hash 的 JS/CSS:长缓存
6.6.2 Cookie 域名与路径
如果上游接口返回:
http
Set-Cookie: token=...; Domain=119.45.157.27
浏览器通过 www.jintanfuture.com 访问时不会把它当作当前域名 Cookie。更推荐后端使用不写死 Domain 的 host-only Cookie。
反向代理后,官网与 IPESAgent 共享同一个域名。还应检查双方是否使用了相同名称的 Token Cookie,例如都叫 Admin-Token。同名 Cookie 可能互相覆盖,最好为不同系统设置专用名称和合适的 Path。
确实无法修改后端时,可谨慎使用:
nginx
proxy_cookie_domain 119.45.157.27 www.jintanfuture.com;
只有确认上游真的写死了 Domain 才需要增加,不要盲目配置。
6.6.3 绝对跳转地址
如果源站响应:
http
Location: http://119.45.157.27/IPESAgent/login
浏览器会离开官网域名。优先修改应用,让它根据 X-Forwarded-Host 和 X-Forwarded-Proto 生成正确地址;临时也可使用 proxy_redirect 做定向替换。
七、注意事项
7.1 上线后必须补齐的安全边界
完成"能访问"只是第一步,生产环境还要补齐以下边界:
- 阿里云安全组只允许需求方服务器访问源站 HTTP 端口;
- 最终将需求方到阿里云的公网链路升级为 HTTPS 或私网隧道;
- 仅启用 TLS 1.2 和 TLS 1.3,不再启用 TLS 1.0/1.1;
- HTTPS 全面验证后再配置 HSTS,谨慎使用
includeSubDomains; - 证书和私钥文件仅允许 Nginx 主进程读取;
- 上传接口设置合理的
client_max_body_size,不要无限放大; - 登录接口增加限流、失败审计和异常告警;
- 日志中避免记录 Token、密码和敏感请求体;
- 定期检查证书到期时间和自动续期任务;
- 通过 CSP 的
frame-ancestors精确控制谁可以嵌入系统; - 不对外暴露不需要的管理端口和后端端口;
- 源站设置健康检查和监控,及时发现 502/504。
7.2 几个容易混淆的问题
7.2.1 域名路径是否意味着项目部署在官网服务器
不一定。浏览器看到的是入口地址,真实静态文件完全可以在另一台服务器。反向代理屏蔽了内部部署细节。
7.2.2 官网已有证书,阿里云是否还必须安装
如果只要求浏览器到官网入口使用 HTTPS,阿里云可以暂时不安装;如果要求两台服务器之间也加密,则阿里云源站也需要 HTTPS,或通过可信私网/VPN 通信。
7.2.3 同一个域名能否访问多个系统
可以。证书覆盖主机名,Nginx 根据路径分发:
text
/IPESAgent/ → 系统 A
/IPAgent/ → 系统 B
/IPWeave/ → 系统 C
每个系统都必须配置独立、明确的路径前缀和 SPA 回退。
7.2.4 是否需要配置 CORS
如果页面和接口都通过 https://www.jintanfuture.com 的相对路径访问,浏览器视角是同源,通常不需要额外 CORS。反向代理发生在服务器内部,不属于浏览器跨域。
7.2.5 为何直接访问 IP 正常,域名代理却异常
直接访问只经过阿里云 Nginx;域名访问多经过一层官网 Nginx。新增的一层可能改变 URI、Host、协议头、Cookie、响应缓冲和缓存规则,因此必须逐层验证。
7.2.6 为何无斜杠地址有时也能访问
因为配置增加了精确重定向,或 Nginx 对特定结尾为 / 的代理 location 自动返回 301 补斜杠。建议仍显式保留:
nginx
location = /IPESAgent {
return 301 /IPESAgent/;
}
7.3 配置变更中的高频误区
不要在没有备份的情况下直接覆盖宝塔面板生成的完整 server 块。 面板可能通过 include 管理证书验证、PHP、伪静态和安全规则。稳妥做法是在确认包含关系后,只改业务相关 location,执行 nginx -t,再平滑重载。面板后续保存配置时也要复查,避免自定义段落被覆盖。
不要用扩大权限掩盖 403。 静态目录只需让 Nginx 运行用户具备逐级读取权限;把目录改成 777 会制造额外风险。403 要结合 error_log 判断是文件权限、目录执行权限、SELinux、面板安全规则,还是请求落到了错误虚拟主机。
不要对所有响应统一长缓存。 带 hash 的 JS/CSS 可以长缓存,index.html 应保持 no-cache。如果入口 HTML 被缓存,发布后它仍可能引用已经删除的旧文件,看起来就像"服务器明明部署成功,部分用户却一直 Loading"。
不要看到 502 就改前端。 502 表示当前代理没有从上游获得有效响应,排查端口监听、防火墙、安全组、路由、DNS、Host 和上游日志;前端代码还没有机会处理这个状态。若只有 /IPAgent 报 502、/IPAgent/ 正常,优先检查精确跳转与 location 命中,而不是修改 Vue Router。
不要过早开启长期 HSTS。 等 HTTPS、子域名和回滚方案全部验证后再逐步加长 max-age。带 includeSubDomains 的长期策略会影响所有子域,配置失误后浏览器仍会强制 HTTPS,回滚并不即时。
7.4 版本与协议兼容检查
本文官网示例只启用 TLS 1.2 和 TLS 1.3 。旧配置中的 TLS 1.1 已不适合新生产入口。http2 on; 的写法与 Nginx 版本有关;若运行版本不支持,应按该版本文档使用 listen 443 ssl http2;。在复制配置之前先执行 nginx -V,确认编译模块与实际版本,不要假设所有服务器语法相同。
SSE 与 WebSocket 也不是同一种协议。SSE 需要关闭代理缓冲并延长读取超时;WebSocket 需要 HTTP/1.1 以及正确的 Upgrade、Connection 头。项目没有对应功能时无需堆满配置,有对应功能时则要单独做持续连接测试。
截至 2026 年,Let's Encrypt 已公开提供短周期 IP 地址证书,但这不等于所有旧版 Certbot、面板和自动续期脚本都已兼容。常规业务系统仍优先使用域名证书。能签发 IP 证书,与该方案是否适合生产运维,是两个不同问题。
八、本文总结
8.1 本次改造得到的结果
本次改造把:
text
http://119.45.157.27/IPESAgent/
转换成了面向用户的统一安全入口:
text
https://www.jintanfuture.com/IPESAgent/
最终并没有迁移 IPESAgent,也没有给每个路径单独申请证书,而是利用需求方现有域名和证书,在官网 Nginx 中增加路径反向代理。
8.2 可以迁移到其他项目的判断方法
整个问题最值得记住的不是某一行配置,而是以下思路:
- 先画清浏览器、入口服务器、源站和后端之间的链路;
- 明确哪一层负责 TLS 终止,证书就配置在哪一层;
- 二级路径部署必须让 Vue
base、Nginxlocation和接口前缀保持一致; - 代理二级路径时必须理解
proxy_pass是否携带 URI; - 官网已有正则静态资源规则时,业务前缀使用
^~防止被截获; - 浏览器只访问 HTTPS 同源路径,避免 HTTP iframe 和接口混合内容;
- "浏览器到入口 HTTPS"不等于"入口到源站也加密";
- IIS 或 Nginx 配置里看不到证书,不代表系统没有证书,证书可能位于更外层代理;
- 排障必须逐层查看请求、响应、日志和状态码,不能只看页面文案。
通过这套方式,官网、嵌入页面和业务系统可以统一使用 HTTPS 域名,同时保留两台服务器独立部署、独立维护的能力。单服务器适合系统都由同一团队管理、文件可放在同一主机的情况;双服务器反向代理适合域名入口与业务源站分属不同团队、不能迁移部署的情况。如果服务器间传输敏感数据,应继续把公网 HTTP 源站升级成 HTTPS、VPN 或可信私网。
九、更多操作
9.1 与之前篇连起来阅读
本文承接之前篇《Server 16,Windows Server 环境下 Vue3 项目部署实战:IIS + Nginx 配置 IPAgent 二级目录完整教程》。之前篇重点是 Windows Server 上 IIS 负责 443 和证书、Nginx 负责 /IPAgent/ 与 /IPWeave/ 路径分发;本文把同一种分层思路扩展到了需求方官网服务器与阿里云服务器之间。
如果项目还要继续增加 /IPAgent/、/IPWeave/、/IPESAgent/ 等系统,建议建立统一的路径登记表,至少记录归属系统、静态资源路径、接口前缀、源站地址、超时要求、是否需要 SSE/WebSocket、Cookie 名称、负责人和上线时间。这张表比到故障时翻多个 Nginx 文件可靠得多。
更多服务器部署与排障实战,可查看我的 CSDN Server 专栏:
该专栏持续整理 Windows Server、IIS、Nginx、Vue 二级目录部署、反向代理、HTTPS 与生产环境排障。本文里的双服务器方案也可以继续扩展到负载均衡、源站 HTTPS、健康检查、灰度发布和统一鉴权。
9.2 推荐的下一步操作
- 给阿里云源站配置独立域名与 HTTPS,把公网第二段链路也加密;
- 将源站端口限制为只允许需求方服务器 IP 访问;
- 为入口 Nginx 增加证书到期、5xx 比例和上游响应时间监控;
- 用自动化脚本检查
index.html引用的每个 hash 资源是否返回 200; - 对 iframe 场景补做 Cookie、CSP
frame-ancestors和超时重新登录测试; - 为 SSE、WebSocket、文件上传和长任务接口分别设置合适的超时与缓冲策略。
9.3 官方资料
- MDN:HTTP
- MDN:HTTPS
- MDN:Transport Layer Security
- MDN:Mixed Content
- NGINX:Reverse Proxy
- NGINX:proxy_pass
- NGINX:location 匹配规则
- NGINX:Configuring HTTPS Servers
- Let's Encrypt:IP Address Certificates
- Let's Encrypt:Six-Day and IP Address Certificates Available in Certbot
文中的公网 IP、域名、目录和端口来自实际案例。复制配置时应替换成自己的环境,并在执行 nginx -s reload 或 systemctl reload nginx 前运行 nginx -t。如果本文解决了你的问题,欢迎收藏并在评论区留下自己的部署结构与报错现象;具体日志往往比一句"页面打不开"更容易定位。
