电商网站上线到香港Linux服务器前,如何配置反向代理、HTTPS与备份?
在香港服务器的 Linux 系统上部署电商网站,生产环境建议采用“公网只开放 80/443、Nginx 接收请求、应用仅监听本机端口、systemd 负责进程守护、数据库与文件定时备份”的结构。这样可以把 HTTPS、访问日志、请求超时、应用重启和故障回滚分别管理,后续排查不会只依赖一个前台进程。
上线前需要准备域名及 DNS 解析、可用的 SSH 管理账号、应用程序和依赖、数据库账号、HTTPS 证书申请邮箱,以及服务器控制台或带外登录方式。下面以 Debian/Ubuntu 系统、Nginx、systemd、MySQL/MariaDB 和应用监听 127.0.0.1:3000 为例;不同发行版的安装命令可调整,但配置关系和验证顺序保持一致。
一、先确定生产环境边界
1. 目标架构
请求链路应当是:
浏览器
│ HTTPS :443
▼
Nginx
│ 反向代理
▼
127.0.0.1:3000 应用进程
│
├── MySQL/MariaDB
└── /srv/shop/shared/uploads
应用端口不直接暴露到公网,数据库也不应接受任意公网连接。Nginx 负责 TLS 证书、HTTP 到 HTTPS 跳转、请求头传递和访问日志;systemd 负责应用异常退出后的自动拉起;备份任务同时保存数据库和用户上传文件。
单台服务器部署时,可以先使用以下参考边界,再根据监控调整:
| 项目 | 初始参考值 | 判断依据 |
|---|---|---|
| 应用进程 | 1 个主进程或 1 组经过测试的 Worker | 先避免与数据库争抢内存 |
| 上传大小 | 20 MB 左右 | 应与商品图片、后台导入文件需求一致 |
| 反向代理超时 | 60 秒左右 | 普通页面不应长期占用连接 |
| 磁盘使用率 | 低于 70% 更稳妥,超过 80% 需处理 | 日志、图片和备份会持续增长 |
| 数据库连接池 | 10~30 个起步 | 不应按每个请求无限创建连接 |
| 本地备份保留 | 7~14 个版本 | 仍需复制到独立存储位置 |
这些是配置起点,不是服务器性能承诺。电商网站的订单、库存和支付回调应优先保证数据一致性,不要通过无限增大超时、连接数或 Worker 数量掩盖资源不足。
2. 检查 DNS、系统和资源
先在服务器外部或本地终端检查域名是否已经指向服务器公网地址:
dig +short A shop.example.com
dig +short AAAA shop.example.com
如果存在错误的 AAAA 记录,部分客户端可能优先通过 IPv6 访问,导致看似随机的打不开问题。应修正 IPv6 地址,或者在未配置 IPv6 服务时删除错误记录。
登录服务器后检查系统、内存、磁盘和当前监听端口:
cat /etc/os-release
uname -m
nproc
free -h
df -h
sudo ss -lntp
至少确认以下条件:
- 系统仍有足够的可用磁盘空间,应用目录、上传目录和备份目录不要放在已接近满载的分区。
- 服务器可以通过 SSH 登录,且已准备非 root 管理账号。
- 应用能在本机单独启动,并提供可检查的
/health或/页面。 - 数据库名称、用户名、密码和字符集已经确认。
- 已保存当前 Nginx 配置和应用版本。涉及覆盖配置、防火墙和数据库的操作,都应保留回滚入口。
安装本示例所需的软件:
sudo apt update
sudo apt install -y nginx curl ca-certificates certbot mariadb-client
如果应用依赖 Node.js、PHP 或其他运行时,应使用项目要求的版本,并通过以下命令确认实际路径,而不是直接猜测:
command -v node
node --version
command -v php
php --version
3. 配置防火墙时先保留 SSH 通道
下面以 UFW 为例。执行前确认 SSH 端口;如果 SSH 使用的不是 22 端口,应替换为实际端口。防火墙调整错误可能导致当前会话断开,因此应先在服务器控制台保留一个备用登录通道。
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
sudo ufw enable
如果启用后发现 SSH 无法连接,应通过服务器控制台执行回滚:
sudo ufw disable
应用的 3000 端口和数据库端口不应加入公网放行规则。它们只需要在本机通信。
二、让应用由 systemd 管理
1. 创建运行用户和目录
不要让电商应用长期以 root 身份运行。下面的目录结构将发布版本、共享上传文件和配置分开:
sudo useradd --system --home /srv/shop --shell /usr/sbin/nologin shop
sudo install -d -o shop -g shop -m 750 /srv/shop/releases
sudo install -d -o shop -g shop -m 750 /srv/shop/shared/uploads
sudo install -d -o root -g shop -m 750 /etc/shop
将已构建的应用放入类似以下目录:
/srv/shop/releases/20261003-01/
/srv/shop/shared/uploads/
/srv/shop/current -> /srv/shop/releases/20261003-01
敏感配置单独保存:
sudo touch /etc/shop/shop.env
sudo chown root:shop /etc/shop/shop.env
sudo chmod 640 /etc/shop/shop.env
示例内容如下,密码、密钥和数据库地址应替换为实际值:
NODE_ENV=production
PORT=3000
HOST=127.0.0.1
DATABASE_URL=mysql://shop_app:REPLACE_PASSWORD@127.0.0.1:3306/shop
SESSION_COOKIE_SECURE=true
应用应明确监听 127.0.0.1:3000,不要监听 0.0.0.0:3000。如果应用必须使用其他端口,应同步修改 systemd 和 Nginx 配置。
2. 创建 systemd 服务
下面以 Node.js 应用的 server.js 为例。执行 command -v node 后,将 /usr/bin/node 替换成实际路径。
# /etc/systemd/system/shop.service
[Unit]
Description=Shop production application
After=network.target
[Service]
Type=simple
User=shop
Group=shop
WorkingDirectory=/srv/shop/current
EnvironmentFile=/etc/shop/shop.env
ExecStart=/usr/bin/node /srv/shop/current/server.js
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
KillSignal=SIGTERM
NoNewPrivileges=true
PrivateTmp=true
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
加载并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable shop
sudo systemctl start shop
sudo systemctl status shop --no-pager
检查应用是否只在本机端口监听:
sudo ss -lntp | grep ':3000'
curl -i http://127.0.0.1:3000/health
如果没有 /health 路径,可以改为应用实际存在的页面。成功时应看到 HTTP 状态码和应用响应,而不是连接拒绝。
查看启动失败原因:
sudo journalctl -u shop -n 100 --no-pager
sudo journalctl -u shop -f
常见原因包括运行时路径错误、环境变量未加载、目录权限不足、数据库连接失败或应用实际监听了其他端口。先修正这些问题,再进入 Nginx 配置;Nginx 不能替代应用本身的启动检查。
三、配置 Nginx 反向代理
1. 先建立 HTTP 配置并验证
创建证书验证目录:
sudo install -d -m 755 /var/www/certbot
创建站点配置。将 shop.example.com 和 www.shop.example.com 换成实际域名:
# /etc/nginx/sites-available/shop.conf
upstream shop_app {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name shop.example.com www.shop.example.com;
access_log /var/log/nginx/shop.access.log;
error_log /var/log/nginx/shop.error.log warn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
启用站点。如果目标链接已经存在,不要直接覆盖一个未知配置;先检查其内容并备份:
sudo ls -l /etc/nginx/sites-enabled/
sudo cp -a /etc/nginx /etc/nginx.backup.$(date +%Y%m%d-%H%M%S)
sudo ln -s /etc/nginx/sites-available/shop.conf /etc/nginx/sites-enabled/shop.conf
如果系统默认站点占用了相同域名或默认响应,可先将其改名保存,而不是删除:
if [ -e /etc/nginx/sites-enabled/default ]; then
sudo mv /etc/nginx/sites-enabled/default \
/etc/nginx/sites-enabled/default.disabled
fi
检查并重新加载:
sudo nginx -t
sudo systemctl reload nginx
nginx -t 失败时不要 reload。先根据错误行号修正配置,或者恢复刚才的备份。
2. 申请 HTTPS 证书
证书申请要求域名已经解析到当前服务器,并且外部可以访问 TCP 80 端口。先申请证书:
sudo certbot certonly \
--webroot \
-w /var/www/certbot \
-d shop.example.com \
-d www.shop.example.com \
--email admin@example.com \
--agree-tos \
--no-eff-email
如果申请失败,优先检查:
curl -I http://shop.example.com/.well-known/acme-challenge/test
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log
sudo ss -lntp | grep ':80'
HTTP 80 端口被防火墙拦截、DNS 指向旧地址、域名包含错误的 IPv6 解析,都会导致验证失败。
3. 切换到 HTTPS 反向代理
证书申请成功后,将站点配置替换为以下内容:
# /etc/nginx/sites-available/shop.conf
upstream shop_app {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name shop.example.com www.shop.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
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 shop.example.com www.shop.example.com;
ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
access_log /var/log/nginx/shop.access.log;
error_log /var/log/nginx/shop.error.log warn;
client_max_body_size 20m;
location / {
proxy_pass http://shop_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
这里使用 $remote_addr 设置 X-Forwarded-For,适合公网请求直接进入这台 Nginx 的结构,避免客户端自行提交的旧请求头被无条件信任。如果以后在 Nginx 前增加了受信任的入口层,应重新设计可信代理范围,而不是直接复制配置。
应用框架也要正确识别反向代理传递的协议。如果应用错误地认为请求仍是 HTTP,可能出现登录 Cookie 不安全、后台反复跳转或支付回调地址错误。框架中应只信任这一层 Nginx,不要对所有来源启用无限制的代理信任。
配置完成后执行:
sudo nginx -t
sudo systemctl reload nginx
不要一开始就启用很长时间的 HSTS。确认 HTTPS、登录、购物车、后台和回调地址全部正常后,再逐步增加:
add_header Strict-Transport-Security "max-age=86400" always;
如果 HTTPS 配置尚未稳定,过早启用 HSTS 会让浏览器持续强制访问 HTTPS,增加排障难度。
4. 设置证书续期和 Nginx reload
先查看系统是否已有 Certbot 定时器:
systemctl list-timers --all | grep -i certbot
增加证书更新后的 Nginx reload 钩子:
sudo install -d -m 755 /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'
#!/bin/sh
/usr/sbin/nginx -t && /bin/systemctl reload nginx
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
用模拟续期验证,不会真正替换生产证书:
sudo certbot renew --dry-run
四、日志、进程和资源边界
1. 观察请求经过了哪一层
应用和 Nginx 的日志应分开查看:
sudo tail -f /var/log/nginx/shop.access.log
sudo tail -f /var/log/nginx/shop.error.log
sudo journalctl -u shop -f
可以通过下面的顺序快速定位:
- Nginx access log 没有请求:先查 DNS、防火墙、监听端口或前置入口。
- Nginx 有请求但返回
502:应用没有运行、监听端口不对,或 Nginx 无法连接127.0.0.1:3000。 - 返回
504:应用连接成功但处理超过proxy_read_timeout,应检查数据库、外部接口和慢查询,不要只把超时改成几分钟。 - 返回
413:请求体超过client_max_body_size,应确认商品图片或导入文件需求,再同步调整应用层限制。 - Nginx 返回
200但页面错误:继续查看应用 journal,问题通常已进入应用或数据库层。
2. 控制内存、连接和磁盘增长
先观察一段时间:
free -h
df -h
uptime
sudo systemctl --failed
sudo journalctl --disk-usage
建议保留以下边界:
- 应用进程数量先从少量开始,确认内存余量后再增加 Worker。
- 数据库连接池设置固定上限,不要让每个请求创建新连接。
- 上传目录和日志目录纳入磁盘监控,磁盘接近 80% 时立即处理。
- Nginx 日志交由系统的 logrotate 管理,确认轮转配置没有被自定义站点覆盖。
- 运行时间较长的报表、批量导入和图片处理尽量改为后台任务,避免占用结算请求。
如果应用需要写入缓存、上传文件或临时文件,应只开放对应目录权限,不要把整个 /srv/shop 改成全局可写。
五、配置数据库和文件备份
1. 设置数据库导出凭据
不要把数据库密码直接写进命令行,因为它可能出现在 shell 历史或进程列表中。以 MySQL/MariaDB 客户端为例,创建 root 仅可读的客户端配置:
sudo tee /root/.my.cnf > /dev/null <<'EOF'
[client]
host=127.0.0.1
user=shop_backup
password=REPLACE_WITH_BACKUP_PASSWORD
EOF
sudo chmod 600 /root/.my.cnf
shop_backup 应使用仅能读取业务数据库的专用账号,不建议直接复用应用账号或数据库管理员账号。
2. 创建备份脚本
以下脚本同时备份数据库、应用发布文件、上传文件和关键配置。备份文件含有业务数据和敏感配置,脚本使用 umask 077 限制权限。
sudo tee /usr/local/sbin/shop-backup > /dev/null <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BACKUP_DIR="/var/backups/shop"
DB_NAME="shop"
TS="$(date +%Y%m%d-%H%M%S)"
DUMP_BIN="$(command -v mariadb-dump || command -v mysqldump)"
install -d -m 700 "$BACKUP_DIR"
"$DUMP_BIN" \
--defaults-extra-file=/root/.my.cnf \
--single-transaction \
--routines \
--events \
--triggers \
"$DB_NAME" | gzip -9 > "$BACKUP_DIR/db-$TS.sql.gz"
tar -czf "$BACKUP_DIR/files-$TS.tar.gz" \
-C /srv/shop releases shared/uploads \
-C /etc shop \
-C /etc/nginx/sites-available shop.conf
gzip -t "$BACKUP_DIR/db-$TS.sql.gz"
tar -tzf "$BACKUP_DIR/files-$TS.tar.gz" >/dev/null
find "$BACKUP_DIR" -type f -mtime +14 -delete
EOF
sudo chmod 700 /usr/local/sbin/shop-backup
执行一次人工测试:
sudo /usr/local/sbin/shop-backup
sudo ls -lh /var/backups/shop
如果备份目录与应用放在同一块磁盘上,它只能防止误删、错误发布和部分数据损坏,不能防止服务器磁盘整体故障。生产环境还应将备份复制到独立的备份存储,并使用加密传输或加密仓库。复制完成后再检查独立存储上的文件是否可读,不要只检查本机脚本返回成功。

3. 使用 systemd timer 定时执行
创建一次性服务和每天执行的定时器:
# /etc/systemd/system/shop-backup.service
[Unit]
Description=Shop database and files backup
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/shop-backup
# /etc/systemd/system/shop-backup.timer
[Unit]
Description=Daily shop backup timer
[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=15m
[Install]
WantedBy=timers.target
启用并检查:
sudo systemctl daemon-reload
sudo systemctl enable --now shop-backup.timer
sudo systemctl list-timers shop-backup.timer
手动执行服务并查看日志:
sudo systemctl start shop-backup.service
sudo journalctl -u shop-backup.service -n 100 --no-pager
4. 验证备份能否恢复
只看到 .sql.gz 文件并不代表备份可用。应定期在临时数据库中验证,禁止直接覆盖生产数据库。
示例流程如下:
gunzip -c /var/backups/shop/db-YYYYMMDD-HHMMSS.sql.gz \
| mysql --defaults-extra-file=/root/.my.cnf shop_restore
执行前应确认 shop_restore 是专门的临时数据库,且不会指向生产库。恢复测试完成后,再由管理员明确检查数据库名称后删除测试库;不要把删除命令直接用于生产数据库。
文件备份可先列出目录:
tar -tzf /var/backups/shop/files-YYYYMMDD-HHMMSS.tar.gz | head -n 30
如果业务要求订单最多只能丢失几分钟或几小时,每日导出并不满足这个目标,还需要规划数据库二进制日志保留和经过演练的时间点恢复流程。
六、上线验证顺序
配置完成后,建议从外到内验证,不要只打开首页看一眼:
curl -I http://shop.example.com
curl -I https://shop.example.com
curl -sS https://shop.example.com/health
预期结果通常是:
- HTTP 返回
301,目标为 HTTPS。 - HTTPS 返回业务正常状态码。
- 证书域名覆盖实际访问域名,浏览器没有证书警告。
- 应用进程处于
active状态。 - 公网只能看到 80/443,应用端口仍为本机监听。
进一步检查证书和服务状态:
echo | openssl s_client \
-connect shop.example.com:443 \
-servername shop.example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
sudo systemctl is-active nginx
sudo systemctl is-active shop
sudo systemctl is-active shop-backup.timer
业务验证至少覆盖:
- 首页、商品详情、搜索和静态图片。
- 注册或登录、Session Cookie 和退出登录。
- 加入购物车、修改数量、提交订单。
- 后台登录、商品图片上传和订单状态更新。
- 支付或第三方回调使用 HTTPS 地址时,应用能够正确识别原始协议。
- 应用重启后仍能自动拉起,备份任务可以手动执行并留下日志。
七、失败处理与回滚
1. Nginx 配置失败
如果 nginx -t 报错,先不要 reload:
sudo nginx -t
sudo cp -a /etc/nginx.backup.YYYYMMDD-HHMMSS/* /etc/nginx/
sudo nginx -t
sudo systemctl reload nginx
备份目录名称应替换为实际创建的目录。恢复前应确认备份确实属于本次变更之前的版本,避免把更早的配置覆盖回来。
2. 应用发布后出现 502 或 500
先查看应用日志和监听端口:
sudo systemctl status shop --no-pager
sudo journalctl -u shop -n 150 --no-pager
sudo ss -lntp | grep ':3000'
如果新版本无法启动,应切回上一个发布目录。切换前保留当前目录和日志,便于分析:
sudo systemctl stop shop
sudo ln -sfn /srv/shop/releases/20261002-02 /srv/shop/current
sudo systemctl start shop
sudo systemctl status shop --no-pager
sudo curl -i http://127.0.0.1:3000/health
只有确认旧版本可用后,再 reload Nginx。使用软链接切换发布版本的好处是回滚范围清晰,不需要在故障时重新上传整套代码。
3. HTTPS 或证书续期失败
不要删除现有证书目录,也不要在业务高峰期反复修改 TLS 配置。先检查:
sudo certbot certificates
sudo certbot renew --dry-run
sudo nginx -t
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log
如果只是续期后的 reload 失败,应修复 Nginx 配置后再 reload;旧证书在有效期内通常仍可继续提供服务。若 HTTP 验证失败,优先回查 DNS、80 端口、证书验证目录权限和错误的 IPv6 记录。
4. 数据恢复错误
数据库恢复是影响范围最大的操作。恢复前必须:
- 确认维护窗口,停止订单写入或启用维护模式。
- 再做一份当前生产数据库备份。
- 核对备份时间、数据库名称和目标实例。
- 优先恢复到临时实例验证,再决定是否替换生产数据。
- 明确恢复时间点之后新增订单、库存变化和退款记录是否需要人工补录。
不要把测试恢复命令改成生产库名称后直接执行。备份只能恢复到备份覆盖的时间点,不能自动找回之后产生的订单数据。
上线验收清单
- [ ] 域名 A/AAAA 记录指向正确地址。
- [ ] SSH 管理通道已确认,防火墙回滚方式可用。
- [ ] 公网仅开放必要的 80/443 端口。
- [ ] 应用由专用用户运行,并只监听
127.0.0.1。 - [ ]
nginx -t通过,HTTP 能跳转 HTTPS。 - [ ] HTTPS 证书域名正确,续期
--dry-run通过。 - [ ] Nginx、应用和数据库日志均能独立查看。
- [ ] 登录、购物车、订单、图片上传和后台操作已验证。
- [ ] 应用异常退出后 systemd 能自动拉起。
- [ ] 数据库、上传文件和关键配置已完成备份。
- [ ] 备份文件通过压缩校验,并完成过临时恢复测试。
- [ ] 本地备份之外已有独立存储副本。
- [ ] 磁盘、内存、日志和备份任务均有持续检查方式。
- [ ] 新版本、Nginx 配置和数据库恢复路径都经过一次可执行的回滚演练。