参数与命名总表
| 分类 |
参数/对象 |
最终值或名称 |
说明 |
| 记录时间 |
配置完成日期 |
2026-08-28 |
本文记录当日最终状态 |
| 云服务器 |
公网 IPv4 |
<服务器公网IP> |
腾讯云 Lighthouse |
| 云服务器 |
管理面板 |
1Panel |
管理 OpenResty、frps 等 |
| 云服务器 |
frps 容器 |
1Panel-frps-TXDh |
Docker,network=host |
| 云服务器 |
frps 版本 |
0.64.0 |
与本地 frpc 保持一致 |
| 云服务器 |
frps 控制端口 |
TCP 7000 |
本地 frpc 主动连接此端口 |
| 云服务器 |
frps Dashboard |
TCP 7500 |
不属于推理 API 公网入口 |
| 云服务器 |
frps 配置 |
/opt/1panel/apps/frps/frps/data/frps.toml |
挂载到容器 /etc/frp/frps.toml |
| 云服务器 |
FRP 转发监听地址 |
127.0.0.1 |
1000/1001 不直接暴露公网 |
| 本地服务器 |
操作系统 |
Ubuntu 22.04.5 LTS x86_64 |
内核曾记录为 6.8.0-138-generic |
| 本地服务器 |
frpc 路径 |
/usr/local/bin/frpc |
版本 0.64.0 |
| 本地服务器 |
frpc 配置 |
/etc/frp/frpc.toml |
root:frpc,权限 640 |
| 本地服务器 |
FRP Token 文件 |
/etc/frp/frps.token |
root:frpc,权限 640;本文不记录内容 |
| 本地服务器 |
frpc 服务 |
/etc/systemd/system/frpc.service |
enabled、active |
| 本地服务器 |
frpc 运行用户 |
frpc |
系统用户、无登录 Shell、不在 docker 组 |
| 固定槽位 D01 |
FRP 代理名 |
api-d01 |
不绑定框架、模型或量化格式 |
| 固定槽位 D01 |
本地入口 |
127.0.0.1:8000 |
更换后端时保持此端口 |
| 固定槽位 D01 |
云端内部入口 |
127.0.0.1:1000 |
仅云端本机可访问 |
| 固定槽位 D01 |
公网 Base URL |
https://<服务器公网IP>/d01/v1 |
OpenAI 兼容 API |
| 固定槽位 D01 |
API Key 文件 |
/opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/d01-api.key |
root:root,权限 600;不记录内容 |
| 固定槽位 D02 |
FRP 代理名 |
api-d02 |
不绑定框架、模型或量化格式 |
| 固定槽位 D02 |
本地入口 |
127.0.0.1:8001 |
更换后端时保持此端口 |
| 固定槽位 D02 |
云端内部入口 |
127.0.0.1:1001 |
仅云端本机可访问 |
| 固定槽位 D02 |
公网 Base URL |
https://<服务器公网IP>/d02/v1 |
OpenAI 兼容 API |
| 固定槽位 D02 |
API Key 文件 |
/opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/d02-api.key |
root:root,权限 600;不记录内容 |
| 当时后端 |
D01 模型 ID |
qwen38-27b |
当时由 8000 返回,不是长期基础设施参数 |
| 当时后端 |
D02 模型 ID |
qwen38-27b-nvfp4 |
当时由 8001 返回,不是长期基础设施参数 |
| 反向代理 |
OpenResty 容器 |
1Panel-openresty-pp9O |
由 1Panel 管理 |
| 反向代理 |
1Panel 站点代号 |
AI-api |
公网 AI API 站点 |
| 反向代理 |
主配置 |
/opt/1panel/apps/openresty/openresty/conf/conf.d/AI-api.conf |
宿主机路径 |
| 反向代理 |
私有路由配置 |
/opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/routes.conf |
root:root,权限 600 |
| HTTPS |
证书主体 |
IP Address:<服务器公网IP> |
Let’s Encrypt 短期 IP 证书 |
| HTTPS |
ACME 客户端 |
/usr/local/bin/lego |
lego 5.3.1 linux/amd64 |
| HTTPS |
正式证书目录 |
/opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod |
含账户、证书和私钥 |
| HTTPS |
证书文件 |
lego-prod/certificates/<服务器公网IP>.crt |
OpenResty 使用 |
| HTTPS |
私钥文件 |
lego-prod/certificates/<服务器公网IP>.key |
不得外传 |
| HTTPS |
续期环境文件 |
/etc/vllm-ip-cert-renew.env |
root:root,权限 600 |
| HTTPS |
续期服务 |
vllm-ip-cert-renew.service |
oneshot;名称保留为实际状态 |
| HTTPS |
续期定时器 |
vllm-ip-cert-renew.timer |
每日两次检查,enabled、active |
| 腾讯云防火墙 |
保留开放 |
TCP 7000、80、443 |
FRP、ACME HTTP-01、HTTPS API |
| 腾讯云防火墙 |
已关闭 |
TCP 1000、1001(IPv4/IPv6) |
外部检测均为 False |
| 外部验证 |
TCP 443 |
True |
Windows 外部网络验证 |
| 外部验证 |
D01/D02 无 Key |
HTTP 401 |
鉴权生效 |
| 外部验证 |
D01/D02 正确 Key |
HTTP 200 |
端到端调用成功 |
安全说明:本文不记录 FRP Token、D01/D02 API Key、ACME 账户私钥或证书私钥。恢复时应从原服务器的受限文件读取,不能从博客、聊天或截图中复制秘密。
目标与最终架构
目标是把内网 Ubuntu 上的两个 OpenAI 兼容推理服务,通过腾讯云公网 IP 安全地提供给外部设备。
基础设施采用“固定端口槽位”设计。FRP、OpenResty、HTTPS 和鉴权只识别 D01/D02,不绑定具体推理框架、模型或量化格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| D01: 外部客户端 → HTTPS <服务器公网IP>:443/d01/v1/* → OpenResty 鉴权与路径重写 → 云端 127.0.0.1:1000 → frps ← TLS FRP 隧道 → frpc → 本地 127.0.0.1:8000
D02: 外部客户端 → HTTPS <服务器公网IP>:443/d02/v1/* → OpenResty 鉴权与路径重写 → 云端 127.0.0.1:1001 → frps ← TLS FRP 隧道 → frpc → 本地 127.0.0.1:8001
|
只要后续服务继续监听本地 8000 或 8001,并保持 OpenAI 兼容接口,上游 FRP、云端端口、公网 URL、证书和 API Key 均不用改变。
实施前提
- 腾讯云服务器具有公网 IPv4:<服务器公网IP>。
- 云服务器已安装 1Panel、Docker、OpenResty 和 frps。
- 本地 Ubuntu 能主动访问云服务器 TCP 7000。
- 本地推理服务分别监听 8000、8001。
- 腾讯云防火墙允许 TCP 7000、80、443。
- 云端 1000、1001 只允许 frps 监听在 127.0.0.1。
确认本地推理服务
实施时存在两个 Docker 服务:
| 槽位 |
当时容器 |
本地端口 |
当时模型 ID |
GPU |
| D01 |
vllm-qwen3.8-27b-fp8 |
8000 |
qwen38-27b |
0、1 |
| D02 |
vllm-qwen3.8-27b-nvfp4 |
8001 |
qwen38-27b-nvfp4 |
2 |
本地验证:
1 2 3 4 5
| curl --max-time 15 -sS -w '\nHTTP_STATUS=%{http_code}\n' \ http://127.0.0.1:8000/v1/models
curl --max-time 15 -sS -w '\nHTTP_STATUS=%{http_code}\n' \ http://127.0.0.1:8001/v1/models
|
当时两者均返回 HTTP 200。模型名称只反映当时状态,长期约束是 D01 使用 8000、D02 使用 8001。
配置云端 frps
容器与挂载
1 2 3 4 5 6 7 8
| 容器:1Panel-frps-TXDh 网络:host
/opt/1panel/apps/frps/frps/data/frps.toml → /etc/frp/frps.toml
/opt/1panel/apps/frps/frps/data/ssl → /etc/frp/ssl
|
备份
1 2 3
| sudo cp -a --no-clobber \ /opt/1panel/apps/frps/frps/data/frps.toml \ /opt/1panel/apps/frps/frps/data/frps.toml.bak-before-vllm
|
关键配置
原有配置包含:
1 2 3 4 5 6 7
| bindAddr = "0.0.0.0" bindPort = 7000
auth.method = "token"
webServer.addr = "0.0.0.0" webServer.port = 7500
|
为两个槽位增加或确认:
1 2 3 4 5 6 7 8
| proxyBindAddr = "127.0.0.1"
allowPorts = [ { single = 1000 }, { single = 1001 } ]
transport.tls.force = true
|
设计含义:
- 7000 供内网 frpc 主动连接。
- 1000、1001 只在云端回环地址监听。
- allowPorts 限制客户端只能申请这两个远端端口。
- FRP 强制 TLS,并使用 Token 鉴权。
验证与重启
1 2 3 4 5
| sudo docker exec 1Panel-frps-TXDh \ frps verify -c /etc/frp/frps.toml
sudo docker restart 1Panel-frps-TXDh sudo docker logs --tail 20 1Panel-frps-TXDh
|
成功标志:
1 2 3
| syntax is ok frps tcp listen on 0.0.0.0:7000 frps started successfully
|
腾讯云防火墙
最终保留:
| 端口 |
用途 |
| TCP 7000 |
本地 frpc 连接云端 frps |
| TCP 80 |
Let’s Encrypt HTTP-01 |
| TCP 443 |
公网 HTTPS API |
最终删除 TCP 1000、1001 的 IPv4 和 IPv6 公网规则,因为这两个端口只供云端 OpenResty 访问。
Windows 外部检测:
1 2 3 4 5 6 7 8 9 10 11 12 13
| foreach ($port in 1000, 1001, 443) { $client = [System.Net.Sockets.TcpClient]::new() try { $ok = $client.ConnectAsync("<服务器公网IP>", $port).Wait(3000) -and $client.Connected } catch { $ok = $false } finally { $client.Dispose() } "Port $port = $ok" }
|
结果:
1 2 3
| Port 1000 = False Port 1001 = False Port 443 = True
|
本地 frpc
安装结果
1 2 3 4
| 版本:0.64.0 路径:/usr/local/bin/frpc 所有者:root:root 权限:755
|
官方归档地址:
1
| https://github.com/fatedier/frp/releases/download/v0.64.0/frp_0.64.0_linux_amd64.tar.gz
|
实际安装时通过 GitHub 代理下载,并在完整性检查通过后安装。
用户和权限
1 2 3 4 5 6 7
| 用户:frpc Shell:/usr/sbin/nologin 未加入 docker 组
/etc/frp root:frpc 750 /etc/frp/frpc.toml root:frpc 640 /etc/frp/frps.token root:frpc 640
|
Token 使用隐藏输入写入:
1
| sudo sh -c 'systemd-ask-password "请输入 frps Token:" > /etc/frp/frps.token'
|
不得在博客、聊天、截图、代码仓库或命令行参数中保存 Token。
最终 frpc.toml
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
| serverAddr = "<服务器公网IP>" serverPort = 7000 loginFailExit = false
auth.method = "token" auth.tokenSource.type = "file" auth.tokenSource.file.path = "/etc/frp/frps.token"
transport.tls.enable = true
log.to = "console" log.level = "info"
[[proxies]] name = "api-d01" type = "tcp" localIP = "127.0.0.1" localPort = 8000 remotePort = 1000
[[proxies]] name = "api-d02" type = "tcp" localIP = "127.0.0.1" localPort = 8001 remotePort = 1001
|
通用化命名记录:
1 2
| vllm-d01-fp8 → api-d01 vllm-d02-nvfp4 → api-d02
|
变更前备份:
1
| /etc/frp/frpc.toml.bak-20260828-184622
|
除两个 name 字段外,其他 frpc 参数未改变。
systemd 和日志
1 2 3 4 5 6 7 8 9
| sudo -u frpc /usr/local/bin/frpc verify \ -c /etc/frp/frpc.toml
sudo systemctl daemon-reload sudo systemctl enable --now frpc
sudo systemctl is-enabled frpc sudo systemctl is-active frpc sudo journalctl -u frpc -n 30 --no-pager
|
最终状态和日志:
1 2 3 4 5 6
| enabled active login to server success proxy added: [api-d01 api-d02] [api-d01] start proxy success [api-d02] start proxy success
|
验证 FRP 内部链路
云服务器检查:
1 2 3 4 5 6 7
| sudo ss -lntp | grep -E '127\.0\.0\.1:(1000|1001)\b'
curl --max-time 15 -sS -w '\nHTTP_STATUS=%{http_code}\n' \ http://127.0.0.1:1000/v1/models
curl --max-time 15 -sS -w '\nHTTP_STATUS=%{http_code}\n' \ http://127.0.0.1:1001/v1/models
|
预期 1000、1001 均由 frps 监听,两个请求均返回 HTTP 200。
1Panel OpenResty
容器挂载
1 2 3 4 5 6 7 8 9 10
| 容器:1Panel-openresty-pp9O
/opt/1panel/apps/openresty/openresty/conf/conf.d → /usr/local/openresty/nginx/conf/conf.d
/opt/1panel/apps/openresty/openresty/root → /usr/share/nginx/html
/opt/1panel/apps/openresty/openresty/www → /www
|
站点
在 1Panel 中创建反向代理站点:
1 2 3 4
| 主机:<服务器公网IP> 代号:AI-api 初始代理:http://127.0.0.1:1000 备注:AI推理 公网 API
|
最终由自定义 Nginx 配置接管两个槽位。
ACME HTTP-01 目录
1 2 3 4 5 6 7 8 9
| sudo install -d -m 755 \ /opt/1panel/apps/openresty/openresty/root/.well-known/acme-challenge
printf 'acme-path-ok\n' | sudo tee \ /opt/1panel/apps/openresty/openresty/root/.well-known/acme-challenge/vllm-check \ > /dev/null
curl --max-time 10 \ http://<服务器公网IP>/.well-known/acme-challenge/vllm-check
|
成功输出:
lego 与 Let’s Encrypt IP 证书
注册邮箱为:2390093616@qq.com
lego
1 2
| 版本:5.3.1 linux/amd64 路径:/usr/local/bin/lego
|
安装前下载归档和 checksums,并执行:
1 2 3 4
| sha256sum -c --ignore-missing lego_5.3.1_checksums.txt tar -xzf lego_v5.3.1_linux_amd64.tar.gz sudo install -m 755 lego /usr/local/bin/lego /usr/local/bin/lego --version
|
Staging
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| read -r -p "请输入用于 Let's Encrypt 的邮箱:" ACME_ACCOUNT_EMAIL
sudo install -d -m 700 \ /opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-staging
sudo /usr/local/bin/lego run \ --server letsencrypt-staging \ --email "$ACME_ACCOUNT_EMAIL" \ --accept-tos \ --domains "<服务器公网IP>" \ --key-type EC256 \ --http \ --http.webroot "/opt/1panel/apps/openresty/openresty/root" \ --path "/opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-staging" \ --profile shortlived \ --ipv4only
unset ACME_ACCOUNT_EMAIL
|
Staging 成功后再签发正式证书。
Production
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| read -r -p "请输入用于 Let's Encrypt 的邮箱:" ACME_ACCOUNT_EMAIL
sudo install -d -m 700 \ /opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod
sudo /usr/local/bin/lego run \ --server letsencrypt \ --email "$ACME_ACCOUNT_EMAIL" \ --accept-tos \ --domains "<服务器公网IP>" \ --key-type EC256 \ --http \ --http.webroot "/opt/1panel/apps/openresty/openresty/root" \ --path "/opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod" \ --profile shortlived \ --ipv4only
unset ACME_ACCOUNT_EMAIL
|
验证:
1 2 3
| sudo openssl x509 \ -in /opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod/certificates/<服务器公网IP>.crt \ -noout -issuer -dates -ext subjectAltName
|
当时结果:
1 2 3 4
| issuer=C = US, O = Let's Encrypt, CN = YE2 notBefore=Aug 28 08:35:22 2026 GMT notAfter=Sep 4 00:35:21 2026 GMT IP Address:<服务器公网IP>
|
IP 证书是短期证书,因此自动续期属于必需配置。
API Key 与路由
生成独立 Key
1 2 3 4 5 6 7 8
| sudo install -d -o root -g root -m 700 \ /opt/1panel/apps/openresty/openresty/www/sites/AI-api/private
sudo sh -c ' umask 077 openssl rand -hex 32 > /opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/d01-api.key openssl rand -hex 32 > /opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/d02-api.key '
|
文件最终为 root:root、权限 600、大小 65 字节。
routes.conf
实际文件:
1 2 3
| /opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/routes.conf root:root mode=600
|
以下为脱敏后的功能结构,真实 Key 不应写入博客:
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
| location ^~ /d01/v1/ { if ($http_authorization != "Bearer <D01_API_KEY>") { return 401; }
rewrite ^/d01(/.*)$ $1 break; proxy_pass http://127.0.0.1:1000; 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_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }
location ^~ /d02/v1/ { if ($http_authorization != "Bearer <D02_API_KEY>") { return 401; }
rewrite ^/d02(/.*)$ $1 break; proxy_pass http://127.0.0.1:1001; 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_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }
|
路径重写:
1 2
| /d01/v1/models → 127.0.0.1:1000/v1/models /d02/v1/models → 127.0.0.1:1001/v1/models
|
AI-api.conf
主配置:
1
| /opt/1panel/apps/openresty/openresty/conf/conf.d/AI-api.conf
|
最终内容:
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
| server { listen 80; server_name <服务器公网IP>;
access_log /www/sites/AI-api/log/access.log main; error_log /www/sites/AI-api/log/error.log;
location ^~ /.well-known/acme-challenge { allow all; root /usr/share/nginx/html; }
location / { return 308 https://<服务器公网IP>$request_uri; } }
server { listen 443 ssl default_server; server_name <服务器公网IP>;
ssl_certificate /www/sites/AI-api/lego-prod/certificates/<服务器公网IP>.crt; ssl_certificate_key /www/sites/AI-api/lego-prod/certificates/<服务器公网IP>.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_session_timeout 10m; ssl_session_tickets off;
access_log /www/sites/AI-api/log/access.log main; error_log /www/sites/AI-api/log/error.log;
include /www/sites/AI-api/private/routes.conf;
location / { return 404; } }
|
备份:
1
| /opt/1panel/apps/openresty/openresty/conf/conf.d/AI-api.conf.bak-before-https
|
每次修改后:
1 2
| sudo docker exec 1Panel-openresty-pp9O openresty -t sudo docker exec 1Panel-openresty-pp9O openresty -s reload
|
必须先看到 syntax is ok 和 test is successful,才能重载。
两次关键故障及修复
server name 冲突
曾出现:
1
| conflicting server name "<服务器公网IP>" on 0.0.0.0:80, ignored
|
旧 Wiki 配置包含:
1
| server_name wiki2.antl.net <服务器公网IP>;
|
实际 Wiki 使用 https://wiki.antl.net/zh/home,域名设置中也没有该 IP,因此备份后仅删除多余 IP:
1
| server_name wiki2.antl.net;
|
备份:
1
| /opt/1panel/apps/openresty/openresty/conf/conf.d/wiki2.antl.net.conf.bak-before-vllm-ip
|
修改后 openresty -t 不再出现冲突警告。
TLS unrecognized name
直接访问 IP 时曾出现:
1
| OpenSSL: tlsv1 unrecognized name
|
原因是 1Panel 默认 HTTPS 站点配置了 ssl_reject_handshake on,直接访问 IP 时请求落入默认拒绝站点。
解决办法:
1
| listen 443 ssl default_server;
|
修复后:
1 2 3
| HTTP_STATUS=404 REMOTE_IP=<服务器公网IP> SSL_VERIFY=0
|
404 是预期安全行为,SSL_VERIFY=0 表示证书验证成功。
证书自动续期
lego v5 使用 run 同时处理首次申请与续期,并动态计算续期时间。
环境文件
1 2 3
| /etc/vllm-ip-cert-renew.env owner=root:root mode=600
|
内容形式:
1
| LEGO_EMAIL=<ACME_ACCOUNT_EMAIL>
|
服务
1 2 3 4 5 6 7 8 9 10 11 12 13
| [Unit] Description=Renew Let's Encrypt short-lived IP certificate for vLLM API Wants=network-online.target After=network-online.target docker.service
[Service] Type=oneshot EnvironmentFile=/etc/vllm-ip-cert-renew.env UMask=0077 TimeoutStartSec=5min ExecStart=/usr/local/bin/lego run --server letsencrypt --email ${LEGO_EMAIL} --accept-tos --domains <服务器公网IP> --key-type EC256 --http --http.webroot /opt/1panel/apps/openresty/openresty/root --path /opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod --profile shortlived --ipv4only ExecStartPost=/usr/bin/docker exec 1Panel-openresty-pp9O openresty -t ExecStartPost=/usr/bin/docker exec 1Panel-openresty-pp9O openresty -s reload
|
文件:
1
| /etc/systemd/system/vllm-ip-cert-renew.service
|
定时器
1 2 3 4 5 6 7 8 9 10 11 12
| [Unit] Description=Check vLLM IP certificate renewal twice daily
[Timer] OnCalendar=*-*-* 03:20:00 OnCalendar=*-*-* 15:20:00 RandomizedDelaySec=30m Persistent=true Unit=vllm-ip-cert-renew.service
[Install] WantedBy=timers.target
|
文件:
1
| /etc/systemd/system/vllm-ip-cert-renew.timer
|
启用与检查:
1 2 3 4 5
| sudo systemctl daemon-reload sudo systemctl enable --now vllm-ip-cert-renew.timer sudo systemctl is-enabled vllm-ip-cert-renew.timer sudo systemctl is-active vllm-ip-cert-renew.timer sudo systemctl list-timers vllm-ip-cert-renew.timer --no-pager
|
最终为 enabled、active。手工测试时,lego 正确判断尚未到续期时间并跳过签发,随后完成 OpenResty 检查与平滑重载。
端到端验证
未授权
1 2 3 4 5 6 7
| curl --max-time 15 -sS -o /dev/null \ -w 'HTTP_STATUS=%{http_code}\n' \ https://<服务器公网IP>/d01/v1/models
curl --max-time 15 -sS -o /dev/null \ -w 'HTTP_STATUS=%{http_code}\n' \ https://<服务器公网IP>/d02/v1/models
|
两者均返回 HTTP 401。
云端授权验证
D01 示例,不输出 Key:
1 2 3 4 5 6 7 8 9 10
| D01_API_KEY="$(sudo cat \ /opt/1panel/apps/openresty/openresty/www/sites/AI-api/private/d01-api.key \ | tr -d '\r\n')"
curl --max-time 15 -sS \ -H "Authorization: Bearer ${D01_API_KEY}" \ -w '\nHTTP_STATUS=%{http_code}\n' \ https://<服务器公网IP>/d01/v1/models
unset D01_API_KEY
|
D02 使用 d02-api.key 和 /d02/v1/models。两者均返回 HTTP 200。
Windows 外部授权验证
1 2 3 4 5 6 7 8 9 10 11 12 13
| $SecureKey = Read-Host "粘贴 D01 API Key" -AsSecureString $Pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureKey)
try { $D01Key = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Pointer) $Headers = @{ Authorization = "Bearer $D01Key" } $Response = Invoke-RestMethod -Uri "https://<服务器公网IP>/d01/v1/models" -Headers $Headers -Method Get $Response.data | Select-Object id, max_model_len } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Pointer) Remove-Variable D01Key, SecureKey, Pointer, Headers, Response -ErrorAction SilentlyContinue }
|
实施时外部 Windows 成功获取:
1 2
| D01:qwen38-27b,max_model_len=262144 D02:qwen38-27b-nvfp4,max_model_len=4096
|
客户端参数
D01
1 2 3
| Base URL: https://<服务器公网IP>/d01/v1 API Key: d01-api.key 的内容 Model: 以 /d01/v1/models 当前返回值为准
|
D02
1 2 3
| Base URL: https://<服务器公网IP>/d02/v1 API Key: d02-api.key 的内容 Model: 以 /d02/v1/models 当前返回值为准
|
FRP Token 只用于 frpc 与 frps 之间登录,不能当作公网 API Key。
以后更换模型或后端
更换 D01 时保持:
1 2 3 4 5
| 本地监听:8000 FRP 名称:api-d01 云端内部端口:1000 公网路径:/d01/v1 D01 API Key:不变
|
更换 D02 时保持:
1 2 3 4 5
| 本地监听:8001 FRP 名称:api-d02 云端内部端口:1001 公网路径:/d02/v1 D02 API Key:不变
|
注意:
- docker restart 只会重启原容器,不会自动切换模型。
- 更换模型通常需要重新创建容器或修改启动配置后重新部署。
- 新服务必须继续映射到宿主机 8000 或 8001。
- 新服务需要保持 OpenAI 兼容的 /v1 接口。
- 如果新服务只提供 /api/* 等非 OpenAI 路径,需要调整 routes.conf。
- 如果客户端 model 参数也要保持不变,应让后端提供固定模型别名;否则以 /v1/models 当前返回值为准。
日常巡检
本地
1 2 3 4 5 6
| sudo systemctl is-enabled frpc sudo systemctl is-active frpc sudo journalctl -u frpc -n 30 --no-pager
curl --max-time 15 -sS http://127.0.0.1:8000/v1/models curl --max-time 15 -sS http://127.0.0.1:8001/v1/models
|
云端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
sudo ss -lntp | grep -E \ '(:7000\b|127\.0\.0\.1:(1000|1001)\b|:80\b|:443\b)'
sudo docker logs --tail 30 1Panel-frps-TXDh sudo docker exec 1Panel-openresty-pp9O openresty -t
sudo systemctl is-active vllm-ip-cert-renew.timer sudo systemctl list-timers vllm-ip-cert-renew.timer --no-pager
sudo openssl x509 \ -in /opt/1panel/apps/openresty/openresty/www/sites/AI-api/lego-prod/certificates/<服务器公网IP>.crt \ -noout -dates -issuer -ext subjectAltName
|
常见故障
云端 1000/1001 不通
检查:
- 本地 frpc 是否 active。
- frpc 是否 login to server success。
- api-d01/api-d02 是否 start proxy success。
- 云端 frps 是否监听 127.0.0.1:1000/1001。
- 两端 FRP 版本、Token 和 TLS 是否一致。
公网返回 502/504
检查 OpenResty 到 1000/1001、FRP 隧道、本地 8000/8001 和推理进程状态。
公网返回 401
检查 Bearer 格式、Key 是否对应正确槽位、是否带多余换行,以及 Key 是否已轮换。
HTTPS 证书错误
检查证书 SAN、有效期、续期 timer、lego 日志、TCP 80 和 ACME challenge 路径。
unrecognized name
确认:
1
| listen 443 ssl default_server;
|
server name conflict
1 2 3 4
| sudo docker exec 1Panel-openresty-pp9O sh -c ' openresty -T 2>&1 | grep -n -B 4 -A 4 "<服务器公网IP>" '
|
确认其他 80/443 server 块没有把该 IP 加入 server_name。
备份与回滚记录
1 2 3 4 5 6 7 8 9 10 11
| 云端 frps: /opt/1panel/apps/frps/frps/data/frps.toml.bak-before-vllm
云端 AI API: /opt/1panel/apps/openresty/openresty/conf/conf.d/AI-api.conf.bak-before-https
云端旧 Wiki 配置: /opt/1panel/apps/openresty/openresty/conf/conf.d/wiki2.antl.net.conf.bak-before-vllm-ip
本地 frpc 通用命名前: /etc/frp/frpc.toml.bak-20260828-184622
|
回滚原则:
- 先核对备份路径和内容。
- 恢复前再备份当前文件。
- 先执行 frpc verify 或 openresty -t。
- 验证成功后再重启 frpc 或平滑重载 OpenResty。
- 不使用覆盖整个系统目录的破坏性命令。
安全复盘
已落实:
- FRP 使用 Token 并强制 TLS。
- Token 从权限 640 的独立文件读取。
- frpc 使用无登录权限的专用用户。
- 云端 1000/1001 只监听 127.0.0.1。
- 腾讯云已删除 1000/1001 公网规则。
- 公网只通过可信 HTTPS 访问。
- D01、D02 使用不同随机 API Key。
- API Key 文件权限为 600。
- 根路径返回 404。
- 未授权接口返回 401。
- 证书由 systemd 自动检查续期。
- 每次续期后先检查 OpenResty 再平滑重载。
后续建议:
- 将 API Key 保存到密码管理器,不放入脚本和代码仓库。
- 定期审计腾讯云其他公网端口。
- 如果本地出口 IP 固定,可将 TCP 7000 来源限制为该 IP。
- 安全备份 lego-prod/accounts 与 certificates。
- 通过 1Panel 修改站点后重新检查 AI-api.conf,防止覆盖手工配置。
- 定期查看续期日志并做外部 HTTPS 调用测试。
参考资料
本文记录 2026-08-28 的实际配置结果。执行恢复、迁移或变更前,应先以服务器当前文件、容器状态和防火墙规则为准进行只读核查。