news 2026/4/20 17:05:17

NewAPI+Sub2API手把手部署搭建教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NewAPI+Sub2API手把手部署搭建教程

免责说明:本教程仅用于学习使用,请勿用于商业用途、生产用途或其他不合规用途。由此导致的任何损失或者风险,本人不承担任何责任。

强烈建议优先阅读以下两个教程,可以帮助理解本文教程过程:

newapi:

https://zhuanlan.zhihu.com/p/2028786961842751352

sub2api :

https://zhuanlan.zhihu.com/p/2028855400531764647

本人服务器环境: Ubuntu 24.04

使用下面命令,可以查看自己的服务器系统。建议云服务器使用硅谷节点,或者其他欧美地区的。采用靠谱厂家,但不要选择TX和ALI,也不要选择AWS等国际一线的。具体原由,可以自行查找A厂合规要求,以及O厂、G厂等等。

cat /etc/os-release | head -5

安装 Docker + Docker Compose

先安装基础工具包

sudo apt-get update -ysudo apt-get install -y ca-certificates curl gnupg lsb-release vim

加 Docker 官方 GPG(中国大陆建议清华源)

sudo install -m 0755 -d /etc/apt/keyrings

优先官方,失败自动尝试清华

sudo curl -fsSL --connect-timeout 8 https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc \

或者

sudo curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc

文件授权

sudo chmod a+r /etc/apt/keyrings/docker.asc

写 apt 源(自动用清华镜像,拉不通再换官方),命令顺序如下

CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME")
ARCH=$(dpkg --print-architecture)
REPO=https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu
curl -fsSI --connect-timeout 5 "$REPO/dists/$CODENAME/Release" >/dev/null \ || REPO=https://download.docker.com/linux/ubuntu
echo "deb [arch=$ARCH signed-by=/etc/apt/keyrings/docker.asc] $REPO $CODENAME stable" \ | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update -y

接下来需要安装 Docker

sudo apt-get install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

验证docker

docker --version

docker compose version

> ⚠️ `usermod -aG docker` 加组之后,**重新登录 SSH** 后才不用 sudo 跑 docker。本教程后面命令仍带 `sudo` 兼容。

配置镜像加速

sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.1panel.live" ], "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}}
sudo systemctl restart docker
docker info | grep -A3 'Registry Mirrors'

`log-opts` 限制单容器日志最多 30 MB(10×3),避免长跑日志撑爆磁盘。

部署 new-api

创建目录

sudo mkdir -p /opt/new-api/{data,logs,mysql,redis}
sudo chown -R $USER:$USER /opt/new-api
cd /opt/new-api

生成密码并保存

执行下面这段会一次性生成 4 个随机密码并写入 `.env.secret`,**只生成一次,丢了就要全部重置**:

cat > /opt/new-api/.env.secret <<EOF
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)MYSQL_USER=newapiMYSQL_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)SESSION_SECRET=$(openssl rand -hex 32)
chmod 600 /opt/new-api/.env.secret
cat /opt/new-api/.env.secret # 复制到自己的密码管理器

把上面输出的 4 行抄到密码管理器(KeePass、1Password 等)。

写 docker-compose.yml

# 加载刚生成的密码到 shell 变量

set -a; source /opt/new-api/.env.secret; set +a
cat > /opt/new-api/docker-compose.yml <<EOF
services: new-api: image: calciumion/new-api:latest container_name: new-api restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_started ports: - "3000:3000" environment: TZ: Asia/Shanghai SQL_DSN: "newapi:${MYSQL_PASSWORD}@tcp(mysql:3306)/new-api?charset=utf8mb4&parseTime=True&loc=Local" REDIS_CONN_STRING: "redis://redis:6379/0" SESSION_SECRET: "${SESSION_SECRET}" CRYPTO_SECRET: "${SESSION_SECRET}" SYSTEM_NAME: "WeskyApi" GIN_MODE: release ERROR_LOG_ENABLED: "true" volumes: - ./data:/data - ./logs:/app/logs healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/api/status >/dev/null || exit 1"] interval: 30s timeout: 5s retries: 10 mysql: image: mysql:8.4 container_name: new-api-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}" MYSQL_DATABASE: "new-api" MYSQL_USER: "newapi" MYSQL_PASSWORD: "${MYSQL_PASSWORD}" TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --innodb-buffer-pool-size=128M - --max-connections=200 - --performance-schema=OFF volumes: - ./mysql:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 20 redis: image: redis:7-alpine container_name: new-api-redis restart: unless-stopped command: ["redis-server", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru", "--save", "900", "1"] volumes: - ./redis:/dataEOF

> 关键调优(小内存机器必须):

> - MySQL `--innodb-buffer-pool-size=128M`:默认会自适应到 25% 内存> - MySQL `--performance-schema=OFF`:省 ~50 MB> - Redis `--maxmemory 128mb` + LRU:防止无限增长

拉镜像并启动

cd /opt/new-apisudo docker compose pull # 首次约 200 MB,国内 1-3 分钟sudo docker compose up -dsudo docker compose ps

等服务就绪

for i in $(seq 1 36); do if curl -fsS http://127.0.0.1:3000/api/status >/dev/null; then echo "ok"; break fi echo "等待中 $i/36"; sleep 5done

成功的话最后显示 `ok`,否则看日志:

sudo docker logs --tail 100 new-apisudo docker logs --tail 100 new-api-mysql

完成首次初始化(创建 root 管理员)

**当前版本 new-api 不再预置默认账户**,必须主动调 `/api/setup` 接口。先生成 root 密码:

ROOT_USER=rootROOT_PASSWORD="Wesky-$(openssl rand -base64 16 | tr -d '=+/' | cut -c1-16)"echo "ROOT_USERNAME=$ROOT_USER" | sudo tee -a /opt/new-api/.env.secretecho "ROOT_PASSWORD=$ROOT_PASSWORD" | sudo tee -a /opt/new-api/.env.secretecho ">>> 务必记下: $ROOT_USER / $ROOT_PASSWORD"

调用 setup 接口:

curl -sS -X POST http://127.0.0.1:3000/api/setup \ -H 'Content-Type: application/json' \ -d "$(cat <<EOF{"Username":"$ROOT_USER","Password":"$ROOT_PASSWORD","ConfirmPassword":"$ROOT_PASSWORD","SelfUseModeEnabled":false,"DemoSiteEnabled":false}EOF)"echo

期望返回 `{"message":"系统初始化成功","success":true}`。

登录拿 cookie + uid

LOGIN_RESP=$(curl -sS -c /tmp/newapi_cookie.txt -X POST http://127.0.0.1:3000/api/user/login \ -H 'Content-Type: application/json' \ -d "{\"username\":\"$ROOT_USER\",\"password\":\"$ROOT_PASSWORD\"}")echo "$LOGIN_RESP"UID=$(echo "$LOGIN_RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["id"])')echo "UID=$UID"

> ⚠️ **关键坑**:调任何管理员接口都要带 `New-Api-User: $UID` 这个 HTTP 头,仅 cookie 不够。否则会得到 `Unauthorized, New-Api-User header not provided`。

写入品牌信息(system_name / footer / 首页 / about)

H="-H Content-Type:application/json -H Accept:application/json -H New-Api-User:$UID"B=http://127.0.0.1:3000

例如把newapi标题,改为WeskyApi

# system_namecurl -sS -b /tmp/newapi_cookie.txt -X PUT $B/api/option/ $H \ -d '{"key":"SystemName","value":"WeskyApi"}'; echo
# Logo(留空 → 前端渲染 SystemName 文字)curl -sS -b /tmp/newapi_cookie.txt -X PUT $B/api/option/ $H \ -d '{"key":"Logo","value":""}'; echo
# 底栏curl -sS -b /tmp/newapi_cookie.txt -X PUT $B/api/option/ $H \ -d '{"key":"Footer","value":"<p style=\"text-align:center\">WeskyApi &copy; 2026</p>"}'; echo
# 首页内容curl -sS -b /tmp/newapi_cookie.txt -X PUT $B/api/option/ $H \ -d '{"key":"HomePageContent","value":"<div style=\"text-align:center;padding:48px 20px\"><h1 style=\"font-size:42px;letter-spacing:4px;color:#00e6ff\">WeskyApi</h1><p style=\"color:#9ab;margin-top:12px\">统一大模型 API 中转服务 · 稳定 · 高速</p></div>"}'; echo
# 关于curl -sS -b /tmp/newapi_cookie.txt -X PUT $B/api/option/ $H \ -d '{"key":"About","value":"<p>WeskyApi 由 Wesky 团队运营与维护。</p>"}'; echo

每条返回 `{"message":"","success":true}` 即成功。

验证

curl -sS http://127.0.0.1:3000/api/status \ | python3 -c 'import sys,json;d=json.load(sys.stdin)["data"];print("system_name=",d["system_name"]);print("footer_html=",d["footer_html"][:80])'

期望 `system_name= WeskyApi`。

安装 cloudflared 并暴露 new-api

装 cloudflared (Cloudflare 官方 apt 源)

sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \ | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' \ | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update -ysudo apt-get install -y cloudflaredcloudflared --version # 期望 2026.x

写 systemd 单元 `cloudflared-newapi.service`

sudo tee /etc/systemd/system/cloudflared-newapi.service >/dev/null <<'EOF'
[Unit]Description=cloudflared quick tunnel for new-api (trycloudflare.com)After=network-online.target docker.serviceWants=network-online.target[Service]Type=simpleExecStart=/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:3000 --no-autoupdate --metrics 127.0.0.1:20241Restart=on-failureRestartSec=5User=rootStandardOutput=append:/var/log/cloudflared-newapi.logStandardError=append:/var/log/cloudflared-newapi.logExecStartPre=/bin/sh -c 'command -v cloudflared | xargs -I{} ln -sf {} /usr/local/bin/cloudflared'[Install]WantedBy=multi-user.targetEOF

sudo : > /var/log/cloudflared-newapi.logsudo chmod 640 /var/log/cloudflared-newapi.logsudo systemctl daemon-reloadsudo systemctl enable --now cloudflared-newapi.servicesudo systemctl status cloudflared-newapi.service --no-pager | head -10

> `--metrics 127.0.0.1:20241`:第一个 tunnel 占 20241,第二个用 20242,**两个不能撞**。

拿到 trycloudflare URL

for i in $(seq 1 30); do URL=$(sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-newapi.log | head -1) [ -n "$URL" ] && { echo "URL: $URL"; break; } sleep 3doneecho "$URL" | sudo tee /opt/new-api/tunnel_url.txt```

映射的公网域名地址会被写入到指定的txt文件内,例如

把 URL 写回 new-api(让 Token 详情页生成完整链接)

curl -sS -b /tmp/newapi_cookie.txt -X PUT http://127.0.0.1:3000/api/option/ \ -H 'Content-Type: application/json' -H "New-Api-User: $UID" \ -d "{\"key\":\"ServerAddress\",\"value\":\"$URL\"}"echo

从你的电脑(不是服务器)验证

curl -sS https://你的tunnel域名.trycloudflare.com/api/status | head -c 200

> ⚠️ 服务器自己 curl trycloudflare.com 可能解析失败(DNS 屏蔽),不影响外部访问。

到这里 NewApi即可整体上线了。浏览器打开 URL 用 root(或者你自己定义的其他用户名) + 生成的密码登录即可。

部署 Sub2API

关键设计

- 复用 new-api 的 Redis(不再起新的 Redis 容器)- 单独起 PostgreSQL 18-alpine(Sub2API 强依赖)- 端口仅绑 `127.0.0.1:8080`,强制走 cloudflared- Postgres 不暴露端口

创建目录

sudo mkdir -p /opt/sub2api/{data,postgres}sudo chown -R $USER:$USER /opt/sub2apicd /opt/sub2api

生成机密

cat > /opt/sub2api/.env.secret <<EOFPOSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)JWT_SECRET=$(openssl rand -hex 32)TOTP_ENCRYPTION_KEY=$(openssl rand -hex 32)ADMIN_EMAIL=admin@weskyapi.localADMIN_PASSWORD=Wesky-$(openssl rand -base64 16 | tr -d '=+/' | cut -c1-16)EOF
chmod 600 /opt/sub2api/.env.secretcat /opt/sub2api/.env.secret # 抄到密码管理器

> - `JWT_SECRET` **必须固定**:变了所有用户被踢下线

> - `TOTP_ENCRYPTION_KEY` **必须固定**:变了所有 2FA 失效

> - `ADMIN_EMAIL` 是登录用户名

写 docker-compose.yml

set -a; source /opt/sub2api/.env.secret; set +a
cat > /opt/sub2api/docker-compose.yml <<EOFservices: sub2api: image: weishaw/sub2api:latest container_name: sub2api restart: unless-stopped ulimits: nofile: { soft: 65535, hard: 65535 } ports: - "127.0.0.1:8080:8080" depends_on: postgres: condition: service_healthy volumes: - ./data:/app/data environment: AUTO_SETUP: "true" SERVER_HOST: "0.0.0.0" SERVER_PORT: "8080" SERVER_MODE: "release" RUN_MODE: "standard" DATABASE_HOST: postgres DATABASE_PORT: "5432" DATABASE_USER: sub2api DATABASE_PASSWORD: "${POSTGRES_PASSWORD}" DATABASE_DBNAME: sub2api DATABASE_SSLMODE: disable DATABASE_MAX_OPEN_CONNS: "30" DATABASE_MAX_IDLE_CONNS: "5" REDIS_HOST: new-api-redis REDIS_PORT: "6379" REDIS_DB: "1" REDIS_POOL_SIZE: "128" REDIS_MIN_IDLE_CONNS: "4" JWT_SECRET: "${JWT_SECRET}" JWT_EXPIRE_HOUR: "168" TOTP_ENCRYPTION_KEY: "${TOTP_ENCRYPTION_KEY}" ADMIN_EMAIL: "${ADMIN_EMAIL}" ADMIN_PASSWORD: "${ADMIN_PASSWORD}" TZ: Asia/Shanghai SECURITY_URL_ALLOWLIST_ENABLED: "false" SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP: "true" SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS: "true" networks: - sub2api-network - newapi-shared healthcheck: test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 5 start_period: 60s postgres: image: postgres:18-alpine container_name: sub2api-postgres restart: unless-stopped shm_size: 128mb environment: PGDATA: /var/lib/postgresql/data POSTGRES_USER: sub2api POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" POSTGRES_DB: sub2api TZ: Asia/Shanghai command: - postgres - -c - shared_buffers=64MB - -c - effective_cache_size=192MB - -c - max_connections=80 volumes: - ./postgres:/var/lib/postgresql/data networks: - sub2api-network healthcheck: test: ["CMD-SHELL", "pg_isready -U sub2api -d sub2api"] interval: 10s timeout: 5s retries: 10 start_period: 30snetworks: sub2api-network: driver: bridge newapi-shared: external: true name: new-api_defaultEOF

> 三个不能省的细节:

> 1. `PGDATA: /var/lib/postgresql/data` 必须显式设置。`postgres:18-alpine` 默认 `PGDATA=/var/lib/postgresql/18/docker`,不显式设的话挂载到 `./postgres` 的卷里 **不会落数据**,重启就 initdb,账号订阅全丢。

> 2. `shm_size: 128mb`:PG18 默认 64MB 太小,复杂查询会报 shared memory 不足。

> 3. `networks.newapi-shared.external: true name: new-api_default`:把 sub2api 接入 new-api 的网络,才能用容器名 `new-api-redis` 解析到那个 redis 容器。

启动

cd /opt/sub2apisudo docker compose pull # 首次约 250 MBsudo docker compose up -dsudo docker compose ps

期望两个容器都 `Up (healthy)`:

sub2api weishaw/sub2api:latest Up (healthy) 127.0.0.1:8080->8080/tcpsub2api-postgres postgres:18-alpine Up (healthy) 5432/tcp

注意 `sub2api` 的 PORTS 列必须是 `127.0.0.1:8080`,不是 `0.0.0.0`。

等就绪 + 验证

for i in $(seq 1 36); do if curl -fsS http://127.0.0.1:8080/health >/dev/null; then echo ok; break; fi echo "等待中 $i"; sleep 5done
curl -sS http://127.0.0.1:8080/health # {"status":"ok"}

启动失败时看日志:

sudo docker logs --tail 100 sub2apisudo docker logs --tail 100 sub2api-postgres

暴露 Sub2API(第二个 cloudflared tunnel)

systemd 单元 `cloudflared-sub2api.service`

sudo tee /etc/systemd/system/cloudflared-sub2api.service >/dev/null <<'EOF'
[Unit]Description=cloudflared quick tunnel for Sub2API (trycloudflare.com)After=network-online.target docker.serviceWants=network-online.target[Service]Type=simpleExecStart=/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:8080 --no-autoupdate --metrics 127.0.0.1:20242Restart=on-failureRestartSec=5User=rootStandardOutput=append:/var/log/cloudflared-sub2api.logStandardError=append:/var/log/cloudflared-sub2api.logExecStartPre=/bin/sh -c 'command -v cloudflared | xargs -I{} ln -sf {} /usr/local/bin/cloudflared'[Install]WantedBy=multi-user.targetEOF
sudo : > /var/log/cloudflared-sub2api.logsudo chmod 640 /var/log/cloudflared-sub2api.logsudo systemctl daemon-reloadsudo systemctl enable --now cloudflared-sub2api.service

与第一个单元的 3 处差异:

- `--url` 指向 `8080`

- `--metrics` 用 `20242`

- 日志路径 `cloudflared-sub2api.log`

拿 URL

for i in $(seq 1 30); do URL2=$(sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-sub2api.log | head -1) [ -n "$URL2" ] && { echo "Sub2API URL: $URL2"; break; } sleep 3done
echo "$URL2" | sudo tee /opt/sub2api/tunnel_url.txt

验证(从其他机器电脑访问)

curl -sS https://你的sub2api.trycloudflare.com/health # 期望: {"status":"ok"}

浏览器打开此 URL,用 `.env.secret` 里的 `ADMIN_EMAIL` / `ADMIN_PASSWORD` 登录。

端口与服务清单(部署完成后查看)

# 容器sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
# systemd 服务systemctl list-units --type=service --state=running | grep -E '(docker|cloudflared)'
# 内存占用free -h
# 端口sudo ss -tlnp | grep -E ':(3000|8080|20241|20242) '

正常情况:

| 服务 | 监听 | 用途 ||---|---|---|| `new-api` | `0.0.0.0:3000` | new-api Web/API || `sub2api` | `127.0.0.1:8080` | Sub2API Web/API || `cloudflared-newapi` | `127.0.0.1:20241` | 第一个 tunnel 指标端口 || `cloudflared-sub2api` | `127.0.0.1:20242` | 第二个 tunnel 指标端口 || `new-api-mysql` | 仅容器内 3306 | MySQL,不暴露 || `new-api-redis` | 仅容器内 6379 | Redis(DB0=newapi, DB1=sub2api) || `sub2api-postgres` | 仅容器内 5432 | PostgreSQL,不暴露 |

日常运维

看日志

sudo docker logs -f --tail 100 new-apisudo docker logs -f --tail 100 sub2apisudo journalctl -u cloudflared-newapi.service -n 100 --no-pagersudo journalctl -u cloudflared-sub2api.service -n 100 --no-pagersudo tail -f /var/log/cloudflared-newapi.log /var/log/cloudflared-sub2api.log

重启服务

# 应用容器sudo docker compose -f /opt/new-api/docker-compose.yml restart new-apisudo docker compose -f /opt/sub2api/docker-compose.yml restart sub2api
# Tunnel(重启会换 trycloudflare URL!)sudo systemctl restart cloudflared-newapi.servicesudo systemctl restart cloudflared-sub2api.service
# 拿新 URLcat /opt/new-api/tunnel_url.txtcat /opt/sub2api/tunnel_url.txt
# 或直接从日志重抓sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-newapi.log | tail -1sudo grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /var/log/cloudflared-sub2api.log | tail -1

升级镜像

cd /opt/new-api && sudo docker compose pull && sudo docker compose up -dcd /opt/sub2api && sudo docker compose pull && sudo docker compose up -dsudo docker image prune -f

备份

sudo mkdir -p /opt/backup
# new-api MySQLsudo docker exec new-api-mysql sh -c \ 'MYSQL_PWD=$(grep ^MYSQL_PASSWORD /opt/new-api/.env.secret | cut -d= -f2) \ mysqldump -unewapi --single-transaction --quick --lock-tables=false new-api' \ | sudo tee /opt/backup/new-api-$(date +%F).sql >/dev/null
# Sub2API PostgreSQLsudo docker exec sub2api-postgres pg_dump -U sub2api sub2api \ | gzip | sudo tee /opt/backup/sub2api-$(date +%F).sql.gz >/dev/null
# 数据目录sudo tar czf /opt/backup/new-api-data-$(date +%F).tgz /opt/new-api/{data,logs}sudo tar czf /opt/backup/sub2api-data-$(date +%F).tgz /opt/sub2api/datals -lh /opt/backup/

加 cron 每天凌晨备份:

sudo crontab -e# 加一行:0 3 * * * /usr/bin/bash -lc '/opt/scripts/backup.sh' >>/var/log/backup.log 2>&1

升级到正式域名(Named Tunnel)

`trycloudflare.com` 临时域名每次重启 cloudflared 都换,不适合生产。要稳定走自有域名:

域名上 Cloudflare

1. 在 [dash.cloudflare.com](https://dash.cloudflare.com) → Add a Site

2. 把域名 NS 改到 Cloudflare 提供的两条 NS

3. 等 Zone 状态变 Active(一般 5-30 分钟)

在 Zero Trust 创建 Tunnel

[one.dash.cloudflare.com](https://one.dash.cloudflare.com) → Networks → Tunnels →

Create a tunnel → Cloudflared → 起个名(如 `vps-1`)→ 选 Linux 看到一段安装命令,里面有 `--token eyJhI...`,把 token 复制下来。

在Public Hostname标签页加两条记录:

| Subdomain | Domain | Type | URL ||---|---|---|---|| `api` | yourdomain.com | HTTP | `localhost:3000` || `pincc` | yourdomain.com | HTTP | `localhost:8080` |

替换 systemd unit

sudo systemctl stop cloudflared-newapi.service cloudflared-sub2api.servicesudo tee /etc/systemd/system/cloudflared.service >/dev/null <<EOF
[Unit]Description=cloudflared (named tunnel)After=network-online.target docker.serviceWants=network-online.target[Service]Type=simpleExecStart=/usr/local/bin/cloudflared tunnel run --token <粘贴你的TOKEN>Restart=on-failureRestartSec=5User=root[Install]WantedBy=multi-user.targetEOF
sudo systemctl disable cloudflared-newapi.service cloudflared-sub2api.servicesudo systemctl daemon-reloadsudo systemctl enable --now cloudflared.servicesudo systemctl status cloudflared.service --no-pager | head -10

同步 ServerAddress

new-api 上:

重新登录拿 cookie + UID,然后:

curl -sS -b /tmp/newapi_cookie.txt -X PUT http://127.0.0.1:3000/api/option/ \ -H 'Content-Type: application/json' -H "New-Api-User: $UID" \ -d '{"key":"ServerAddress","value":"https://api.yourdomain.com"}'

故障排查速查

| 症状 | 可能原因 | 处置 ||---|---|---|| `docker compose pull` 卡死 | 镜像源不可达 | 第 2.5 节加加速器后 `sudo systemctl restart docker` || MySQL OOM 自动重启 | buffer-pool 没限制 | 确认 compose 里有 `--innodb-buffer-pool-size=128M`,加 swap || `Unauthorized, New-Api-User header not provided` | 调管理员接口忘带 header | 所有 `/api/option /api/user/...` 必须带 `-H "New-Api-User: $UID"` || 第二次跑 setup 报"用户名/密码不正确" | DB 已有 root,密码不对 | 用 `.env.secret` 里的 ROOT_PASSWORD,不要重新生成 || Sub2API 启动报 `POSTGRES_PASSWORD is required` | env 没带过去 | 检查 `set -a; source .env.secret; set +a` 后再 envsubst || Sub2API 重启数据全丢 | `PGDATA` 没显式设 | 第 5.4 节关键细节 1 || Sub2API 连不上 redis | external 网络名错 | `docker network ls` 看 new-api 的网络名是不是 `new-api_default` || Tunnel URL 突然不通 | cloudflared 重启换了 URL | `cat /opt/*/tunnel_url.txt` 拿新地址,或看日志 || 服务器自己 curl trycloudflare 失败 | 国内 DNS 屏蔽 | 不影响外部,从本机/手机访问验证 || 两个 tunnel 同时只起来一个 | metrics 端口撞了 | 第二个改成 20242 |

安全清单

  • - [ ] `/opt/new-api/.env.secret` 与 `/opt/sub2api/.env.secret` 都是 `chmod 600`

  • - [ ] MySQL / Postgres / Redis 端口都没暴露宿主机

  • - [ ] 想更严的话把 new-api 端口也改成 `127.0.0.1:3000`

  • - [ ] 所有密码 ≥24 字节随机生成

  • - [ ] `JWT_SECRET` / `TOTP_ENCRYPTION_KEY` 是固定值

  • - [ ] 部署完后改 SSH:`PasswordAuthentication no`,只留密钥

  • - [ ] 启用 ufw 或腾讯云安全组:只开 22/80/443,封掉 3000/8080

  • - [ ] 备份脚本接 cron

  • - [ ] 留一条 `df -h` / `free -h` 的监控告警

文件位置速查

| 路径 | 用途 ||---|---|| `/opt/new-api/docker-compose.yml` | new-api 栈定义 || `/opt/new-api/.env.secret` | new-api MySQL/root 密码 || `/opt/new-api/data/` | new-api 应用数据 || `/opt/new-api/mysql/` | MySQL 数据 || `/opt/new-api/redis/` | Redis 持久化 || `/opt/new-api/tunnel_url.txt` | 当前 trycloudflare URL || `/opt/sub2api/docker-compose.yml` | Sub2API 栈定义 || `/opt/sub2api/.env.secret` | Sub2API JWT/Postgres/管理员密码 || `/opt/sub2api/data/` | Sub2API 应用数据 || `/opt/sub2api/postgres/` | PostgreSQL 数据 || `/opt/sub2api/tunnel_url.txt` | 当前 trycloudflare URL || `/etc/systemd/system/cloudflared-newapi.service` | new-api tunnel || `/etc/systemd/system/cloudflared-sub2api.service` | Sub2API tunnel || `/var/log/cloudflared-newapi.log` | new-api tunnel 日志 || `/var/log/cloudflared-sub2api.log` | Sub2API tunnel 日志 || `/etc/docker/daemon.json` | Docker 镜像加速 |

参考链接

- new-api:https://github.com/Calcium-Ion/new-api- Sub2API:https://github.com/Wei-Shaw/sub2api- Cloudflare Tunnel:https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/

知乎图文简易教程链接:

newapi:

https://zhuanlan.zhihu.com/p/2028786961842751352

sub2api :

https://zhuanlan.zhihu.com/p/2028855400531764647

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 17:05:15

数字记忆的守护者:如何用WeChatMsg永久保存微信聊天记录

数字记忆的守护者&#xff1a;如何用WeChatMsg永久保存微信聊天记录 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeC…

作者头像 李华
网站建设 2026/4/20 16:57:18

Python学习第二天

Python数据容器是什么&#xff1f; 一种可以容纳多份数据的Python数据类型&#xff0c;容纳的每一份数据称为1个元素&#xff0c;每一个元素可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。数据容器根据特点的不同&#xff0c;如<1>是否支持重复元素&…

作者头像 李华
网站建设 2026/4/20 16:56:19

为什么选择DEAP?5个理由让你快速上手进化算法

为什么选择DEAP&#xff1f;5个理由让你快速上手进化算法 【免费下载链接】deap Distributed Evolutionary Algorithms in Python 项目地址: https://gitcode.com/gh_mirrors/de/deap 在人工智能和优化问题日益复杂的今天&#xff0c;如何高效地解决复杂的搜索和优化问题…

作者头像 李华
网站建设 2026/4/20 16:55:22

深度解析onnx2torch:实现ONNX与PyTorch无缝转换的技术解密

深度解析onnx2torch&#xff1a;实现ONNX与PyTorch无缝转换的技术解密 【免费下载链接】onnx2torch Convert ONNX models to PyTorch. 项目地址: https://gitcode.com/gh_mirrors/on/onnx2torch 在深度学习模型部署与迁移的复杂生态中&#xff0c;框架间的互操作性一直是…

作者头像 李华