参数与命名总表

分类 参数/对象 最终值或名称 说明
记录时间 配置完成日期 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 均不用改变。

实施前提

  1. 腾讯云服务器具有公网 IPv4:<服务器公网IP>。
  2. 云服务器已安装 1Panel、Docker、OpenResty 和 frps。
  3. 本地 Ubuntu 能主动访问云服务器 TCP 7000。
  4. 本地推理服务分别监听 8000、8001。
  5. 腾讯云防火墙允许 TCP 7000、80、443。
  6. 云端 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

成功输出:

1
acme-path-ok

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:不变

注意:

  1. docker restart 只会重启原容器,不会自动切换模型。
  2. 更换模型通常需要重新创建容器或修改启动配置后重新部署。
  3. 新服务必须继续映射到宿主机 8000 或 8001。
  4. 新服务需要保持 OpenAI 兼容的 /v1 接口。
  5. 如果新服务只提供 /api/* 等非 OpenAI 路径,需要调整 routes.conf。
  6. 如果客户端 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 不通

检查:

  1. 本地 frpc 是否 active。
  2. frpc 是否 login to server success。
  3. api-d01/api-d02 是否 start proxy success。
  4. 云端 frps 是否监听 127.0.0.1:1000/1001。
  5. 两端 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

回滚原则:

  1. 先核对备份路径和内容。
  2. 恢复前再备份当前文件。
  3. 先执行 frpc verify 或 openresty -t。
  4. 验证成功后再重启 frpc 或平滑重载 OpenResty。
  5. 不使用覆盖整个系统目录的破坏性命令。

安全复盘

已落实:

  • 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 的实际配置结果。执行恢复、迁移或变更前,应先以服务器当前文件、容器状态和防火墙规则为准进行只读核查。