Ubuntu 24.04 部署 Sub2API:小内存 VPS 从零搭建指南

面向零基础用户,完整说明 VPS、SSH、DNS、Docker、外部 PostgreSQL/Redis、Sub2API、Caddy HTTPS、排障、更新和安全注意事项。

Ubuntu 24.04 部署 Sub2API:小内存 VPS 从零搭建指南 封面

这篇文章复盘一套已经实际运行过的 Sub2API 部署方案:在一台 Ubuntu 24.04 小内存 VPS 上运行 Sub2API 容器,把 PostgreSQL 和 Redis 放在外部托管服务中,再由 Caddy 提供域名和 HTTPS。

文章面向第一次接触服务器的人。所有 IP、域名和账号都是示例,必须替换成自己的值。不要把真实口令、API Key、SSH 私钥、数据库连接串或 .env 文件发到聊天、论坛和公开仓库。

本文以 Sub2API v0.1.161 的部署文件为核对基准,写于 2026 年 7 月。项目仍在更新,实际部署前应重新阅读 Sub2API 仓库 的 README 和发布说明。

先理解:我们究竟在搭建什么

Sub2API 是一个 API 网关。它接收客户端请求,根据后台配置选择上游账号或渠道,再把响应返回给客户端。它不是模型本身,也不会凭空提供模型额度。

本方案的数据流如下:

电脑上的 API 客户端
        |
        | HTTPS 443
        v
api.example.com
        |
        v
Caddy 反向代理
        |
        | 仅服务器本机可访问
        v
127.0.0.1:8080 -> Sub2API 容器
                       |       |
                       |       +-> 外部 Redis
                       +----------> 外部 PostgreSQL
                       |
                       +----------> 已获授权的上游 API

每一层的职责不同:

  • DNS 把域名指向 VPS 公网 IP。
  • Caddy 监听 80 和 443,自动申请 HTTPS 证书。
  • Sub2API 只监听 127.0.0.1:8080,不直接暴露到互联网。
  • PostgreSQL 保存账号、用户和业务数据。
  • Redis 用于缓存、限流、队列等运行状态。
  • 客户端只拿到 Sub2API 生成的下游 API Key,不应拿到上游账号凭据。

使用边界与风险

部署前先确认以下事项:

  1. 只添加自己拥有或明确获授权使用的上游账号。
  2. 阅读并遵守上游服务条款、账号地区要求和当地法律。
  3. 自建中转不会消除账号风控。数据中心 IP、多人共享、异常并发和跨地区登录都可能触发限制。
  4. Sub2API 项目 README 明确提示账号封禁、服务中断和数据损失风险,并声明未授权商业运营。
  5. 本文部署的是 API 服务,不是通用 VPN、住宅代理或翻墙服务。

个人或少量可信用户可以使用这套架构,但不要把注册入口和 API Key 随意公开。

方案选择与最低配置

本文采用的是低内存方案:

组件部署位置原因
Sub2APIVPS 上的 Docker 容器便于启动、更新和查看健康状态
PostgreSQL外部托管服务减少 VPS 内存占用
Redis外部托管服务减少 VPS 内存占用
CaddyVPS 宿主机提供 HTTPS 和反向代理
DNSDuckDNS 或自己的域名服务商将域名解析到 VPS

实测 1 GB 内存、约 25 GB 磁盘并配有 2 GB 以上 Swap,可以承担一个人或少量人的轻量使用。但余量不大,不适合高并发、图片生成或大量账号调度。

如果想把 Sub2API、PostgreSQL 和 Redis 全部放在同一台机器,应改用上游的 docker-compose.local.yml,至少准备 2 GB 内存,稳妥起见使用 4 GB。不要在 1 GB 机器上无条件照搬完整三容器方案。

外部数据库和 Redis 的免费额度、休眠规则、连接数限制可能随时变化。免费不等于永久免费,也不等于有可靠性保证。

第一部分:创建 Ubuntu VPS

创建服务器时建议选择:

  • 系统:Ubuntu 24.04 LTS。
  • 架构:常见的 x86_64。
  • 地区:选择客户端网络相对稳定、同时符合上游服务地区政策的机房。
  • 登录:优先使用 SSH Ed25519 公钥,不依赖弱口令。
  • 自动备份:预算允许时开启;否则至少自行备份数据库和配置。

美国机房通常能访问更多国际服务,但中国大陆到美国的延迟和丢包可能更高。VPS 提供的是数据中心 IP,不是住宅 IP。机房选择应同时考虑网络质量、上游条款和数据合规,不能只看延迟。

本文使用以下示例,全文保持一致:

VPS IP: 203.0.113.10
SSH 用户: linuxuser
API 域名: api.example.com

203.0.113.10 是文档专用地址,不能直接拿来连接。

第二部分:在 Windows 上准备 SSH

以下命令在 Windows PowerShell 中执行,不是在服务器中执行。

生成 SSH 密钥

ssh-keygen -t ed25519 -f "$env:USERPROFILE\.ssh\id_ed25519"

系统会询问是否设置私钥口令。个人电脑建议设置,并妥善保存。生成后有两个文件:

  • id_ed25519 是私钥,绝对不能发给别人。
  • id_ed25519.pub 是公钥,可以添加到 VPS。

查看公钥:

Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub"

公钥通常以 ssh-ed25519 开头。复制完整的一行到服务器提供商的 SSH Key 页面。

连接服务器

ssh -i "$env:USERPROFILE\.ssh\id_ed25519" [email protected]

第一次连接会显示主机指纹。应先在 VPS 控制台核对指纹,再输入 yes。不能在没有核对的情况下盲目信任。

如果重装服务器后出现:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

先到提供商控制台确认新指纹确实属于刚重装的服务器,然后在 PowerShell 中删除旧记录:

ssh-keygen -R 203.0.113.10

再重新连接并核对新指纹。若没有重装或更换服务器,不要直接删除警告,它也可能意味着中间人攻击。

两个常见误解

运行 sudo 时,终端不会显示星号,也不会回显任何字符,这是正常的安全设计。输入完成后按回车即可。

查看 Ubuntu 版本的正确命令是:

cat /etc/os-release

路径中是连字符 -,不是波浪号 ~。

第三部分:初始化 Ubuntu

从这里开始,命令都在 SSH 登录后的 Ubuntu 终端中执行。

先确认身份和 sudo 权限:

whoami
sudo -v
sudo whoami

最后一条应输出 root。

更新系统和安装基础工具

sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl openssl ufw

如果升级安装了新内核,安排一次重启:

sudo reboot

等待几十秒后重新 SSH 登录。

检查内存、Swap 和磁盘

free -h
df -h /
swapon --show

1 GB VPS 最好有 2 GB 左右 Swap。Swap 不能替代内存,只是降低瞬时内存不足时被系统直接杀死进程的概率。

只有在 swapon --show 没有任何输出时,才创建 Swap 文件:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

完成后再次执行 free -h。不要重复向 /etc/fstab 添加同一行。

配置防火墙

先放行 SSH,再启用 UFW,否则可能把自己锁在服务器外面:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

最终只需要公网开放 22、80 和 443。不要放行 8080、5432 或 6379。

如果 VPS 提供商还有一层云防火墙,也要同步放行 22、80、443。UFW 和云防火墙任意一层阻断,外部都会无法访问。

第四部分:配置 DNS

可以使用自己的域名,也可以使用 DuckDNS 免费子域名。在 DNS 控制台中添加一条 A 记录,将 API 域名指向 VPS 的 IPv4 地址。

如果没有正确配置 IPv6,不要添加 AAAA 记录。错误或过期的 AAAA 记录会导致部分网络优先走 IPv6 后连接失败。

在 Windows PowerShell 中检查公共 DNS:

Resolve-DnsName api.example.com -Server 1.1.1.1

返回的 A 记录必须是自己的 VPS IP。DNS 生效前不要继续申请证书。

第五部分:安装 Docker 和 Caddy

Ubuntu 24.04 可以直接安装发行版维护的软件包:

sudo apt update
sudo apt install -y docker.io docker-compose-v2 caddy
sudo systemctl enable --now docker
sudo systemctl enable --now caddy

把当前管理用户加入 Docker 组:

sudo usermod -aG docker "$USER"

Docker 组基本等同于 root 权限,只能授予可信的服务器管理员。执行后退出 SSH 并重新登录,组权限才会完整生效。

验证安装:

docker --version
docker compose version
docker run --rm hello-world
sudo systemctl is-active caddy

hello-world 成功只证明 Docker 能拉取并运行容器,不代表 Sub2API 已部署。

第六部分:准备外部 PostgreSQL 和 Redis

docker-compose.standalone.yml 的含义是“只启动 Sub2API 应用”。它不会创建数据库和 Redis。如果没有外部服务,容器会不断重启或变成 unhealthy。

在托管服务控制台创建:

  • PostgreSQL 15 或更高版本。
  • Redis 7 或更高版本。
  • 尽量选择靠近 VPS 的区域。
  • 开启 TLS。
  • 如果提供 IP 白名单,只允许 VPS 公网 IP。

PostgreSQL 连接地址通常类似:

postgresql://<用户>:<口令>@<主机>:<端口>/<数据库>?sslmode=require

Redis TLS 地址通常以 rediss:// 开头。Sub2API 的 standalone Compose 需要把连接串拆成独立字段,而不是把整条 URL 填到一个位置。

应提前记录这些字段:

.env 字段内容
DATABASE_HOSTPostgreSQL 主机名
DATABASE_PORTPostgreSQL 端口
DATABASE_USERPostgreSQL 用户名
DATABASE_PASSWORDPostgreSQL 口令
DATABASE_DBNAME数据库名称,注意不是 DATABASE_NAME
DATABASE_SSLMODE托管服务通常使用 require
REDIS_HOSTRedis 主机名
REDIS_PORTRedis TLS 端口
REDIS_PASSWORDRedis 口令
REDIS_ENABLE_TLS使用 TLS 时填 true

免费数据库通常限制最大连接数。个人使用可从较小连接池开始,例如最大打开连接数 10、最大空闲连接数 5;Redis 连接池也不必使用面向高并发的巨大默认值。

第七部分:下载 Sub2API 部署文件

创建独立目录:

mkdir -p ~/sub2api
cd ~/sub2api
pwd

为了让教程可复现,这里固定读取 v0.1.161 的 Compose 文件:

curl -fsSL --retry 3 \
  https://raw.githubusercontent.com/Wei-Shaw/sub2api/v0.1.161/deploy/docker-compose.standalone.yml \
  -o docker-compose.yml

确认文件存在,并核对关键内容:

ls -lh docker-compose.yml
grep -E 'image:|AUTO_SETUP|BIND_HOST|DATABASE_HOST|REDIS_HOST' docker-compose.yml

该版本 Compose 中的镜像仍是 weishaw/sub2api:latest。这方便入门,但意味着未来重新拉取时可能得到新版本。更严格的生产环境应在测试后固定镜像 tag 或 digest,并保存回滚用的旧镜像信息。

不要从陌生网盘、聊天附件或二次打包站下载 Compose 文件。

第八部分:创建安全的 .env

先生成三个互不相同的随机值,分别用于管理员登录、JWT 签名和其他需要的本地秘密。每条命令的输出都只保存在自己的密码管理器中,不要截图或发送给别人:

openssl rand -hex 24
openssl rand -hex 32
openssl rand -hex 32

十六进制随机值没有 $、空格和 #,可以减少 Docker Compose 解析特殊字符时出错的概率。

创建文件:

umask 077
nano .env

先填写不含口令的部分:

BIND_HOST=127.0.0.1
SERVER_PORT=8080
SERVER_MODE=release
TZ=Asia/Shanghai

DATABASE_HOST=<数据库主机名>
DATABASE_PORT=<数据库端口>
DATABASE_USER=<数据库用户名>
DATABASE_DBNAME=<数据库名称>
DATABASE_SSLMODE=require
DATABASE_MAX_OPEN_CONNS=10
DATABASE_MAX_IDLE_CONNS=5

REDIS_HOST=<Redis主机名>
REDIS_PORT=<Redis端口>
REDIS_DB=0
REDIS_POOL_SIZE=32
REDIS_MIN_IDLE_CONNS=2
REDIS_ENABLE_TLS=true

ADMIN_EMAIL=<自己的管理员邮箱>
JWT_SECRET=<64位十六进制随机值>
JWT_EXPIRE_HOUR=24

然后按照同样的“字段名、ASCII 等号、值”格式,补齐以下三个敏感字段:

  • DATABASE_PASSWORD:数据库控制台提供的口令。
  • REDIS_PASSWORD:Redis 控制台提供的口令。
  • ADMIN_PASSWORD:刚生成的管理员随机口令。

如果托管服务提供的口令包含 $、# 或空格,应按照 Docker Compose 的 .env 规则使用单引号包住整个值。不要自行对独立字段再做 URL 百分号编码。

保存后收紧权限:

chmod 600 .env
ls -l .env

不要执行会把展开后配置完整打印到屏幕的命令并把输出发给别人。只做静默语法验证:

docker compose config --quiet

没有输出且退出码为 0,表示 Compose 能解析文件;它不代表数据库凭据一定正确。

为什么 JWT 必须固定

JWT_SECRET 用于签发登录会话。如果留空,应用可能在启动时生成随机值;容器重启后旧会话无法验证,网页就会提示“会话已过期,请重新登录”或登录后又回到登录页。

因此首次正式运行前就应设置固定、足够长的随机值。修改该值会让现有会话全部失效。

如果启用 TOTP 双因素认证,还要核对自己使用的 Compose 版本是否将 TOTP_ENCRYPTION_KEY 传入容器。v0.1.161 的完整 Compose 包含它,但 standalone 文件未包含该项,不能只在 .env 中添加字段就假设已经生效。

第九部分:启动 Sub2API

在 ~/sub2api 中执行:

docker compose up -d
docker compose ps

第一次启动需要连接数据库并执行迁移,健康状态可能暂时显示 starting。等待一会儿后检查:

docker inspect -f '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}} restart_count={{.RestartCount}}' sub2api
curl -i http://127.0.0.1:8080/health

正常结果应满足:

  • 容器状态是 running healthy。
  • 重启次数保持为 0。
  • 健康接口返回 HTTP 200 和 {"status":"ok"}。
  • docker compose ps 只显示一个 Sub2API 服务,这是 standalone 模式的正常现象。

确认端口只监听本机:

sudo ss -lntp | grep ':8080'

应该看到 127.0.0.1:8080,不能是 0.0.0.0:8080。

如果容器反复 unhealthy

先查看退出信息和最近日志:

docker inspect -f 'exit={{.State.ExitCode}} error={{.State.Error}} restart_count={{.RestartCount}}' sub2api
docker compose logs --tail=200 sub2api

重点检查:

  1. DATABASE_DBNAME 是否写成了错误的字段名。
  2. PostgreSQL 是否要求 TLS,DATABASE_SSLMODE 是否匹配。
  3. Redis 是否要求 TLS,端口和 REDIS_ENABLE_TLS 是否匹配。
  4. 托管服务是否限制了 VPS IP。
  5. 免费数据库的连接数上限是否低于当前连接池设置。
  6. .env 中带 $ 的值是否被 Compose 当成变量展开。
  7. VPS 是否能解析并访问托管服务主机名。

日志有可能包含自动生成的管理员口令或连接错误细节。向别人求助前必须脱敏。

第十部分:用 Caddy 提供 HTTPS

这一步的目标是让公网只接触 Caddy,Caddy 再访问本机 8080。

先备份现有配置:

sudo cp -a /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.backup.$(date +%Y%m%d%H%M%S)"

编辑配置:

sudoedit /etc/caddy/Caddyfile

如果服务器上还有博客或其他站点,不要覆盖它们,只新增一个独立站点块:

api.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8080
}

将 api.example.com 换成自己的真实域名。Caddy 原生支持 WebSocket,并会正确处理常见的流式响应,不需要为了 SSE 复制来历不明的超时配置。

格式化、验证并加载:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl is-active caddy

只有 validate 成功后才能 reload。若 reload 失败,立即查看:

sudo journalctl -u caddy -n 100 --no-pager

Caddy 自动申请证书需要满足:

  • 域名 A 记录已经指向当前 VPS。
  • 80 和 443 在云防火墙与 UFW 中都已放行。
  • 没有错误的 AAAA 记录把访问引向另一台机器。
  • 80 或 443 没有被其他程序占用。

第十一部分:逐层验收

不要只看“网页能打开”。按从内到外的顺序检查,才能知道故障在哪一层。

服务器本机

curl --fail --silent --show-error http://127.0.0.1:8080/health
docker compose ps
sudo systemctl is-active caddy
sudo ss -lntp | grep -E ':(22|80|443|8080)[[:space:]]'

预期结果:Sub2API healthy、Caddy active、8080 仅为 loopback、80/443 由 Caddy 监听。

公网 HTTPS

在 Windows PowerShell 中执行:

curl.exe -i --connect-timeout 10 https://api.example.com/health

Windows PowerShell 中可明确写 curl.exe;Ubuntu 中应使用 curl。在 Ubuntu 里输入 curl.exe 会得到 command not found,这不是网络故障。

公网健康接口也必须返回 200。若本机 8080 正常而公网失败,问题通常位于 DNS、UFW、云防火墙、Caddy 或证书层。

管理后台

浏览器打开:

https://api.example.com/

使用 .env 中设置的管理员邮箱和管理员口令登录。进入后台后再添加自己有权使用的上游账号、分组和下游 API Key。

客户端通常填写:

Base URL: https://api.example.com/v1
API Key: 在 Sub2API 后台生成的下游 Key

不同客户端会自动追加 /responses、/chat/completions 等路径,不要在不清楚客户端行为时重复拼接路径。

第十二部分:常见问题与判断方法

1. 443 端口连接被拒绝

先在服务器执行:

sudo ss -lntp | grep -E ':(80|443)[[:space:]]'
sudo systemctl status caddy --no-pager

如果没有监听,先修复 Caddy。若有监听,再检查 DNS、UFW 和云防火墙。

2. 登录后立即回到登录页

依次检查:

  1. 容器是否正在重启。
  2. JWT_SECRET 是否固定且长度足够。
  3. 修改 JWT 后是否清除了浏览器旧站点数据并重新登录。
  4. 系统时间是否正确。
docker inspect -f '{{.State.Health.Status}} restart_count={{.RestartCount}}' sub2api
timedatectl status

不要把 JWT 值贴到聊天中。

3. 客户端提示本地 502

若错误 URL 是 http://127.0.0.1:<某端口>/...,502 可能来自 CC Switch 或其他本地转发程序,而不是 VPS。

按三步隔离:

  1. 浏览器或 curl.exe 直接访问公网 /health。
  2. 服务器上访问 127.0.0.1:8080/health。
  3. 最后再检查本地代理软件、TUN 模式和规则。

公网健康正常但本地工具失败,应优先检查本地工具选择的 Provider、Base URL、模型名和代理链路。

4. 开启 Clash 后响应变慢或重连

全局代理或 TUN 模式可能让原本直连的 API 域名改走远端节点,也可能改变 WebSocket、HTTP/2 和长连接路径。切换网络出口时,正在进行的流式连接会被重建。

排查时比较:

  • 关闭代理后的公网健康延迟。
  • 开启代理后的公网健康延迟。
  • 浏览器访问与 API 客户端访问是否经过同一路径。
  • 本地 127.0.0.1 流量是否被错误接管。

不要同时叠加多个全局代理内核。需要规则时,应只对明确域名做可解释的路由,并保留本地回环地址直连。

5. 域名有时能开、有时失败

重点检查:

Resolve-DnsName api.example.com -Server 1.1.1.1
Resolve-DnsName api.example.com -Type AAAA -Server 1.1.1.1

错误 AAAA、运营商 DNS 缓存、代理软件 DNS 劫持和跨境链路波动都可能造成间歇问题。不要只用一次浏览器刷新判断服务器健康。

第十三部分:日常运维

常用检查

cd ~/sub2api
docker compose ps
docker compose logs --tail=100 sub2api
curl --fail --silent --show-error http://127.0.0.1:8080/health
sudo systemctl is-active caddy
free -h
df -h /

日志持续增长时要检查磁盘。不要等磁盘达到 100% 才处理。

更新前先做什么

至少保存:

  • 当前 docker-compose.yml。
  • 权限为 600 的 .env 安全备份。
  • 当前镜像 digest。
  • PostgreSQL 的可恢复备份。
  • 托管 Redis 的持久化或导出策略说明。

查看当前镜像信息:

docker image inspect weishaw/sub2api:latest --format '{{json .RepoDigests}}'

确认上游发布说明和数据库迁移要求后再更新:

cd ~/sub2api
docker compose pull
docker compose up -d
docker compose ps
curl --fail --silent --show-error http://127.0.0.1:8080/health

不要使用 docker compose down -v,其中 -v 会删除 Compose 管理的数据卷。standalone 模式的主要业务数据在外部 PostgreSQL,但应用卷、配置和日志仍可能有用。

回滚原则

更新前记录旧镜像 digest。新版本失败时,先收集日志,再把 Compose 中的镜像改回已验证的旧 digest,重新创建容器并验证健康接口。不要在没有数据库备份的情况下尝试逆向数据库迁移。

第十四部分:安全检查清单

上线前逐项确认:

  • SSH 使用公钥,私钥从未上传到服务器网页之外的第三方位置。
  • UFW 和云防火墙只开放 22、80、443。
  • 8080 仅监听 127.0.0.1。
  • PostgreSQL 和 Redis 不在 VPS 公网监听。
  • .env 权限为 600,且没有进入 Git 仓库。
  • 管理员口令、数据库口令、Redis 口令和 JWT 随机值互不相同。
  • JWT_SECRET 固定,容器重启后登录仍正常。
  • Caddy 配置通过 validate,HTTPS 健康接口返回 200。
  • 容器状态 healthy,重启次数没有持续增加。
  • 已建立数据库备份和更新回滚方法。
  • 只添加获授权的上游账号,并遵守相关服务条款。
  • 向他人求助前已从日志和截图中删除口令、Token、Cookie 和 Authorization。

最后总结

这套方案能在较低成本下运行,是因为把最吃常驻内存的 PostgreSQL 和 Redis 移到了外部服务。真正决定安全性和稳定性的不是“容器启动成功”,而是以下四条边界始终成立:

  1. 公网只开放 SSH 和 HTTPS 必需端口。
  2. Sub2API 的 8080 永远只监听本机。
  3. 所有长期秘密固定、分离并安全保存。
  4. 每次修改都从容器健康、回环健康、Caddy、DNS到客户端逐层验证。

做到这些以后,服务器故障和本地代理故障就不会混在一起,后续更新、迁移和排障也会清晰很多。

参考资料

评论

评论使用 GitHub Discussions 保存,点击后加载。