首页
/ 9Router 云端部署实战指南:VPS、Docker 与 Nginx 反向代理完整方案

9Router 云端部署实战指南:VPS、Docker 与 Nginx 反向代理完整方案

2026-09-11 11:48:15作者:虞亚竹Luna

9Router 是一个开源 AI 网关,可将 Claude Code、Codex、Cursor、Cline、Copilot、Antigravity 等工具连接到免费的 Claude/GPT/Gemini 模型,并通过自动故障切换(Auto-fallback)与 RTK(-40% Token)等特性避免触达限额。本指南以 云端部署文档 为主体,结合仓库源码(package.jsonDockerfiledocker-compose.ymlcustom-server.jsdashboardGuard.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,且 DockerfileENV PORT=20128cli/src/cli/api/client.js 默认 port: 20128,CLI 面板也提示 Endpoint: http://localhost:20128/v1(见 cli/src/cli/menus/settings.js)。

结合 DockerfileEXPOSE 20128ENV 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.jsgetClientIp)基于真实 IP,不会被攻击者伪造 X-Forwarded-For 绕过;
  • Nginx 反向代理必须运行在与应用相同的本机(或通过回环地址转发),转发头才能被信任。

VPS 部署

前置要求

按文档要求,VPS 需满足:

  • Ubuntu 20.04+ 或类似 Linux 发行版;
  • Node.js 20+(仓库 Dockerfile 使用 node:22-alpinepackage.json 无 engines 字段但以 Node 20+ 为基准);
  • Git;
  • root 或 sudo 权限。

步骤 1:克隆仓库

当前仓库根目录即为应用代码(不存在文档所述 app/ 子目录),因此克隆后直接进入仓库目录即可:

git clone https://github.com/decolua/9router.git
cd 9router

步骤 2:安装依赖

npm install

说明:仓库将 better-sqlite3 放在 optionalDependenciespackage.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) 服务监听端口 Dockerfilepackage.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=productionENV PORT=20128ENV HOSTNAME=0.0.0.0ENV DATA_DIR=/app/data
  • 关键拷贝:除了 .next/standalone,还显式拷贝 custom-server.jsopen-ssesrc/mitmnode-forgenext(注释说明 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 后,仪表盘与 /v1 API 均通过该端口对外提供服务。

方式 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.jsopen-sse/rtk 目录;
  • .env 文件env_file: .env 会自动加载宿主机同目录 .env,因此务必在其中写入 JWT_SECRETINITIAL_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 offproxy_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.jsshouldUseSecureCookie,为 auth_token Cookie 自动启用 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_SECRETINITIAL_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 需要 KeydashboardGuard.js 规定 /v1 等 LLM API 前缀在远程访问时必须有合法 API Key(Authorization: Bearer / x-api-key / x-goog-api-key / URL key 参数均可),本地回环访问除外;
  • 本地专有接口/api/mcp//api/tunnel/*/api/auth/reset-password/api/headroom/* 等会启动子进程或读取宿主机敏感信息的路由被列入 LOCAL_ONLY_PATHS,仅允许本机(回环地址 + 合法 JWT)或携带 CLI Token 的请求访问(见 dashboardGuard.jscanAccessLocalOnlyRoute)。

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 端口也会同时阻断 /v1 API。若希望只开放 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.1Upgrade/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),必要时手动修正宿主机卷属主。


下一步

完成云端部署后,可继续阅读仓库内相关文档:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
933
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.96 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23