自建服务器 + 云服务器:网站部署与维护
前言
云服务器的内存和磁盘不够用,但手边有一台性能更好的电脑、Mac mini 或自建服务器,可以把网站运行在自己的设备上,让云服务器只负责公网入口。
应用、运行依赖和主要数据留在自建服务器,通过反向 SSH 隧道连接云服务器。用户仍然用域名和 HTTPS 访问,不需要进入你的局域网,自建服务器通常也不需要公网 IP 或路由器端口映射。
这篇从首次上线开始,说明两台机器怎样连接、域名如何访问内网应用,以及后续更新、排障和回滚怎么做。
两台服务器分别做什么
用户浏览器
│ HTTPS
▼
云服务器:公网 IP、域名入口、Apache、TLS 证书
│ 访问云端 loopback 转发端口
▼
反向 SSH 隧道(由自建服务器主动建立)
│
▼
自建服务器:应用进程、运行依赖、发布版本
│
└─ 数据库、上传文件或其他持久化数据| 位置 | 主要职责 |
|---|---|
| 开发或构建环境 | 保存源码、运行测试、生成发布包;也可以是自建服务器 |
| 自建服务器 | 运行网站,保存应用版本与数据,维持隧道连接 |
| 云服务器 | 接收公网请求,处理 HTTPS,将请求转入隧道 |
节省资源的关键是应用在哪里运行,不只是源码放在哪里。生产环境可以只接收运行产物和必要依赖,不必保留完整源码;云服务器也不必长期中转、保存发布包。
这种方式能减轻云端的应用 CPU、内存和主要存储压力,但云端代理、TLS、SSH、日志仍占资源,流量也仍经过云服务器。实际收益要看应用负载,不能理解成“只用一个 IP,其他零占用”。
代价是网站同时依赖自建设备、供电、上行带宽和隧道。设备睡眠、重启或家中断网,公网网站也会受影响。纯静态官网通常直接托管更简单,不必为了用这套结构额外运行一个服务。
上线前确定配置
| 配置项 | 怎样确定与检查 |
|---|---|
| 应用启动命令 | 查看仓库脚本和生产入口,确认启动的是 Web 服务,不是开发服务器 |
应用端口 APP_PORT | 在自建服务器分配并检查监听,本文假设 SSH 客户端与应用处于同一主机网络空间 |
测试端口 TEST_PORT | 与正式端口分开,确认没有其他进程占用 |
云端转发端口 TUNNEL_PORT | 在云服务器分配,只绑定 127.0.0.1,Apache 指向同一个端口 |
SSH 目标 CLOUD_SSH | 使用已核对主机指纹的专用账号或 SSH 别名,配置独立密钥 |
| 域名与证书 | DNS 指向云端入口,明确证书在何处签发、由谁续期 |
| 服务标签与目录 | 应用和隧道各自管理,名称不与其他项目冲突;数据目录不放进发布目录 |
健康检查 HEALTH_PATH | 使用应用实际实现的只读路径,最好返回运行版本 |
这三个端口不是同一个值,也不需要相同。示例若用应用端口 3000、云端转发端口 18080,连接关系就是云端 127.0.0.1:18080 转到自建服务器 127.0.0.1:3000;实际值从端口登记和监听检查中确定。
部署第二个项目时,为它分配自己的端口、标签和目录。已有项目更新则保留这些配置,只生成新的发布版本,不每次重新挑端口。
在自建服务器启动应用
先按项目锁文件安装依赖、运行测试和生产构建。可以在自建服务器构建,也可以在兼容的构建环境生成产物后传过去;包含原生依赖时,核对系统与 CPU 架构。
发布与数据分开,例如:
应用目录/
releases/<发布标识>/ 程序和运行依赖
shared/ 配置、日志和持久化数据新目录接收本次产物,不原地覆盖旧版本。发布包排除凭据、私钥和用户数据,传输前后比对 SHA-256。记录源码版本;工作区有未提交修改时,额外保留对应快照。
按应用真实支持的配置方式,让服务监听 127.0.0.1:$APP_PORT。先检查进程与本机请求:
# 自建服务器执行;变量由实际配置填写
: "${APP_PORT:?请先设置应用端口}"
: "${HEALTH_PATH:?请先设置健康检查路径}"
lsof -nP -iTCP:"$APP_PORT" -sTCP:LISTEN
curl -fsS "http://127.0.0.1:$APP_PORT$HEALTH_PATH"确认监听者就是新启动的进程,响应版本也正确。新进程因端口占用退出、旧进程继续返回 200,不算启动成功;不要为了释放端口批量结束未知进程。
本机检查通过后,用 systemd、LaunchAgent 或现有容器管理方式托管应用。配置工作目录、环境变量、日志、启动和失败重试策略,并检查机器重启后的行为。macOS 的用户 LaunchAgent 依赖用户会话,不能默认等同于无人登录也会运行的系统服务。
建立反向 SSH 隧道
自建服务器主动连云服务器,云端不需要直接访问家庭内网。先在云端配置专用隧道账号和公钥,将远程转发权限限制到分配的 loopback 端口,不授予不必要的交互管理权限。
OpenSSH 服务端可结合 AllowTcpForwarding remote、PermitListen 等配置限制该账号,并保持 GatewayPorts no。这些规则应限定到专用账号,先检查有效配置,不覆盖整台机器的 SSH 设置。具体约束见 sshd_config。
在自建服务器上前台试连:
: "${CLOUD_SSH:?请先设置已验证的云端 SSH 目标}"
: "${APP_PORT:?请先设置应用端口}"
: "${TUNNEL_PORT:?请先设置云端转发端口}"
ssh -NT \
-o BatchMode=yes \
-o StrictHostKeyChecking=yes \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-R "127.0.0.1:$TUNNEL_PORT:127.0.0.1:$APP_PORT" \
"$CLOUD_SSH"-R 左侧是在云服务器监听的地址,右侧目标由自建服务器上的 SSH 客户端连接。-N 不执行远端命令,-T 不分配终端。参数说明见 OpenSSH ssh。
ExitOnForwardFailure 能发现转发端口建立失败,但不代表后面的应用一定可达。保持隧道运行,在云服务器另一个终端检查:
# 云服务器执行,并在此终端填写同一组云端端口和健康路径
: "${TUNNEL_PORT:?}"
: "${HEALTH_PATH:?}"
ss -ltnp
curl -fsS "http://127.0.0.1:$TUNNEL_PORT$HEALTH_PATH"确认只出现预期的 loopback 监听,响应与自建服务器本机一致。无需向公网开放这个转发端口;公网只通过 Web 入口访问应用,SSH 入口按实际管理策略限制。
试连通过后,把同一条前台 SSH 命令交给进程管理器运行,配置开机启动、断线重启和重试间隔。保活用于发现断连,本身不负责重新连接;不要仅在终端加 & 就当作长期服务。
配置域名、HTTPS 与反向代理
域名 A 记录指向云服务器公网 IPv4;使用 AAAA 时,也要保证 IPv6 入口确实可用。先核对生效 DNS 和虚拟主机,避免同一地址端口上出现相互冲突的站点配置。
最直接的证书方案是在云服务器签发并续期,因为 HTTPS 在这里终止。HTTP-01 需要公网挑战路径可达;DNS-01 通过 DNS TXT 验证,适合通配符或不开放 HTTP 挑战的环境。使用所选 ACME 客户端与 DNS 服务商支持的插件,参见 挑战类型。
下面是 Apache 2.4 的 HTTPS 反代示例,假设域名为 example.com、云端转发端口为 18080,证书已经签发。替换成实际值,并确认 ssl、proxy、proxy_http、headers 模块已加载:
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
ProxyRequests Off
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
ProxyPass / http://127.0.0.1:18080/
ProxyPassReverse / http://127.0.0.1:18080/
</VirtualHost>保留现有 TLS 安全配置和日志设置,不把示例当成全部站点配置。HTTP 入口按证书验证方式保留所需挑战路径,再安排跳转 HTTPS;不要把别人的内网挑战转发规则一起抄进来。
应用的公开 origin 应是 https://example.com,不是内部 HTTP 地址。只有指定代理可信,应用才使用其转发头;Cookie、登录回调、WebSocket、上传和媒体 Range 根据业务分别测试。
修改前备份 Apache 配置,语法检查通过才重载:
sudo apache2ctl configtest && sudo systemctl reload apache2如果证书在另一台机器签发,续期后的分发和云端重载也必须自动化,单次复制证书不够。使用 Certbot 时确认调度任务和续期配置,再执行对应证书的 renew --dry-run,不要把测试证书部署到生产。参见 Certbot 续期说明。
完成首次上线验收
| 层级 | 检查结果 |
|---|---|
| 自建服务器 | 应用进程存活,正式端口与健康响应版本正确 |
| 云端隧道端口 | loopback 监听正确,可以取得相同的应用响应 |
| 公网域名 | HTTPS 验证通过,路由到预期站点,没有旧版本或错误缓存 |
| 真实浏览器 | 页面、接口、登录和关键操作可用;移动端按实际业务复测 |
PUBLIC_ORIGIN='https://example.com'
curl -fsS -o /dev/null \
-w 'http=%{http_code} tls=%{ssl_verify_result}\n' "$PUBLIC_ORIGIN/"
curl -sSI "$PUBLIC_ORIGIN/"不加 -k 绕过证书验证,也不只凭首页 200 判定完成。检查浏览器请求和关键业务,用测试账号验证写操作。
正式使用前,在维护窗口验证隧道断开能否恢复,以及设备重启后应用和隧道能否重新接上。自建设备的睡眠策略、网络变化和供电条件,都是这套部署的一部分。
上线后的更新与回滚
日常更新主要发生在自建服务器,云端域名、证书和转发端口通常保持不变。
新产物放入新的 release 目录,用独立测试端口试运行;测试数据、定时任务和消息消费者也要隔离,避免试运行直接改生产数据。确认新进程、版本和关键接口后,备份服务定义,再将正式应用切到新版本。
正式应用继续使用原端口时,隧道和 Apache 通常不需要重启。普通单实例切换可能短暂中断请求;要求无中断时,需要另外设计双实例、流量摘除和连接排空,不能靠重启命令保证。
切换失败就恢复上一个已验证 release 和服务定义,再按“本机 → 云端隧道端口 → 公网 → 浏览器”复查。不要为了回滚应用顺便覆盖数据库、上传文件或同步数据。
数据格式有变化时,先确认旧代码兼容性。备份使用存储支持的一致性方式,并在隔离位置演练恢复;能列出备份文件,不代表备份一定可用。
日常排障与资源检查
| 表现 | 排查顺序 |
|---|---|
公网 502 | 应用本机 → 隧道进程 → 云端监听和请求 → Apache 错误日志 |
| 内网正常,公网登录或跳转异常 | 公共 origin、Host/转发头、Cookie 和 HTTPS 配置 |
| 重启后网站离线 | 自建服务器会话、服务自启动、密钥读取、隧道重连 |
| 访问慢 | 应用负载、自建网络上行、隧道延迟、云端带宽和连接数 |
| 证书续期失败 | 按错误检查 DNS、挑战可达性或 ACME 出站连接,不因某个错误前缀就排除配置问题 |
两台机器分别监控:自建服务器看应用、数据、磁盘与供电网络;云服务器看代理、隧道端口、证书和流量。日志设置容量与保留期限,并脱敏凭据及个人信息。
这套结构把主要计算和数据搬到了自己的设备上,而不是消除了运行成本。只要把公网入口、隧道、应用和数据各自的职责分清,首次上线和后续维护就能沿着同一条请求链完成。

暂无评论