初创出海在免备案条件下,如何用一台香港服务器隔离部署官网、后台与测试?
目标状态是:同一台香港服务器对外提供官网、后台和测试环境,但三者不共用公网端口、不共用运行用户、不共用配置文件和数据库凭据。官网通过 Nginx 提供静态文件,后台和测试服务只监听 127.0.0.1,由不同域名反向代理到不同端口,再用 systemd 管理进程、用日志和资源配额限制故障范围。
香港机房通常不需要办理中国大陆网站的ICP备案,但域名注册、业务内容、隐私保护、数据跨境和服务商政策仍需自行核对。下面以 Ubuntu 22.04/24.04、Nginx、systemd 和 Node.js 后台为例;如果实际后台使用 Python、Java 或 Go,只需替换进程启动命令,隔离、代理、证书和验收方法不变。
一、上线前的目标和准备条件
1. 推荐的域名与端口规划
使用域名区分环境,不把环境名称隐藏在路径中:
| 用途 | 示例域名 | 服务器内部监听地址 | 是否直接开放公网端口 |
|---|---|---|---|
| 官网生产环境 | www.example.com | Nginx 直接读取静态目录 | 只开放 80、443 |
| 后台生产环境 | admin.example.com | 127.0.0.1:4000 | 不开放 4000 |
| 测试环境 | staging.example.com | 127.0.0.1:5000 | 不开放 5000 |
| 证书验证 | 上述域名的 HTTP 路径 | Nginx /.well-known/ | 需要开放 80 |
如果还需要根域名访问官网,可以把 example.com 和 www.example.com 放在同一个 Nginx server 块中,并将根域名 301 跳转到 www。后台和测试环境应使用独立子域名,不建议使用 www.example.com/admin 作为后台入口,因为这样容易混淆缓存、Cookie、日志和访问控制边界。

2. 服务器和账号条件
作为低流量初创项目的起步参考,可以按以下资源准备:
- 2 vCPU、4 GB 内存、40 GB 以上 SSD:适合官网、轻量后台和非持续运行的测试环境。
- 4 vCPU、8 GB 内存:更适合后台需要构建、测试环境需要常驻、同时存在数据库或搜索服务的情况。
- 至少一个公网 IPv4 地址。
- 如果配置了 IPv6 AAAA 记录,必须确保服务器、系统防火墙和 Nginx 同时支持 IPv6;否则宁可暂时不发布 AAAA 记录。
- 一个可登录服务器控制台或带外管理控制台的服务商账号。
- 域名 DNS 修改权限、SSH 密钥、应用源代码或构建产物。
- 生产数据库和测试数据库的独立凭据。测试环境不能直接使用生产数据库账号。
一台 4 GB 内存服务器并不意味着所有服务都可以无限制运行。一个可操作的资源预算示例如下:
| 组件 | 典型内存预算 | 资源边界建议 |
|---|---|---|
| 系统、SSH、Nginx | 0.7~1 GB | 保留给系统,不分配给应用 |
| 后台服务 | 512~768 MB | MemoryMax 限制 |
| 测试服务 | 384~512 MB | 更低优先级和更小 CPU 配额 |
| 本地数据库(如确有必要) | 512 MB~1.5 GB | 尽量迁移到独立数据库 |
| 构建、缓存和安全余量 | 至少 1 GB | 不要全部分配给常驻进程 |
如果服务器需要在本机运行数据库,4 GB 内存会很快进入紧张状态。更稳妥的方式是官网和两个应用共享一台香港服务器,但数据库使用独立实例;如果暂时只能本机运行,则必须为数据库单独创建生产库、测试库、账号和备份策略。
围绕初创出海项目在单机上分开部署官网、后台与测试环境的需求,A5数据提供香港物理服务器租用,涵盖入门建站、Xeon Gold和AMD EPYC等系列。多核处理器、大内存与SSD或NVMe存储,为多进程运行、数据库缓存及版本文件留存提供资源基础;不同套餐的CN2与国际带宽选择,则可承接面向国内及海外用户的网站和接口访问需求,为应用层隔离部署提供硬件与网络支撑。
3. 先确认系统和网络状态
以下命令用于确认系统版本、Node.js 路径、监听端口和磁盘情况,不要在版本未知时直接套用发行版专属路径。
cat /etc/os-release
uname -a
command -v nginx || true
command -v node || true
node --version 2>/dev/null || true
sudo ss -lntp
free -h
df -h
建议先完成以下基础动作:
- 使用 SSH 密钥登录,保留服务商控制台作为失联后的恢复入口。
- 创建一个具备
sudo权限的运维账号,再考虑关闭密码登录。 - 确认实际 SSH 端口,不要把示例中的
22当成固定值。 - 在 DNS 中添加
A记录;如果没有准备好 IPv6,不要添加错误的AAAA记录。 - 等待 DNS 解析生效后,再进行证书申请。
修改 SSH 或防火墙前,应先开启一个备用 SSH 会话,并确认服务商控制台可用。防火墙配置错误可能导致当前会话断开,影响范围是整台服务器的网络入口,而不是单个应用。
二、建立目录、用户和环境边界
1. 创建不同的运行用户
官网静态文件由 Nginx 读取,后台和测试环境分别使用独立的系统用户。这样即使测试进程被错误配置,也不会默认获得后台目录的写权限。
下面命令以应用名 acme 为例:
sudo adduser --system --group --no-create-home acme-admin
sudo adduser --system --group --no-create-home acme-staging
sudo install -d -m 0755 /srv/acme
sudo install -d -m 0755 /srv/acme/web
sudo install -d -m 0750 -o acme-admin -g acme-admin /srv/acme/admin
sudo install -d -m 0750 -o acme-admin -g acme-admin /srv/acme/admin/releases
sudo install -d -m 0750 -o acme-admin -g acme-admin /srv/acme/admin/shared
sudo install -d -m 0750 -o acme-staging -g acme-staging /srv/acme/staging
sudo install -d -m 0750 -o acme-staging -g acme-staging /srv/acme/staging/releases
sudo install -d -m 0750 -o acme-staging -g acme-staging /srv/acme/staging/shared
sudo install -d -m 0755 /var/www/acme
sudo install -d -m 0750 /etc/acme
releases 用于存放多个版本,current 只作为当前版本的符号链接。发布新版本时切换链接,不要直接覆盖正在运行的目录。
如果目录已经存在,不要盲目重复执行创建用户或修改所有权的命令。尤其是递归执行 chown -R 会改变整个目录树的权限,影响范围可能包括上传文件、密钥和历史版本。修改前先检查:
sudo namei -l /srv/acme/admin/current
sudo find /srv/acme -maxdepth 3 -type d -printf '%M %u:%g %p\n'
2. 为不同环境准备独立配置
环境变量文件只存放服务器上的密钥、数据库连接信息和端口,不要提交到代码仓库:
sudo install -o root -g acme-admin -m 0640 /dev/null /etc/acme/admin.env
sudo install -o root -g acme-staging -m 0640 /dev/null /etc/acme/staging.env
后台生产环境示例:
NODE_ENV=production
HOST=127.0.0.1
PORT=4000
DATABASE_URL=填写生产数据库连接
SESSION_COOKIE_DOMAIN=admin.example.com
测试环境示例:
NODE_ENV=staging
HOST=127.0.0.1
PORT=5000
DATABASE_URL=填写测试数据库连接
SESSION_COOKIE_DOMAIN=staging.example.com
测试环境应该使用独立数据库或经过脱敏的数据副本。即使后台代码完全相同,也不能仅靠 NODE_ENV=staging 防止误操作;数据库账号权限、数据库名称和网络访问范围必须同时隔离。
3. 部署官网和应用版本
官网如果是静态构建产物,可以放在独立版本目录:
sudo install -d -m 0755 /srv/acme/web/releases/20250308-1200
sudo rsync -a ./dist/ /srv/acme/web/releases/20250308-1200/
sudo ln -sfn /srv/acme/web/releases/20250308-1200 /srv/acme/web/current
sudo chown -h root:root /srv/acme/web/current
sudo chown -R root:www-data /srv/acme/web/releases/20250308-1200
sudo find /srv/acme/web/releases/20250308-1200 -type d -exec chmod 0755 {} \;
sudo find /srv/acme/web/releases/20250308-1200 -type f -exec chmod 0644 {} \;
上面的 ./dist/ 代表已经在构建机完成的官网产物。若使用 rsync --delete,必须确认目标是新建的版本目录,而不是 /srv/acme/web/current,否则可能删除正在提供服务的文件。
Node.js 后台的发布过程应先在新版本目录完成依赖安装和构建,再切换符号链接。示例:
sudo install -d -m 0750 -o acme-admin -g acme-admin /srv/acme/admin/releases/20250308-1200
sudo rsync -a ./backend/ /srv/acme/admin/releases/20250308-1200/
sudo chown -R acme-admin:acme-admin /srv/acme/admin/releases/20250308-1200
sudo -u acme-admin bash -lc '
cd /srv/acme/admin/releases/20250308-1200 &&
npm ci --omit=dev
'
如果项目需要构建步骤,应在切换前执行:
sudo -u acme-admin bash -lc '
cd /srv/acme/admin/releases/20250308-1200 &&
npm run build
'
Node.js 的实际路径可能是 /usr/bin/node、/usr/local/bin/node 或其他路径。使用 command -v node 确认后,再把结果写入 systemd 的 ExecStart,不要凭经验填写。
三、用 systemd 守护后台和测试进程
1. 创建通用服务模板
用 systemd 代替直接在 SSH 会话中运行 npm start。SSH 断开、进程异常退出或服务器重启后,systemd 可以自动拉起服务,并把输出交给 journald 保存。
创建模板文件:
sudoedit /etc/systemd/system/acme-backend@.service
内容如下:
[Unit]
Description=Acme backend instance %i
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=acme-%i
Group=acme-%i
WorkingDirectory=/srv/acme/%i/current
EnvironmentFile=-/etc/acme/%i.env
ExecStart=/usr/bin/node /srv/acme/%i/current/server.js
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/srv/acme/%i/shared
MemoryMax=768M
CPUQuota=100%
TasksMax=128
LimitNOFILE=65535
StandardOutput=journal
StandardError=journal
SyslogIdentifier=acme-%i
[Install]
WantedBy=multi-user.target
这里的 ExecStart 假设入口文件是 server.js。如果项目使用 npm start,可以改成项目实际需要的启动方式;如果项目启动脚本会再派生子进程,要确认 systemd 能够正确追踪整个进程组。
ProtectSystem=strict 会让服务默认不能写入系统目录,只有 ReadWritePaths 指定的共享目录可写。如果应用还需要写入上传目录、缓存目录或临时目录,应先建立专用目录并加入 ReadWritePaths,不要为了省事关闭整个保护项。

2. 为测试环境降低资源优先级
后台生产环境使用 768 MB 内存和一个 CPU 核的参考上限,测试环境可以进一步收紧:
sudo install -d -m 0755 /etc/systemd/system/acme-backend@staging.service.d
sudoedit /etc/systemd/system/acme-backend@staging.service.d/limits.conf
[Service]
MemoryMax=512M
CPUQuota=50%
Nice=10
CPUQuota=50% 在 systemd 中通常表示最多使用半个 CPU 核的时间配额,而不是整台服务器的 50% 网络带宽。测试构建如果需要大量 CPU,建议安排在低峰期执行,或在构建机完成后只上传构建产物。
加载并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable --now acme-backend@admin.service
sudo systemctl enable --now acme-backend@staging.service
sudo systemctl status acme-backend@admin.service --no-pager
sudo systemctl status acme-backend@staging.service --no-pager
检查服务实际监听情况:
sudo ss -lntp | grep -E '127\.0\.0\.1:(4000|5000)'
正确结果应该只看到 127.0.0.1:4000 和 127.0.0.1:5000。如果看到 0.0.0.0:4000,说明应用没有按环境变量绑定本机地址,后台端口可能已经直接暴露在公网,需要先修改应用配置再继续。
3. 提供健康检查接口
后台和测试服务都应提供一个不读取敏感数据的健康检查接口,例如:
GET /healthz
接口可以只返回:
{"status":"ok"}
在服务器本机测试:
curl -i http://127.0.0.1:4000/healthz
curl -i http://127.0.0.1:5000/healthz
如果健康检查依赖数据库,应区分“进程存活”和“业务可用”两类接口,避免数据库短暂故障时造成 systemd 不断重启进程。一般可以让 /healthz 检查进程,另设内部监控接口检查数据库和关键依赖。
四、配置 Nginx 反向代理和访问边界
1. 安装 Nginx 与证书工具
sudo apt-get update
sudo apt-get install -y nginx certbot apache2-utils
如果 Node.js 尚未安装,应使用项目要求的长期支持版本,并记录安装方式和实际路径。不要在生产机上临时切换多个 Node.js 版本而不更新 systemd 配置。
为证书验证创建目录:
sudo install -d -m 0755 /var/www/acme/.well-known/acme-challenge
2. 先配置 HTTP 证书验证站点
证书尚未签发前,不要直接引用不存在的证书文件。先创建临时 HTTP 配置:
sudoedit /etc/nginx/conf.d/acme-http.conf
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com admin.example.com staging.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
return 404;
}
}
检查并重新加载:
sudo nginx -t
sudo systemctl reload nginx
如果 nginx -t 失败,不要执行 reload。先查看具体行号:
sudo nginx -T
sudo journalctl -u nginx -n 80 --no-pager
3. 申请证书并确认实际证书路径
确保 DNS 已经解析到这台服务器,且安全组和防火墙允许 TCP 80。然后申请包含四个域名的证书:
sudo certbot certonly \
--webroot \
-w /var/www/acme \
-d example.com \
-d www.example.com \
-d admin.example.com \
-d staging.example.com \
--email ops@example.com \
--agree-tos \
--no-eff-email
查看证书的实际 lineage 名称和到期时间:
sudo certbot certificates
如果 Certbot 输出的目录是 /etc/letsencrypt/live/example.com-001/,后续 Nginx 配置必须使用实际目录,不能机械套用 /etc/letsencrypt/live/example.com/。
4. 创建生产 Nginx 配置
证书签发成功后,将 HTTP 配置替换为包含 HTTPS 的配置。以下配置假设证书目录为 /etc/letsencrypt/live/example.com/,如果实际名称不同需调整。
sudoedit /etc/nginx/conf.d/acme.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream acme_admin {
server 127.0.0.1:4000;
keepalive 16;
}
upstream acme_staging {
server 127.0.0.1:5000;
keepalive 8;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com admin.example.com staging.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
root /srv/acme/web/current;
index index.html;
access_log /var/log/nginx/acme-web.access.log;
error_log /var/log/nginx/acme-web.error.log warn;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
try_files $uri $uri/ /index.html;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name admin.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20m;
access_log /var/log/nginx/acme-admin.access.log;
error_log /var/log/nginx/acme-admin.error.log warn;
location / {
proxy_pass http://acme_admin;
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-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name staging.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20m;
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd-ops;
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
access_log /var/log/nginx/acme-staging.access.log;
error_log /var/log/nginx/acme-staging.error.log warn;
location / {
proxy_pass http://acme_staging;
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-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
官网配置中的 try_files ... /index.html 适用于单页应用。如果官网是普通多页面静态站,应按实际站点改为正确的 404 处理方式,避免所有不存在的 URL 都返回首页。
client_max_body_size 20m 只是示例。后台上传文件时,应同时检查应用自身的上传限制、反向代理超时、磁盘容量和对象存储策略。不要仅仅把限制调大来解决上传失败,否则可能给单台服务器带来磁盘和内存压力。
5. 给后台和测试环境增加访问保护
测试环境建议始终保留额外认证。创建 Basic Auth 文件时,第一次使用 -c,后续添加用户不能再次使用 -c,否则会覆盖原文件:
sudo htpasswd -c /etc/nginx/.htpasswd-ops opsadmin
sudo htpasswd /etc/nginx/.htpasswd-ops qauser
sudo chown root:www-data /etc/nginx/.htpasswd-ops
sudo chmod 0640 /etc/nginx/.htpasswd-ops
Basic Auth 不是后台应用登录的替代品。生产后台仍应启用应用自身的强密码、多因素认证、会话过期和权限分级。如果团队有固定办公出口 IP,也可以在 admin.example.com 的 location 中增加 allow 和 deny,但在启用前要确认所有合法运维人员的出口地址,避免误封。
检查配置并加载:
sudo nginx -t
sudo systemctl reload nginx
五、配置防火墙、日志和证书续期
1. 只开放必要的公网入口
应用端口 4000 和 5000 已经绑定到回环地址,即使不配置防火墙也不应被外网直接访问。但仍建议在服务商安全组和系统防火墙同时限制,只保留 SSH、HTTP 和 HTTPS。
使用 UFW 前先备份配置,并通过服务商控制台或备用 SSH 会话操作。下面的 22 仅是示例,实际 SSH 端口不同必须替换:
sudo cp -a /etc/ufw /root/ufw-backup-$(date +%Y%m%d-%H%M%S)
sudo ufw status verbose
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo ufw status numbered
如果当前 SSH 端口不是 22,先放行真实端口,再执行 ufw enable。防火墙启用前要确认 ufw status numbered 中有正确的 SSH 规则。错误配置的影响范围是整台服务器,回滚应优先通过服务商控制台执行;紧急情况下可以临时执行:
sudo ufw disable
这会停用整套 UFW 规则,只适合作为排查或恢复连接的临时措施,之后要重新建立明确的入站规则。
2. 分离访问日志、错误日志和应用日志
Nginx 已按域名将访问日志分开,排查时可以快速判断请求进入了哪套配置:
sudo tail -f /var/log/nginx/acme-web.access.log
sudo tail -f /var/log/nginx/acme-admin.error.log
sudo tail -f /var/log/nginx/acme-staging.error.log
后台和测试进程输出由 journald 管理:
sudo journalctl -u acme-backend@admin.service -f
sudo journalctl -u acme-backend@staging.service -f
建议启用持久化 journald,并设置容量上限:
sudoedit /etc/systemd/journald.conf
确认或补充:
[Journal]
Storage=persistent
SystemMaxUse=1G
MaxRetentionSec=14day
然后重新加载:
sudo systemctl restart systemd-journald
日志不应包含密码、完整访问令牌、银行卡信息或未经脱敏的个人数据。访问日志长期增长会消耗磁盘,确认系统已经安装并启用了 Nginx 的 logrotate 配置:
sudo test -f /etc/logrotate.d/nginx && cat /etc/logrotate.d/nginx
sudo logrotate -d /etc/logrotate.d/nginx
logrotate -d 只进行模拟检查,不会实际轮换文件。
3. 验证证书自动续期
Certbot 通常会安装 systemd timer。检查定时任务和续期模拟:
sudo systemctl list-timers --all | grep -i certbot
sudo certbot renew --dry-run
续期成功后,Nginx 需要重新加载才能使用新证书。可以检查 Certbot 是否已配置 reload hook;如果没有,可增加:
sudo install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
sudoedit /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx
sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
脚本只执行 Nginx reload,不重启后台应用,因此证书更新不会主动中断后台进程。
六、备份和发布回滚设计
1. 备份哪些内容
至少需要区分四类数据:
| 数据类型 | 备份内容 | 备份建议 |
|---|---|---|
| 应用代码 | Git 仓库或构建产物 | 保留多个发布版本 |
| 配置和密钥 | /etc/nginx、/etc/acme、systemd 单元、证书私钥 | 加密后存放在服务器外 |
| 用户上传文件 | 官网、后台上传目录 | 与代码版本分开备份 |
| 数据库 | 生产库和测试库 | 生产库独立备份并定期恢复演练 |
同一台服务器上的 /root/backup 不是完整备份。磁盘损坏、误删、账号被入侵时,服务器内的副本可能同时失效。至少要保留一份异地或对象存储备份,并限制备份桶的写入和读取权限。
配置备份示例:
sudo install -d -m 0700 /root/acme-backups
sudo tar -czf \
/root/acme-backups/acme-config-$(date +%Y%m%d-%H%M%S).tar.gz \
/etc/nginx \
/etc/systemd/system/acme-backend@.service \
/etc/systemd/system/acme-backend@staging.service.d \
/etc/acme \
/etc/letsencrypt
该压缩包包含环境变量和证书私钥,必须保持 0700 目录权限,并在复制到外部存储后进行加密。备份数据库前应使用对应数据库的原生备份工具,并在生产迁移、删除数据或执行不可逆结构变更前完成一次可验证的备份。
2. 使用版本目录进行无覆盖发布
发布新后台版本时,推荐顺序如下:
sudo systemctl is-active --quiet acme-backend@admin.service
sudo -u acme-admin bash -lc '
cd /srv/acme/admin/releases/20250308-1300 &&
npm ci --omit=dev
'
sudo ln -sfn /srv/acme/admin/releases/20250308-1300 /srv/acme/admin/current
sudo systemctl restart acme-backend@admin.service
sudo systemctl status acme-backend@admin.service --no-pager
实际发布前应把新版本目录中的依赖安装、构建、配置检查和本地健康检查全部完成。符号链接切换只改变当前版本指向,不删除旧版本,因此代码层面的回滚速度较快。
数据库迁移是例外。若新代码要求数据库结构变化,不能认为切换回旧代码就一定能恢复。生产迁移应采用向前兼容的方式,例如先增加字段和索引,再发布兼容代码,最后在确认无旧版本依赖后清理旧结构。涉及删除列、改类型或批量修改数据时,应单独准备数据库备份和恢复方案。
七、结果验证
1. 验证进程、端口和资源限制
sudo systemctl is-enabled acme-backend@admin.service
sudo systemctl is-enabled acme-backend@staging.service
sudo systemctl is-active acme-backend@admin.service
sudo systemctl is-active acme-backend@staging.service
sudo ss -lntp | grep -E ':(4000|5000)\b'
sudo systemctl show acme-backend@admin.service \
-p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax
sudo systemctl show acme-backend@staging.service \
-p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax
预期结果包括:
- 后台服务和测试服务均为
active。 - 4000、5000 只监听
127.0.0.1,没有监听公网地址。 - 测试实例的内存和 CPU 上限低于后台生产实例。
- 应用异常退出后,systemd 状态能够恢复为
active,而不是持续进入failed。
2. 验证域名和 HTTPS
服务器公网地址用 SERVER_IP 表示:
dig +short A example.com
dig +short A www.example.com
dig +short A admin.example.com
dig +short A staging.example.com
curl -I --resolve www.example.com:443:SERVER_IP \
https://www.example.com/
curl -I --resolve admin.example.com:443:SERVER_IP \
https://admin.example.com/
curl -I --resolve staging.example.com:443:SERVER_IP \
https://staging.example.com/
正常情况下:
- 官网返回
200或符合预期的301。 - 后台如果启用了应用登录,可能返回
200、302或应用定义的未登录状态。 - 测试环境应至少返回 Basic Auth 的
401,未经认证不能直接看到页面。 - HTTP 访问应跳转到 HTTPS。
- 证书域名应与访问域名匹配,不出现证书过期或主机名不匹配。
如果 DNS 仍未生效,可以用 --resolve 验证服务器本身;如果 --resolve 成功但直接访问域名失败,问题通常在 DNS、AAAA 记录或外部安全组,而不是 Nginx。
3. 验证日志、上传和隔离效果
分别访问官网、后台和测试页面后检查:
sudo tail -n 20 /var/log/nginx/acme-web.access.log
sudo tail -n 20 /var/log/nginx/acme-admin.access.log
sudo tail -n 20 /var/log/nginx/acme-staging.access.log
sudo journalctl -u acme-backend@admin.service -n 50 --no-pager
sudo journalctl -u acme-backend@staging.service -n 50 --no-pager
再确认:
- 官网日志没有混入后台请求。
- 后台日志没有把测试域名当作生产域名。
- 测试环境不会读到生产数据库或生产密钥。
- 上传文件不会写入应用代码目录。
- 测试站点带有
X-Robots-Tag: noindex, nofollow, noarchive。 - 服务器磁盘仍有足够空间,日志和上传目录没有异常增长。
八、常见失败处理
1. 证书申请失败
按外到内的顺序检查:
dig +short A example.com
curl -i http://example.com/.well-known/acme-challenge/test
sudo ufw status verbose
sudo nginx -t
sudo ss -lntp | grep ':80'
不同结果对应的处理方向:
- DNS 不是当前服务器地址:先修正 A 或 AAAA 记录。
- HTTP 连接超时:检查服务商安全组、UFW 和 Nginx 是否监听 80。
- 返回 404:检查
/var/www/acme/.well-known/acme-challenge/路径、Nginxroot和try_files。 - 返回其他站点内容:检查是否有默认站点或重复的
server_name。 - IPv6 访问失败:修复 IPv6 路由和监听,或暂时删除错误的 AAAA 记录。
不要在证书尚未签发时把不存在的 privkey.pem 写入生产配置,也不要为了验证证书而关闭全部防火墙。
2. Nginx 返回 502
检查代理目标和服务状态:
sudo systemctl status acme-backend@admin.service --no-pager
sudo journalctl -u acme-backend@admin.service -n 100 --no-pager
sudo ss -lntp | grep ':4000'
curl -i http://127.0.0.1:4000/healthz
sudo tail -n 100 /var/log/nginx/acme-admin.error.log
常见原因包括:
- systemd 服务没有启动。
- 应用实际监听 3000,但 Nginx 配置为 4000。
- 应用只监听了 IPv6 或错误的网卡地址。
ExecStart中的 Node.js 路径不正确。- 应用启动后因环境变量或数据库连接失败而退出。
MemoryMax太低,进程被 cgroup 终止。
如果本机 curl 就失败,先修复应用;如果本机成功、域名访问 502,再检查 Nginx upstream 和配置加载状态。

3. 官网返回 403 或 404
检查当前版本链接、目录权限和文件名:
readlink -f /srv/acme/web/current
sudo namei -l /srv/acme/web/current/index.html
sudo test -r /srv/acme/web/current/index.html && echo readable
sudo nginx -T | grep -A20 -B5 'server_name example.com'
Nginx 运行用户需要能够遍历父目录并读取文件,但不应获得官网发布目录的写权限。如果前端是单页应用,确认是否需要 /index.html 回退;如果是普通静态站,改用实际的错误页和路径规则。
4. 测试环境占满资源
查看内存、磁盘和内核日志:
free -h
df -h
sudo journalctl -k -g 'out of memory|oom' --no-pager
sudo systemctl status acme-backend@staging.service --no-pager
sudo systemctl show acme-backend@staging.service -p MemoryCurrent -p CPUUsageNSec
如果测试构建或数据导入持续消耗资源,可以先停止测试服务,释放资源:
sudo systemctl stop acme-backend@staging.service
这只影响测试环境,不会停止官网和生产后台。之后应减少测试数据、降低构建并发、清理旧版本或扩容,而不是简单删除日志和盲目提高 MemoryMax。
5. 后台登录状态或真实 IP 不正确
确认应用信任反向代理的配置与部署框架一致。Nginx 已传递:
HostX-Real-IPX-Forwarded-ForX-Forwarded-Proto
应用必须正确识别 HTTPS,否则可能生成 http 回调地址、错误设置安全 Cookie,或者把所有请求记录为 127.0.0.1。不要直接让应用信任任意客户端提交的 X-Forwarded-For,应按框架文档配置可信代理范围。
九、回滚流程
1. 代码版本回滚
先记录当前版本和旧版本:
readlink -f /srv/acme/admin/current
ls -1dt /srv/acme/admin/releases/*
切换到已知可用版本:
sudo ln -sfn /srv/acme/admin/releases/20250308-1200 /srv/acme/admin/current
sudo systemctl restart acme-backend@admin.service
sudo systemctl status acme-backend@admin.service --no-pager
curl -i http://127.0.0.1:4000/healthz
官网也可以按同样方式切换:
sudo ln -sfn /srv/acme/web/releases/20250308-1100 /srv/acme/web/current
sudo nginx -t
sudo systemctl reload nginx
这类回滚只处理代码和静态文件。若新版本已经执行不可逆数据库迁移,必须同时执行经过验证的数据库恢复或兼容性处理,不能只切换符号链接。

2. Nginx 配置回滚
修改配置前先备份:
sudo cp -a /etc/nginx /root/nginx-backup-$(date +%Y%m%d-%H%M%S)
新配置测试失败时,不要 reload。已经加载但访问异常时,可以恢复备份中的配置,再检查:
sudo nginx -t
sudo systemctl reload nginx
如果证书续期后出现异常,先确认 Nginx 当前引用的证书路径和证书 lineage,不要删除 /etc/letsencrypt 下的历史文件。证书文件删除会影响所有引用该证书的域名,回滚应优先恢复配置指向仍然存在的有效证书。
3. 资源或安全策略回滚
如果 systemd 的资源上限导致应用启动失败,可先查看限制:
sudo systemctl show acme-backend@admin.service \
-p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax
临时回滚应修改对应 drop-in,然后重新加载和重启;不要直接删除整个 /etc/systemd/system 目录。对于防火墙,优先通过服务商控制台恢复备份规则,避免在无法确认 SSH 状态时执行大范围 reset。
十、上线和验收清单
上线前逐项确认:
- [ ]
example.com、www、admin、staging的 DNS 指向正确。 - [ ] 未配置或已正确配置 IPv6 AAAA 记录。
- [ ] SSH 密钥登录和备用控制台均可用。
- [ ] 服务器只计划开放 SSH、80、443。
- [ ] 官网、后台、测试使用不同目录和不同运行用户。
- [ ] 后台监听
127.0.0.1:4000,测试监听127.0.0.1:5000。 - [ ] 测试环境没有生产数据库连接、生产密钥和生产回调地址。
- [ ] Nginx 配置通过
nginx -t。 - [ ] HTTP 能跳转到 HTTPS,证书覆盖全部实际访问域名。
- [ ] 后台启用了应用登录保护,测试环境额外启用了 Basic Auth 或等效访问控制。
- [ ] 测试环境返回
X-Robots-Tag: noindex。 - [ ] 官网、后台、测试拥有独立访问日志和错误日志。
- [ ] systemd 能够自动重启异常退出的应用。
- [ ] 测试环境设置了较低的 CPU、内存和任务数上限。
- [ ] 日志保留周期和磁盘上限已配置。
- [ ] 证书续期
certbot renew --dry-run通过。 - [ ] 应用代码、配置、上传文件和数据库均有服务器外备份。
- [ ] 已记录当前版本、上一版本和数据库迁移状态。
- [ ] 至少完成一次官网访问、后台登录、测试认证、上传和健康检查验证。
- [ ] 已明确代码回滚、配置回滚、防火墙恢复和数据库恢复的执行人及顺序。
达到以上状态后,一台香港服务器可以在不把三个环境混成一套服务的前提下,承担初创阶段的官网、后台和测试部署。关键不在于把更多应用塞进同一台机器,而在于让域名、端口、用户、进程、配置、日志、数据库凭据和资源上限分别形成可检查、可验证、可回滚的边界。
