文章目录
- [02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上](#02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上)
-
- [1. 目标架构](#1. 目标架构)
- [2. API Key 鉴权(llama-server 侧)](#2. API Key 鉴权(llama-server 侧))
- [3. 反向隧道(autossh + 最小权限密钥)](#3. 反向隧道(autossh + 最小权限密钥))
- [4. nginx 反向代理(与既有站点共存)](#4. nginx 反向代理(与既有站点共存))
- [5. 暴露面收敛(重要)](#5. 暴露面收敛(重要))
- [6. 验收清单](#6. 验收清单)
02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上
《DeepSeek-V4-Flash 本地部署与公网服务化》系列 · 第 ② 篇 / 共 5 篇
① 硬件选型与 llama.cpp 部署 | ② API 鉴权与腾讯云公网桥接(本篇) | ③ 多协议支持:Anthropic 与 Responses API | ④ 稳定性与性能调优实录 | ⑤ 客户端接入:pi 与各类 SDK
本篇解决:llama-server 起在局域网工作站上(192.168.x.x:8081),如何让外网通过https://api.example.com/v1(或 http)安全地访问,且不被白嫖算力。
1. 目标架构
客户端(本机/任意网络)
→ https://api.example.com(腾讯云 nginx, 443/80)
→ 127.0.0.1:8081(跳板机本地回环)
→ SSH 反向隧道(local-server 主动发起)
→ local-server:8081 llama-server(API Key 鉴权)
模型机在 NAT 后,跳板机无法直连,所以用反向隧道:local-server 主动 ssh 到腾讯云,把远端的 8081 端口映射回自己的 8081。整条链路只有腾讯云安全组放行的 80/443 暴露公网。
2. API Key 鉴权(llama-server 侧)
llama-server 原生支持 --api-key-file(比 --api-key 好------key 不会出现在 ps 进程列表里):
bash
umask 077
openssl rand -hex 32 > ~/deepseek-v4-flash/.api-key # 64 位十六进制
chmod 600 ~/deepseek-v4-flash/.api-key
serve.sh 里做成自动检测(删文件并重启即回到无鉴权):
bash
KEY_ARGS=()
if [ -f "$HOME/deepseek-v4-flash/.api-key" ]; then
KEY_ARGS=(--api-key-file "$HOME/deepseek-v4-flash/.api-key")
fi
exec ... "$LLAMA" "${KEY_ARGS[@]}" ...
实测鉴权行为(这个行为边界要清楚):
| 请求 | 无 key | 带 key |
|---|---|---|
POST /v1/chat/completions |
401 | 200 |
GET /props |
401 | 200 |
GET /health |
200 | 200(公开,设计如此) |
GET /v1/models |
200 | 200(公开,只泄露模型名) |
3. 反向隧道(autossh + 最小权限密钥)
local-server 上生成专用密钥(空口令,仅供隧道):
bash
ssh-keygen -t ed25519 -f ~/.ssh/jump_tunnel_ed25519 -N "" -C ds-tunnel-to-jump
跳板机 ~/.ssh/authorized_keys 里给这把公钥上镣铐------即使密钥泄露也只能用于这两条端口转发,拿不到 shell、改不了别的端口:
restrict,port-forwarding,permitlisten="127.0.0.1:8081",permitlisten="127.0.0.1:8090" ssh-ed25519 AAAA... ds-tunnel-to-jump
(8090 是 03 篇的 LiteLLM shim,先跑 8081 一条也行。)
local-server 上用 autossh 保持隧道,跑在 tmux 里(与模型服务同款运维方式):
bash
tmux new-session -d -s ds-tunnel 'autossh -M 0 -N \
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new \
-i ~/.ssh/jump_tunnel_ed25519 \
-R 127.0.0.1:8081:127.0.0.1:8081 \
-R 127.0.0.1:8090:127.0.0.1:8090 \
ubuntu@<跳板机>'
运维:tmux attach -t ds-tunnel 查看;断线 autossh 自动重连。注意一个坑 :tmux kill-session 杀不掉 autossh 进程本身(忽略 SIGHUP),残留进程会占着远端端口导致新隧道 ExitOnForwardFailure 退出------重建前先 pkill -f 'autossh.*<跳板机IP>'(模式里 IP 用 106[.]54 这类写法,避免 pkill 匹配到自身命令行误杀会话------亲身踩过)。
4. nginx 反向代理(与既有站点共存)
跳板机 80/443 原本跑着一个静态站点(SPA)。要求:API 走隧道,站点不受影响;http 的 API 路径不做 301 跳转(很多 SDK 不跟随 POST 重定向,用户明确要 http base_url 可用)。
共享代理参数(/etc/nginx/snippets/ds-api-proxy.conf):
nginx
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 关闭逐跳 Connection 头, 支持 SSE
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off; # SSE 流式必须关缓冲
proxy_request_buffering off;
client_max_body_size 100m; # 大 prompt, 默认 1m 会 413
proxy_read_timeout 900s; # 长生成
proxy_send_timeout 900s;
站点配置里(80 和 443 两个 server block 都加):
nginx
location ^~ /v1/ { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /health { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /healthz { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /chat/completions { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /completion { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /completions { include /etc/nginx/snippets/ds-api-proxy.conf; }
# 其余 location / { try_files ... }(443)或 return 301(80)保持不变
要点:
location ^~ /v1/是前缀匹配且跳过正则,静态站点的其它路径不受影响;80 块里它排在location / { return 301 ... }之前被精确/前缀规则优先命中,所以 API 的 http 请求直达后端。- 只代理白名单路径,不整站转发------
/仍然是原来的站点。 - 改配置前备份到
sites-enabled之外(如/etc/nginx/backups/):include /etc/nginx/sites-enabled/*会把.bak文件也加载进来 ,我们踩出过一个 "conflicting server name" 警告------备份文件参与了 server_name 竞争。sudo nginx -t && sudo systemctl reload nginx。
5. 暴露面收敛(重要)
跳板机 sshd 配置了 GatewayPorts yes(历史原因),导致 -R 127.0.0.1:8081:... 实际绑在 0.0.0.0------回环绑定请求被全局配置覆盖了。两层兜底:
-
腾讯云安全组 :本来就不放行 8081/8090,公网不可达(实测从外网
curl <跳板机IP>:8081超时)。 -
iptables 主机防火墙 (防御纵深,防 VPC 内横向 + 防安全组误开):
bashsudo iptables -I INPUT 1 -p tcp --dport 8081 ! -i lo -j DROP sudo iptables -I INPUT 1 -p tcp --dport 8090 ! -i lo -j DROP注意
iptables-persistent未安装,跳板机重启后需重加(可写进 systemd unit 或装好 persistent)。 -
就算前两层都失守,llama-server 的 API Key 鉴权仍是最后防线(401)。
6. 验收清单
bash
# 链路通
curl -s https://api.example.com/health # {"status":"ok"}
curl -s http://api.example.com/health # 同样直达, 不是 301
# 鉴权
curl -s -o /dev/null -w '%{http_code}' -X POST https://api.example.com/v1/chat/completions \
-H 'content-type: application/json' -d '{"model":"x","messages":[{"role":"user","content":"hi"}]}'
# → 401
# 带 key 生成(OpenAI SDK 同此)
curl -s -X POST https://api.example.com/v1/chat/completions \
-H "Authorization: Bearer $(cat .api-key)" -H 'content-type: application/json' \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":16}'
# 站点未被破坏
curl -s -o /dev/null -w '%{http_code}' https://api.example.com/ # 200
上一篇 :01 硬件选型与 llama.cpp 部署 · 下一篇 :03 多协议支持:Anthropic 与 Responses API