9Router 云端部署实战指南:VPS、Docker 与 Nginx 反向代理完整方案
9Router 是一个开源 AI 网关,可将 Claude Code、Codex、Cursor、Cline、Copilot、Antigravity 等工具连接到免费的 Claude/GPT/Gemini 模型,并通过自动故障切换(Auto-fallback)与 RTK(-40% Token)等特性避免触达限额。本指南以 云端部署文档 为主体,结合仓库源码(package.json、Dockerfile、docker-compose.yml、custom-server.js、dashboardGuard.js 等)深度讲解将 9Router 部署到 VPS 或 Docker 的完整流程。读完本文,你将掌握从零搭建生产环境、配置 Nginx 反向代理与 SSL、加固安全策略以及日常监控与排障的完整实战能力。
注意:本文涉及的端口、环境变量与命令均以当前仓库实际代码为准。仓库根目录即为应用目录(无
app/子目录),Web 仪表盘监听20127端口,LLM API 代理监听20128端口,具体实现见下文「端口与进程架构」一节。
部署前的关键认知:端口与进程架构
在动手部署前,先理解 9Router 的运行时架构,能避免后续大量踩坑。从 package.json 的脚本定义可见:
"dev": "next dev --port 20127",
"start": "next start --port 20127",
"build": "next build --webpack"
- 20127 端口:Next.js Web 应用(仪表盘 + 页面)默认监听端口;
- 20128 端口:LLM API 代理 / OpenAI 兼容端点(
/v1)所在端口,README.md 中明确 Dashboard 为http://localhost:20128/dashboard、OpenAI 兼容 API 为http://localhost:20128/v1,且 Dockerfile 中ENV PORT=20128、cli/src/cli/api/client.js 默认port: 20128,CLI 面板也提示Endpoint: http://localhost:20128/v1(见 cli/src/cli/menus/settings.js)。
结合 Dockerfile 的 EXPOSE 20128 与 ENV HOSTNAME=0.0.0.0 可以看出,生产环境(standalone + custom-server.js)中 20128 是统一入口。PORT 环境变量用于覆盖监听端口,Docker 镜像内部默认使用 20128。因此在 Nginx 反向代理配置中,仪表盘与 /v1 API 的 proxy_pass 目标端口要依据实际运行方式(npm run start 时为 20127,Docker 时为 20128)正确设置,下文会分别给出对应配置。
另外,custom-server.js 是生产环境(Docker standalone)的实际入口:它包装了 Node HTTP Server,从 TCP socket 读取不可伪造的客户端真实 IP,并删除客户端传入的 X-Forwarded-For / X-Real-IP 头,只有在 TCP 对端是本机回环地址(即存在本机反向代理)时才信任转发头,再以 x-9r-real-ip 内部头传递给应用。这意味着:
- 9Router 自身的限流、登录防护(见 src/app/api/auth/login/route.js 的
getClientIp)基于真实 IP,不会被攻击者伪造X-Forwarded-For绕过; - Nginx 反向代理必须运行在与应用相同的本机(或通过回环地址转发),转发头才能被信任。
VPS 部署
前置要求
按文档要求,VPS 需满足:
- Ubuntu 20.04+ 或类似 Linux 发行版;
- Node.js 20+(仓库 Dockerfile 使用
node:22-alpine,package.json 无 engines 字段但以 Node 20+ 为基准); - Git;
- root 或 sudo 权限。
步骤 1:克隆仓库
当前仓库根目录即为应用代码(不存在文档所述 app/ 子目录),因此克隆后直接进入仓库目录即可:
git clone https://github.com/decolua/9router.git
cd 9router
步骤 2:安装依赖
npm install
说明:仓库将
better-sqlite3放在optionalDependencies(package.json 注释明确说明),因此即使目标系统缺少编译工具链,npm install也不会失败,运行时数据库会回退到sql.js(纯 WASM 实现),这为无编译环境的生产机提供了良好兼容性。
步骤 3:构建应用
npm run build
构建产物为 Next.js standalone 模式(next build --webpack),输出到 .next/standalone,配合 custom-server.js 使用。
步骤 4:配置环境变量
创建 .env 文件或导出变量:
export JWT_SECRET="your-secure-secret-change-this-to-random-string"
export INITIAL_PASSWORD="your-secure-password"
export DATA_DIR="/var/lib/9router"
export NODE_ENV="production"
环境变量表(结合源码验证):
| 变量 | 默认值 | 说明 | 源码依据 |
|---|---|---|---|
JWT_SECRET |
自动生成并写入 $DATA_DIR/jwt-secret |
生产环境必须修改! 用于 JWT token 签名(HS256,24 小时有效期) | src/lib/auth/dashboardSession.js |
INITIAL_PASSWORD |
123456 |
仪表盘首次登录密码;设置密码后以数据库 bcrypt 哈希为准 | src/app/api/auth/login/route.js |
DATA_DIR |
~/.9router |
数据库与数据存储路径;目录不可写时自动回退默认目录 | src/lib/dataDir.js |
NODE_ENV |
development |
部署时设为 production |
src/mitm/config.js 等按此分支 |
ENABLE_REQUEST_LOGS |
false |
设为 true 启用 debug 请求/响应日志 |
open-sse/utils/requestLogger.js |
PORT |
20128(Docker)/ 20127(npm run start) | 服务监听端口 | Dockerfile、package.json |
HOSTNAME |
0.0.0.0(Docker) |
监听地址,容器内必须为 0.0.0.0 |
Dockerfile |
源码细节印证:
JWT_SECRET若未设置,dashboardSession.js 会用crypto.randomBytes(32).toString("hex")生成随机密钥并以0600权限写入$DATA_DIR/jwt-secret,重启后仍有效。但为了多实例部署与可控性,生产环境务必显式设置;INITIAL_PASSWORD仅在数据库尚未设置密码哈希时生效(storedHash为空),一旦用户在仪表盘修改过密码,环境变量将不再作为登录凭据;DATA_DIR的解析在 dataDir.js 中实现:Windows 平台会忽略 Unix 风格绝对路径,EACCES/EPERM权限错误时回退~/.9router,避免因目录不可写导致启动失败。
步骤 5:创建数据目录
sudo mkdir -p /var/lib/9router
sudo chown $USER:$USER /var/lib/9router
数据目录承载 SQLite 数据库、JWT 密钥文件、请求详情记录等,权限配置不当会触发 dataDir.js 的回退逻辑,导致数据落在默认目录,所以请务必保证目录属主为运行用户。
步骤 6:启动应用
npm run start
该命令实际执行
next start --port 20127(见 package.json),仪表盘监听20127,而 LLM API(/v1)由内部代理进程在20128提供服务。若需修改端口,使用PORT=xxxx npm run start并同步调整 Nginx 与防火墙规则。
步骤 7:用 PM2 部署到生产环境
PM2 让应用持续运行、崩溃时自动重启:
# 全局安装 PM2
npm install -g pm2
# 用 PM2 启动 9Router
pm2 start npm --name 9router -- start
# 保存 PM2 配置
pm2 save
# 设置开机自启
pm2 startup
# 按上一条命令打印的提示执行
PM2 管理命令:
# 查看日志
pm2 logs 9router
# 重启应用
pm2 restart 9router
# 停止应用
pm2 stop 9router
# 查看状态
pm2 status
# 监控资源
pm2 monit
Docker 部署
仓库已内置生产级 Dockerfile(多阶段构建)与 docker-compose.yml,建议直接复用,无需再从文档示例重新编写。下面先解读仓库自带 Dockerfile,再给出开箱即用的运行方式。
方式 1:使用仓库内置 Dockerfile
仓库 Dockerfile 要点:
- 基础镜像:
node:22-alpine(可通过ARG NODE_IMAGE覆盖); - 多阶段构建:builder 阶段安装
python3 make g++ linux-headers以支持原生模块编译(如better-sqlite3),NEXT_TELEMETRY_DISABLED=1关闭遥测,npm run build产出 standalone 产物; - 运行阶段:
ENV NODE_ENV=production、ENV PORT=20128、ENV HOSTNAME=0.0.0.0、ENV DATA_DIR=/app/data; - 关键拷贝:除了
.next/standalone,还显式拷贝custom-server.js、open-sse、src/mitm、node-forge与next(注释说明 standalone 文件追踪可能遗漏这些 MITM 子进程与运行时依赖); - 数据目录:
mkdir -p /app/data并将/app/data-home符号链接到/root/.9router,兼容默认数据路径的读取逻辑; - 入口:
ENTRYPOINT ["/entrypoint.sh"](启动前chown -R node:node /app/data /app/data-home修正挂载卷权限,再以su-exec node降权运行),CMD ["node", "custom-server.js"]; - 端口:
EXPOSE 20128。
构建并运行:
# 构建镜像
docker build -t 9router .
# 运行容器
docker run -d \
--name 9router \
-p 20128:20128 \
-e JWT_SECRET="your-secure-secret-change-this" \
-e INITIAL_PASSWORD="your-secure-password" \
-e NODE_ENV="production" \
-e DATA_DIR="/app/data" \
-v 9router-data:/app/data \
9router
与原文档示例不同,仓库镜像内部端口为
20128(非 3000),映射-p 20128:20128后,仪表盘与/v1API 均通过该端口对外提供服务。
方式 2:使用仓库内置 Docker Compose
仓库 docker-compose.yml 实际包含两个服务:
services:
9router:
image: decolua/9router:latest
container_name: 9router
restart: always
ports:
- "20128:20128"
volumes:
- 9router-data:/app/data
env_file:
- .env
environment:
DATA_DIR: /app/data
PORT: "20128"
HOSTNAME: "0.0.0.0"
NODE_ENV: production
HEADROOM_URL: http://headroom:8787
depends_on:
- headroom
headroom:
image: ghcr.io/chopratejas/headroom:latest
container_name: headroom
restart: always
ports:
- "8787:8787"
volumes:
9router-data:
name: 9router-data
要点:
- headroom 服务:9Router 的 RTK(Token 节省)能力依赖 headroom 代理服务,compose 中通过
HEADROOM_URL: http://headroom:8787将两个容器接入同一网络;仓库中 headroom 相关实现可见 src/lib/headroom/detect.js 与 open-sse/rtk 目录; .env文件:env_file: .env会自动加载宿主机同目录.env,因此务必在其中写入JWT_SECRET、INITIAL_PASSWORD等敏感变量(不要提交到版本库);- 数据卷:命名卷
9router-data挂载到/app/data,配合 Dockerfile 的 entrypoint 自动修正权限。
使用 Compose 运行:
# 先准备 .env(参考上文环境变量表)
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f
# 停止服务
docker compose down
# 重新构建并重启(本地镜像构建时)
docker compose up -d --build
仓库还提供了 start.sh 作为快速重建脚本(停止并删除旧容器 → 构建镜像 → 以 .env 环境变量与数据卷运行),适合开发迭代:
./start.sh
Nginx 反向代理
为什么使用 Nginx?
- SSL/TLS 终止(统一管理证书,应用层保持 HTTP);
- 域名映射(对外暴露
https://your-domain.com,隐藏实际端口); - 负载均衡(多实例扩展时的入口分发);
- 更好的安全性(统一入口控制、限流、隐藏后端指纹)。
步骤 1:安装 Nginx
sudo apt update
sudo apt install nginx
步骤 2:配置 Nginx
创建 /etc/nginx/sites-available/9router。以下配置将 HTTP 80 重定向到 HTTPS 443,并在 443 上同时代理仪表盘(根路径 → 20128,若用 npm run start 部署则改为 20127)与 /v1 LLM API(→ 20128)。SSE 支持是流式输出的关键:proxy_buffering off 与 proxy_read_timeout 86400 缺一不可,否则 AI 流式响应会被缓冲或提前断开:
server {
listen 80;
server_name your-domain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL certificates (use certbot to generate)
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Proxy to 9Router (Docker / PORT=20128 时使用 20128;npm run start 时为 20127)
location / {
proxy_pass http://localhost:20128;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# SSE support - CRITICAL for streaming
proxy_buffering off;
proxy_read_timeout 86400;
}
# API endpoint (OpenAI-compatible /v1)
location /v1 {
proxy_pass http://localhost:20128;
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;
# SSE support - CRITICAL for streaming
proxy_buffering off;
proxy_read_timeout 86400;
}
}
结合 custom-server.js 的实现,Nginx 与 9Router 位于同一主机时,转发头会被视为可信来源(TCP 对端为回环地址),应用据此还原真实客户端 IP 用于限流与日志;
X-Forwarded-Proto: https还会触发 dashboardSession.js 的shouldUseSecureCookie,为auth_tokenCookie 自动启用Secure标志,保证 HTTPS 下 Cookie 不泄露。
步骤 3:启用站点
# 创建软链接
sudo ln -s /etc/nginx/sites-available/9router /etc/nginx/sites-enabled/
# 测试配置
sudo nginx -t
# 重新加载 Nginx
sudo systemctl reload nginx
步骤 4:使用 Let's Encrypt 配置 SSL
# 安装 certbot
sudo apt install certbot python3-certbot-nginx
# 获取 SSL 证书
sudo certbot --nginx -d your-domain.com
# 自动续期已自动配置
# 测试续期
sudo certbot renew --dry-run
安全注意事项
1. 修改默认凭据
关键: 部署前修改 JWT_SECRET 和 INITIAL_PASSWORD:
# 生成安全的 JWT secret
openssl rand -base64 32
# 将该值用于 JWT_SECRET
export JWT_SECRET="generated-secret-here"
源码层面还有几层值得注意的加固逻辑:
- 默认密码强制改密:src/app/api/auth/login/route.js 中,当数据库未设置密码且未配置
INITIAL_PASSWORD时,远程客户端登录会返回mustChangePassword: true,强制用户先修改密码再使用仪表盘,避免默认密码123456暴露在公网; - 登录限流:同一 IP 连续失败会被锁定(
checkLock/recordFail,返回 429 并附Retry-After),有效抵御暴力破解; - 远程 API 需要 Key:dashboardGuard.js 规定
/v1等 LLM API 前缀在远程访问时必须有合法 API Key(Authorization: Bearer/x-api-key/x-goog-api-key/ URLkey参数均可),本地回环访问除外; - 本地专有接口:
/api/mcp/、/api/tunnel/*、/api/auth/reset-password、/api/headroom/*等会启动子进程或读取宿主机敏感信息的路由被列入LOCAL_ONLY_PATHS,仅允许本机(回环地址 + 合法 JWT)或携带 CLI Token 的请求访问(见 dashboardGuard.js 的canAccessLocalOnlyRoute)。
2. 防火墙配置
# 允许 SSH
sudo ufw allow 22/tcp
# 允许 HTTP/HTTPS(若使用 Nginx)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 若不使用反向代理,放开 9Router 端口
sudo ufw allow 20128/tcp
# 启用防火墙
sudo ufw enable
原文档示例为
3000/tcp,当前仓库实际对外端口为20128(以及npm run start模式下的20127),请按实际监听端口放行。
3. 限制仪表盘访问
如果只需要 API 访问,可限制仪表盘端口,仅允许 localhost 访问:
# 仅允许 localhost 访问仪表盘(以 20128 为例,或对 20127 执行同样操作)
sudo ufw deny 20128/tcp
注意:直接
deny端口也会同时阻断/v1API。若希望只开放 API 而隐藏仪表盘,更稳妥的做法是不开放任何公网端口,仅通过 SSH 隧道访问仪表盘,API 则通过 Nginx 按路径分流并配合 dashboardGuard.js 的 API Key 校验实现。
通过 SSH 隧道访问仪表盘:
ssh -L 3000:localhost:20128 user@your-server.com
# 然后在浏览器打开 http://localhost:3000
4. 定期更新
# 更新系统包
sudo apt update && sudo apt upgrade -y
# 更新 9Router(当前仓库根目录即应用目录,无需进入 app 子目录)
cd /path/to/9router
git pull
npm install
npm run build
pm2 restart 9router
5. 备份策略
# 备份数据目录
tar -czf 9router-backup-$(date +%Y%m%d).tar.gz /var/lib/9router
# 每日自动备份(加入 crontab,注意 % 需转义)
0 2 * * * tar -czf /backups/9router-$(date +\%Y\%m\%d).tar.gz /var/lib/9router
数据目录包含 SQLite 数据库、jwt-secret 密钥文件与请求记录,是恢复服务的唯一凭据,务必纳入备份;若使用 Docker 命名卷 9router-data,可通过 docker run --rm -v 9router-data:/data -v $(pwd):/backup alpine tar czf /backup/9router-backup.tar.gz -C /data . 备份卷内容。
监控
检查应用状态
# PM2 状态
pm2 status
# 查看日志
pm2 logs 9router --lines 100
# 监控资源
pm2 monit
Nginx 日志
# 访问日志
sudo tail -f /var/log/nginx/access.log
# 错误日志
sudo tail -f /var/log/nginx/error.log
系统资源
# CPU 和内存使用
htop
# 磁盘使用
df -h
# 网络连接(确认监听端口)
netstat -tulpn | grep -E '20127|20128'
如需更细粒度的请求调试,可设置 ENABLE_REQUEST_LOGS=true 并重启应用,open-sse/utils/requestLogger.js 会输出 debug 级请求/响应日志,便于定位代理链路问题(生产环境建议仅在排查时临时开启)。
故障排除
应用无法启动
# 查看日志
pm2 logs 9router
# 检查端口是否被占用
sudo lsof -i :20127
sudo lsof -i :20128
# 检查环境变量
pm2 env 9router
若日志中出现 [DATA_DIR] ... not writable → fallback ~/.9router 之类的警告,说明 DATA_DIR 指向的目录权限不足(见 src/lib/dataDir.js),数据落到了默认目录,需修正目录属主并重启。
Nginx 502 Bad Gateway
# 检查 9Router 是否运行
pm2 status
# 查看 Nginx 错误日志
sudo tail -f /var/log/nginx/error.log
# 测试 Nginx 配置
sudo nginx -t
502 的常见原因包括:后端端口与 Nginx proxy_pass 不一致(例如 npm run start 为 20127 却代理到 20128)、Docker 容器未启动、防火墙拦截了回环流量等。
SSE 流式输出无法工作
确保 Nginx 配置中已设置 proxy_buffering off(配合 proxy_read_timeout 86400),这是流式响应不被缓冲的关键;同时确认 proxy_http_version 1.1 与 Upgrade/Connection 头已按上文示例配置。
权限被拒绝错误
# 修复数据目录权限
sudo chown -R $USER:$USER /var/lib/9router
chmod 755 /var/lib/9router
Docker 场景下若挂载卷出现权限问题,可检查 Dockerfile 内置的 /entrypoint.sh(启动时会自动 chown -R node:node /app/data /app/data-home),必要时手动修正宿主机卷属主。
下一步
完成云端部署后,可继续阅读仓库内相关文档:
- 连接提供商:配置 Claude / Gemini / GPT 等提供商订阅;
- 配置组合:利用自动故障切换组合多个提供商,避免触达限额;
- 集成工具:将 Cursor、Claude Code 等工具接入部署好的 9Router 网关;
- 其他部署场景可参考 localhost 部署 与 快速开始。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python270
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46066
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20143
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34051