把外贸网站服务器投入生产环境前,反向代理、证书与备份应如何配置
面向维护外贸网站服务器的站长与技术负责人,本文按上线流程讲解Nginx反向代理、systemd进程守护、HTTPS证书自动续期、日志关联、异地备份与资源限制,并提供配置验证、恢复测试及故障回滚要点。

投入生产环境后的目标状态应当是:应用进程只监听本机或内网地址,由 Nginx 统一接收公网请求;HTTPS 证书可以自动续期;应用由 systemd 等服务管理器守护;代理日志与应用日志能够关联;数据库、上传文件和配置有独立于服务器的备份,并且已经验证可以恢复。任何一项缺失,都可能让外贸网站服务器在流量进入后出现无法续证、进程退出、磁盘写满或备份不可用等问题。
以下操作以 Ubuntu 22.04/24.04、Debian 12、Nginx 和 systemd 为例,假设应用监听 127.0.0.1:3000。执行前需要准备已解析的域名、服务器管理权限、可用的 80/443 端口和一个短时维护窗口。其他发行版的包管理器、Nginx配置目录和服务名称可能不同,应先核对,不能直接照搬。
先确认现状和变更影响范围
不要从安装证书或修改 Nginx 开始。先确认请求现在经过哪些组件,以及应用是否能绕过反向代理直接被公网访问。
cat /etc/os-release
nginx -v
systemctl --version | head -n 1
sudo nginx -T
sudo ss -lntp
systemctl list-units --type=service --state=running
df -h
free -h
重点核对以下事项:
- 域名的 A、AAAA 记录是否都指向当前入口。若存在错误的 AAAA 记录,ACME 验证可能访问到另一台服务器。
- 80 和 443 端口前是否还有 CDN、负载均衡器或云防火墙。
- 应用是否只监听
127.0.0.1。如果监听0.0.0.0:3000且端口对公网开放,用户可能绕过 Nginx、证书和访问控制。 - Nginx 是否已有同名
server_name,避免新旧虚拟主机相互冲突。 - 数据库和上传目录在哪里,是否存在依赖本机磁盘的持久化数据。
- 当前应用如何启动。由终端、
nohup或临时脚本启动的进程,不适合作为生产环境的长期运行方式。
如果入口前还有 CDN 或负载均衡器,Nginx 看到的 $remote_addr 通常是上游节点地址。此时需要按照上游公开的地址范围配置 Real IP 模块,不能为了获取客户端 IP 而信任任意来源提交的 X-Forwarded-For。
在改动前留下可回退副本
配置备份和业务备份不是一回事。前者用于快速撤销本次变更,后者用于服务器损坏或数据误操作后的恢复,两者都需要。
下面的命令会把 Nginx、systemd 自定义服务和证书目录保存到仅 root 可读的位置。证书目录包含私钥,不能放到公开目录或未经加密的共享存储中。
CHANGE_ID=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 0700 /root/change-backups
sudo tar --acls --xattrs -czf \
"/root/change-backups/nginx-systemd-${CHANGE_ID}.tar.gz" \
/etc/nginx /etc/systemd/system
if [ -d /etc/letsencrypt ]; then
sudo tar --acls --xattrs -czf \
"/root/change-backups/letsencrypt-${CHANGE_ID}.tar.gz" \
/etc/letsencrypt
fi
再导出当前生效配置,便于比较变更前后的差异:
sudo nginx -T > "/tmp/nginx-before-${CHANGE_ID}.txt" 2>&1
sudo systemctl cat site-app.service > "/tmp/site-app-before-${CHANGE_ID}.txt" 2>&1 || true
如果部署同时包含数据库迁移,必须先确认迁移是否可逆。仅回滚应用代码而数据库结构无法回退,通常不能恢复到原来的运行状态。
先让应用具备独立运行能力
反向代理只能转发请求,不能代替应用进程守护。Node.js、Python 或自定义二进制程序可以使用 systemd;PHP 应用通常应管理对应的 PHP-FPM 服务,而不是额外启动一个长期 PHP 进程。
下面是 Node.js 应用的 systemd 示例。User、运行路径和 ExecStart 必须根据实际环境修改,可以先用 command -v node 核对可执行文件位置。
[Unit]
Description=Website Application
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=siteapp
Group=siteapp
WorkingDirectory=/srv/site/current
EnvironmentFile=-/etc/site-app.env
ExecStart=/usr/bin/node /srv/site/current/server.js
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
KillSignal=SIGTERM
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/srv/site/shared
[Install]
WantedBy=multi-user.target
将其保存为 /etc/systemd/system/site-app.service 后,先检查环境变量文件和目录权限。应用不应以 root 身份运行,敏感环境变量文件建议设置为仅管理员和服务账号可读。
sudo systemctl daemon-reload
sudo systemctl enable --now site-app.service
sudo systemctl status site-app.service
sudo journalctl -u site-app.service -n 100 --no-pager
不要看到 active (running) 就认为应用正常,还应直接访问本机监听端口:
curl -sS -o /dev/null \
-w 'HTTP %{http_code} time=%{time_total}s\n' \
http://127.0.0.1:3000/
如果应用提供专用健康检查接口,应优先检查该接口。连接被拒绝通常意味着进程未监听、端口不一致或服务启动失败;返回 500 则应继续查看应用日志和数据库连接状态。
分两阶段启用 Nginx 反向代理
证书签发依赖域名验证,因此更稳妥的顺序是先启用 HTTP 和 ACME 验证目录,确认域名请求能够到达当前服务器,再申请证书并启用 HTTPS。
先建立验证目录:
sudo install -d -m 0755 /var/www/acme/.well-known/acme-challenge
在 Ubuntu、Debian 的标准 Nginx 目录中,可以创建 /etc/nginx/sites-available/site.conf。先使用以下 HTTP 配置,并把示例域名替换为真实域名:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
access_log /var/log/nginx/site.access.log;
error_log /var/log/nginx/site.error.log warn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
proxy_pass http://127.0.0.1:3000;
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;
proxy_set_header X-Request-ID $request_id;
add_header X-Request-ID $request_id always;
}
}
启用站点前,先检查是否已经存在同名配置:
sudo grep -R "server_name.*example.com" /etc/nginx
sudo ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/site.conf
sudo nginx -t
如果符号链接已经存在,不要强制覆盖,应先确认它指向哪个文件。只有 nginx -t 成功后才能平滑加载:
sudo systemctl reload nginx
创建临时验证文件并从域名访问:
echo "acme-check" | sudo tee \
/var/www/acme/.well-known/acme-challenge/check.txt >/dev/null
curl -i http://example.com/.well-known/acme-challenge/check.txt
应返回 200 和 acme-check。如果返回 404,需要检查请求是否进入了另一个虚拟主机;超时则应检查 DNS、入口防火墙和上游负载均衡器。
申请证书并切换到 HTTPS
在上述系统中,可以使用 Certbot 的 Webroot 模式申请证书。它不会为了签发证书而接管应用端口,也便于保留明确的 Nginx 配置。
sudo apt update
sudo apt install certbot
sudo certbot certonly \
--webroot \
-w /var/www/acme \
-d example.com \
-d www.example.com
签发成功后,将站点配置调整为 HTTP 跳转和 HTTPS 代理。不要在证书文件尚未生成时提前引用相应路径,否则 Nginx 配置检查会失败。
server {
listen 80;
listen [::]:80;
server_name example.com www.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;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
access_log /var/log/nginx/site.access.log;
error_log /var/log/nginx/site.error.log warn;
location / {
proxy_pass http://127.0.0.1:3000;
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 https;
proxy_set_header X-Request-ID $request_id;
add_header X-Request-ID $request_id always;
}
}
再次执行检查和加载:
sudo nginx -t
sudo systemctl reload nginx
如果应用包含 WebSocket,需要额外配置 Upgrade 和 Connection 请求头;如果有大文件上传或长时间任务,还要根据业务实际调整 client_max_body_size、proxy_read_timeout 等参数。不能为了避免 413 或超时而无限放大这些值,否则会增加内存、连接和慢请求占用。
证书自动续期必须单独验证:
systemctl list-timers --all | grep -i certbot
sudo certbot renew --dry-run
可以在 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx 中设置续期后的安全加载脚本:
#!/bin/sh
/usr/sbin/nginx -t && /bin/systemctl reload nginx
创建前应确认同名文件不存在,并将脚本设置为可执行。不要使用无条件重启 Nginx 的续期钩子,否则错误配置可能导致服务中断。
sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
HSTS 不建议在首次切换 HTTPS 时立即加入。只有确认所有子域、第三方回调和历史链接都支持 HTTPS 后再启用,尤其不要贸然使用 includeSubDomains。
让代理日志能够回答“慢在哪里”
只记录 HTTP 状态码不足以区分 Nginx 慢、应用慢还是上游连接失败。可以在 /etc/nginx/conf.d/site-log-format.conf 中增加包含请求时间和上游时间的日志格式:
log_format site_main
'$remote_addr [$time_local] "$request_method $uri $server_protocol" '
'status=$status bytes=$body_bytes_sent '
'request_time=$request_time upstream_time=$upstream_response_time '
'upstream_addr=$upstream_addr request_id=$request_id '
'referer="$http_referer" agent="$http_user_agent"';
然后把 HTTPS 虚拟主机中的访问日志改为:
access_log /var/log/nginx/site.access.log site_main;
执行 sudo nginx -t 后再 reload。应用也应记录同一个 X-Request-ID,这样可以从 Nginx 的一条 502 或慢请求继续定位到应用日志。
常用观察命令如下:
sudo tail -f /var/log/nginx/site.access.log
sudo tail -f /var/log/nginx/site.error.log
sudo journalctl -u site-app.service -f
sudo logrotate -d /etc/logrotate.d/nginx
logrotate -d 只用于调试轮转规则,不会真正轮转。还应检查 journald 和 Nginx 日志的磁盘上限,避免访问量增长后写满系统盘。日志中不要记录密码、完整令牌、支付参数或不必要的查询字符串。
备份必须覆盖数据、配置和恢复链路
生产备份不能只复制网站代码。代码通常可以重新发布,真正难以重建的是数据库、用户上传文件、运行配置和密钥。
| 备份对象 | 推荐方式 | 关键检查 |
|---|---|---|
| 数据库 | 数据库原生逻辑备份或一致性物理备份 | 能否在隔离环境中恢复,事务和字符集是否正确 |
| 上传文件 | 文件级备份、对象存储复制或一致性快照 | 数据库记录与文件版本是否对应 |
| Nginx、systemd 配置 | 版本管理加加密备份 | 路径、权限和服务账号是否保留 |
| 应用密钥与环境变量 | 加密备份并限制访问身份 | 是否具备轮换和撤销机制 |
| ACME 证书目录 | 保留权限的加密备份 | 私钥不得进入公开仓库 |
| 备份清单与校验值 | 随备份保存 | 能否发现传输或存储损坏 |
以 PostgreSQL 为例,可以在维护窗口执行一次逻辑备份。以下数据库名和目录需要按实际环境修改:
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M%S)
DEST="/var/backups/site/${STAMP}"
sudo install -d -m 0700 "$DEST"
sudo -u postgres pg_dump -Fc shopdb \
| sudo tee "${DEST}/shopdb.dump" >/dev/null
sudo tar --acls --xattrs -czf "${DEST}/uploads.tar.gz" \
-C /srv/site/shared uploads
sudo sha256sum "${DEST}/shopdb.dump" "${DEST}/uploads.tar.gz" \
| sudo tee "${DEST}/SHA256SUMS" >/dev/null
MySQL 或 MariaDB 应使用相应的 mysqldump、mariadb-dump 或物理备份工具,认证信息不要直接写在命令行参数中。--single-transaction 只适合支持事务一致性的表,不能把它视为所有存储引擎的通用保证。
本地 /var/backups 只能作为临时落地点。完整备份还应由独立凭据传输到服务器之外,并设置加密、保留周期和失败告警。服务器磁盘损坏、账号被入侵或误删除时,本机备份往往会一起丢失。
恢复验证应在隔离环境中进行,至少确认:
- 压缩包和校验值可以读取。
- 数据库备份能够导入空数据库。
- 上传目录的权限、属主和符号链接正确。
- 恢复后的应用能读取数据库和文件。
- 备份所需时间、恢复顺序和负责人员已经记录。
不要用“备份任务显示成功”代替恢复测试。
给进程、磁盘和请求设置资源边界
资源限制不是数值越小越安全。设置前应先观察应用在正常流量、发布和备份期间的占用,再为操作系统、Nginx、数据库和恢复任务保留空间。
systemd 常见边界包括:
MemoryHigh:接近该值时开始施加内存压力,适合作为预警边界。MemoryMax:硬性上限,超过后服务可能被终止,设置过低会直接造成请求中断。TasksMax:限制线程和子进程数量,需要考虑应用 worker 模型。CPUQuota:限制 CPU 使用比例,过紧会把短时高峰变成长时间排队。LimitNOFILE:限制文件描述符,必须与应用连接池和 Nginx 并发设置配合。
可分配给应用的内存上限,应从总内存中扣除操作系统、Nginx、数据库、备份峰值和安全余量,而不是复制其他服务器的固定数值。修改限制后执行:
sudo systemctl daemon-reload
sudo systemctl restart site-app.service
sudo systemctl show site-app.service \
-p MemoryCurrent -p MemoryHigh -p MemoryMax -p TasksCurrent -p TasksMax
重启会中断当前进程,应在维护窗口执行。磁盘方面至少监控系统盘、数据库目录、上传目录和日志目录,备份任务也不能把源服务器磁盘占满。
上线验证与回滚触发条件
切换完成后,应从本机代理、外部域名、证书链和应用日志四个层面检查,而不是只打开一次首页。
curl -I http://example.com/
curl -sS -o /dev/null \
-w 'HTTP %{http_code} time=%{time_total}s\n' \
https://example.com/
openssl s_client \
-connect example.com:443 \
-servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
sudo nginx -t
sudo systemctl is-active nginx
sudo systemctl is-active site-app.service
sudo journalctl -u site-app.service -S -30min --no-pager
还要实际验证登录、表单提交、文件上传、后台任务、邮件回调和第三方接口。涉及写操作时使用测试账号,避免污染正式订单和客户数据。
出现以下情况时,不应继续叠加修改,而应暂停变更并评估回滚:
- 502、504 或应用 5xx 持续出现。
- 登录状态、回调地址或 HTTPS 跳转异常。
- 证书域名不匹配、证书链不完整或续期演练失败。
- 应用进程反复重启,内存、任务数或磁盘持续逼近上限。
- 新配置导致上传、WebSocket、长请求等原有功能失效。
- 备份任务失败,或者新版本包含不可逆的数据迁移。
回滚 Nginx 时,先恢复变更前的配置文件,再执行 sudo nginx -t;只有检查通过后才能 reload。回滚 systemd 单元后需要 daemon-reload,重启应用前应评估连接中断。若涉及数据库迁移,必须按照已验证的迁移回退方案处理,不能直接用旧程序连接不兼容的新结构。
生产变更后的观察期至少应覆盖一次业务高峰、一次证书续期演练和一次完整备份任务。只有代理错误率、应用日志、资源占用和备份结果都处于可解释状态,外贸网站服务器才算完成了从“能够访问”到“可以持续运行和恢复”的切换。