上一篇 下一篇 分享链接 返回 返回顶部

首次部署香港服务器,系统初始化、SSH加固与可用性验证如何按顺序完成?

发布人:Minchunlin 发布时间:2026-10-07 15:25 阅读量:12

首次部署香港服务器,验收目标不应只是“能远程登录”。一台可以上线的主机至少应达到:管理员可通过密钥登录、系统与时区已统一、SSH 和防火墙不会把自己锁在门外、应用仅通过预期端口对外提供服务,域名解析、HTTP/HTTPS、进程自启和健康检查均能得到明确结果。

引言配图

下面采用一套可执行的示例环境:Ubuntu Server 24.04 LTS 64 位、systemd、Nginx、Python 3.12 虚拟环境和 Gunicorn,域名使用 example.com 作为占位符。目标是部署一个仅监听本机 127.0.0.1:8000 的 Python Web 服务,由 Nginx 接收 80/443 端口请求;如果实际业务使用 Node.js、Java 或其他运行时,应替换对应运行时和进程启动命令,不要在一台主机上无目的地安装多套依赖。

一、准备条件与上线边界

1. 明确服务器和运行环境

香港服务器的地域只决定实例所在网络位置,首次部署仍应按照公网 IP、操作系统、访问控制和应用监听关系来规划。部署前记录以下信息:

项目示例值说明
操作系统Ubuntu Server 24.04 LTS amd64本文命令以此版本为准
初始化账户云厂商提供的 ubuntu 或自定义账户仅用于首次登录和创建管理账户
管理账户deploy使用 SSH 公钥登录,加入 sudo 组
Web 入口Nginx对外监听 80/443
应用运行时Python 3.12、venv、Gunicorn应用进程只监听 127.0.0.1:8000
必要依赖python3-venv、python3-pip、nginx、ufw、curl按应用实际依赖增减
对外端口TCP 22、80、44322 应限制来源,80/443按业务需要开放
域名example.com替换成实际域名
上线目标SSH 密钥登录、Nginx 代理应用、健康检查通过作为最终验收边界

演练或轻量服务可以从 2 vCPU、4 GB 内存起步,但这只是部署示例,不代表所有应用的容量要求。数据库、编译任务、消息队列和高并发应用应单独评估资源。

面向香港网站、业务后台和接口服务的部署,A5数据提供入门建站、Xeon Gold及AMD EPYC等物理服务器产品,以不同档位的计算、内存资源搭配SSD或NVMe存储,为应用进程、数据库及多任务运行提供硬件基础。香港产品还提供CN2与国际带宽方案,覆盖面向不同访问人群的网络资源需求,可承载以Nginx为对外入口的Web应用部署。

2. 准备管理入口和回滚手段

正式修改 SSH、防火墙和 Nginx 前,至少确认以下条件:

  • 云厂商控制台可以打开 VNC、串口或救援终端等带外管理入口。
  • 已记录公网 IPv4;如果启用 IPv6,也记录 IPv6 地址和对应的网段。
  • 已保存云主机快照或磁盘备份。快照只能覆盖快照时刻的磁盘状态,不能替代数据库和业务文件的异机备份。
  • 已准备一个不会放在服务器上的 SSH 私钥。
  • 已知云平台安全组、网络 ACL 与服务器内部防火墙的配置位置。
  • 已确定维护窗口。更新内核、重启服务或切换 HTTPS 配置,都可能造成短暂中断。

本地操作终端可以先设置示例变量,实际使用时替换为真实值:

export SERVER_IP='203.0.113.10'
export DOMAIN='example.com'
export SSH_KEY="$HOME/.ssh/id_ed25519_hk_web"

203.0.113.10 属于文档示例地址,不能直接用于实际连接。

3. 先检查云平台安全组

安全组和 UFW 是两层独立控制。建议先在云平台放行:

  • TCP 22:仅允许办公出口 IP、堡垒机 IP 或管理员固定网段。
  • TCP 80:如果需要 HTTP 访问或申请证书,允许公网访问。
  • TCP 443:启用 HTTPS 后允许公网访问。
  • 其他端口:默认不放行,尤其是数据库、缓存、应用内部端口和管理面板端口。

如果管理员出口地址经常变化,不能简单地把 SSH 端口永久暴露给全部公网。可以先利用控制台完成初始化,再通过临时规则处理变化;规则调整前必须确认新的出口地址已经加入。

二、首次登录与系统初始化

1. 核验操作系统、架构和网络地址

使用云厂商提供的初始账户登录后,先不要修改 SSH 配置。确认系统版本、内核、地址和默认路由:

ssh -i "$SSH_KEY" ubuntu@"$SERVER_IP"

在服务器上执行:

cat /etc/os-release
uname -m
hostnamectl
ip -br address
ip route
resolvectl status

应重点确认:

  • PRETTY_NAME 为 Ubuntu 24.04 LTS 或实际申请的版本。
  • 架构与业务软件包匹配,常见为 x86_64。
  • 公网 IPv4 位于预期网卡上。
  • 默认路由存在。
  • DNS 解析器可用。

如果系统不是预期版本,不要继续套用本文命令。不同发行版的服务名、包名和 SSH 默认配置可能不同,应先确认对应文档或重装为预定镜像。

2. 建立系统备份并更新基础软件

先取得 root shell,创建配置备份目录:

sudo -i

BACKUP_DIR="/root/first-deploy-$(date +%Y%m%d-%H%M%S)"
install -d -m 700 "$BACKUP_DIR"

cp -a /etc/ssh "$BACKUP_DIR/ssh-before"
cp -a /etc/nginx "$BACKUP_DIR/nginx-before" 2>/dev/null || true
tar -C /etc -czf "$BACKUP_DIR/etc-selected.tgz" ssh systemd/system
sha256sum "$BACKUP_DIR/etc-selected.tgz" > "$BACKUP_DIR/SHA256SUMS"

如果当前还没有安装 Nginx,cp -a /etc/nginx 报错是正常的。这里没有忽略 SSH 备份;SSH 配置是后续回滚的关键。

更新软件包索引并安装基础依赖:

apt update
apt full-upgrade -y

apt install -y \
  ca-certificates \
  curl \
  nginx \
  python3 \
  python3-venv \
  python3-pip \
  ufw \
  openssh-server

full-upgrade 可能更新内核或需要重启。执行后检查是否有失败服务:

systemctl --failed
systemctl is-active ssh
systemctl is-active nginx

如果系统提示需要重启,应在 SSH 加固前完成重启验证,避免把“系统更新后无法启动”和“SSH 配置错误”混在一起排查:

reboot

重启会中断当前连接,只有在云控制台可用、初始账户仍可登录的情况下执行。重连后再次检查:

ssh -i "$SSH_KEY" ubuntu@"$SERVER_IP"

3. 设置主机名、时区和时间同步

主机名应能体现地域和用途,例如:

sudo hostnamectl set-hostname hk-web-01
sudo timedatectl set-timezone Asia/Hong_Kong
sudo timedatectl set-ntp true
timedatectl status

如果应用、日志平台和数据库统一使用 UTC,则不要为了地域名称强行设置本地时区。关键不是选哪一个时区,而是所有组件保持一致。采用香港本地时区时,日志时间应显示为 HKT 或 +08:00;采用 UTC 时,应在运维平台统一换算。

4. 创建独立管理账户

不要长期使用云厂商初始账户,更不要让应用进程使用 root 运行。创建一个专用于运维的账户:

sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG sudo deploy

sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
sudo install -m 600 -o deploy -g deploy /dev/null \
  /home/deploy/.ssh/authorized_keys

在本地生成独立密钥。已有可靠密钥时可以复用,不必重复覆盖:

ssh-keygen -t ed25519 -a 100 -f "$SSH_KEY" -C "deploy@hk-web-01"

将公钥写入服务器。下面的命令需要使用当前仍可登录的初始账户执行:

cat "${SSH_KEY}.pub" | ssh -i "$HOME/.ssh/initial-key" ubuntu@"$SERVER_IP" \
  'sudo tee -a /home/deploy/.ssh/authorized_keys >/dev/null && \
   sudo chown deploy:deploy /home/deploy/.ssh/authorized_keys && \
   sudo chmod 600 /home/deploy/.ssh/authorized_keys'

如果云平台只提供控制台而没有可用的初始 SSH 密钥,可以在控制台中粘贴公钥,但不要把私钥复制到服务器。

先用新账户开启第二个终端验证:

ssh -o IdentitiesOnly=yes -i "$SSH_KEY" deploy@"$SERVER_IP"

验证 sudo 权限:

sudo -v
sudo id

只有新账户可以密钥登录并获得预期管理权限后,才进入 SSH 加固阶段。当前初始连接不要关闭,它是失败时的回退通道。

三、SSH 加固:先验证密钥,再关闭密码配图

三、SSH 加固:先验证密钥,再关闭密码

1. 备份当前 SSH 配置

在服务器上执行:

sudo -i

SSH_BACKUP="/root/ssh-backup-$(date +%Y%m%d-%H%M%S)"
install -d -m 700 "$SSH_BACKUP"
cp -a /etc/ssh/sshd_config "$SSH_BACKUP/"
cp -a /etc/ssh/sshd_config.d "$SSH_BACKUP/"

SSH 配置涉及远程管理入口,任何覆盖、权限变更或语法错误都可能导致新连接失败。必须保留当前会话和带外控制台,不能只依赖即将修改的 SSH 连接。

2. 使用独立 drop-in 文件加固

在 Ubuntu 24.04 上,推荐将自定义内容放进 /etc/ssh/sshd_config.d/,避免直接覆盖主配置:

cat > /etc/ssh/sshd_config.d/99-hardening.conf <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 3
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
EOF

chmod 644 /etc/ssh/sshd_config.d/99-hardening.conf

这些设置的作用分别是:

  • PubkeyAuthentication yes:启用公钥认证。
  • PasswordAuthentication no:关闭 SSH 密码认证,降低密码撞库风险。
  • KbdInteractiveAuthentication no:关闭键盘交互式认证,避免通过其他交互方式绕过密码策略。
  • PermitRootLogin no:禁止 root 直接远程登录,改由普通账户登录后使用 sudo。
  • MaxAuthTries 3:限制单次连接的认证尝试次数。
  • X11Forwarding no:服务器不需要图形转发时关闭。
  • AllowTcpForwarding no:不需要 SSH 隧道时关闭。若运维工具依赖端口转发,应先确认再启用。
  • ClientAliveInterval 和 ClientAliveCountMax:清理异常断开的会话,不等同于应用层心跳。

先检查语法和最终生效值:

sshd -t
sshd -T | grep -E \
  '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin|maxauthtries|x11forwarding|allowtcpforwarding|clientaliveinterval|clientalivecountmax) '

只有 sshd -t 没有输出错误时,才重新加载服务:

systemctl reload ssh

3. 开启新连接测试

不要关闭原有终端,另开窗口执行:

ssh \
  -o IdentitiesOnly=yes \
  -o PasswordAuthentication=no \
  -o PreferredAuthentications=publickey \
  -i "$SSH_KEY" \
  deploy@"$SERVER_IP"

进入后确认:

whoami
sudo id

如果新连接成功,再测试旧初始账户是否已按预期限制。不要只测试当前会话,因为当前会话可能在配置变化前已经完成认证。

SSH 加固完成后的基本状态应是:

验证项预期结果
deploy 密钥登录成功
deploy 执行 sudo成功并按系统策略要求输入密码
root 直接登录被拒绝
SSH 密码登录被拒绝
SSH 服务状态active (running)
初始连接在确认新连接前保持不关闭

四、防火墙配置:先放行管理地址,再启用策略

1. 确认当前管理出口地址

在当前 SSH 会话中查看连接来源:

echo "$SSH_CONNECTION"

输出通常类似:

198.51.100.25 53000 203.0.113.10 22

第一列是客户端地址。可以提取为变量并人工确认:

ADMIN_IP="$(printf '%s\n' "$SSH_CONNECTION" | awk '{print $1}')"
echo "$ADMIN_IP"

如果通过堡垒机连接,第一列是堡垒机地址,而不是个人电脑地址;应允许堡垒机网段,不能误把个人公网地址写入规则。

2. 配置 UFW

先放行 SSH,再设置默认策略:

sudo ufw allow from "$ADMIN_IP" to any port 22 proto tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

sudo ufw default deny incoming
sudo ufw default allow outgoing

sudo ufw status numbered

确认规则中已经存在正确的 SSH 来源,再启用防火墙:

sudo ufw --force enable
sudo ufw status verbose

ufw --force enable 会立即改变入站访问策略。如果 SSH 规则写错,当前连接可能继续存在,但新连接会被拒绝。因此必须在启用前检查 ufw status numbered,并保留云控制台。

如果管理员没有固定 IP,可以使用更宽的临时 SSH 规则或 ufw limit 22/tcp,但这只是兼容动态地址的折中方案。上线后应尽量改为固定出口、VPN 之外的合规堡垒机或明确的管理网段策略;不要把临时的全公网 SSH 规则当作最终状态。

IPv6 需要单独验证。确认服务器确实启用了 IPv6、云平台安全组允许对应流量、UFW 的 IPv6 策略正确后,再配置 AAAA 记录。只配置 AAAA 但没有完整 IPv6 路由和防火墙规则,会造成部分访问失败。

3. 检查实际监听端口

sudo ss -lntup
sudo ufw status verbose

初始阶段常见的预期是:

0.0.0.0:22       sshd
0.0.0.0:80       nginx
127.0.0.1:8000   gunicorn

应用端口如果显示为 0.0.0.0:8000,意味着它直接暴露在公网或云平台网络上。除非有明确原因,否则应改为 127.0.0.1:8000,由 Nginx 统一处理外部请求。

四、防火墙配置:先放行管理地址,再启用策略配图

五、部署运行时和应用进程

1. 创建独立应用账户

应用不应使用 deploy 或 root 账户运行。创建系统账户:

sudo adduser \
  --system \
  --group \
  --home /opt/example-app \
  --shell /usr/sbin/nologin \
  app

sudo install -d -m 0750 -o app -g app /opt/example-app

下面的 Python WSGI 文件只是用于验证进程、Nginx 和健康检查链路的最小示例。实际上线时,应替换为经过测试的业务代码和锁定版本的 requirements.txt。

创建示例依赖:

sudo tee /opt/example-app/requirements.txt >/dev/null <<'EOF'
gunicorn
EOF

创建健康检查应用:

sudo tee /opt/example-app/wsgi.py >/dev/null <<'EOF'
def app(environ, start_response):
    path = environ.get("PATH_INFO", "/")

    if path == "/healthz":
        status = "200 OK"
        body = b'{"status":"ok"}\n'
        content_type = "application/json"
    else:
        status = "200 OK"
        body = b"example-app is running\n"
        content_type = "text/plain; charset=utf-8"

    headers = [
        ("Content-Type", content_type),
        ("Content-Length", str(len(body))),
    ]
    start_response(status, headers)
    return [body]
EOF

sudo chown -R app:app /opt/example-app
sudo chmod 0750 /opt/example-app
sudo chmod 0640 /opt/example-app/requirements.txt /opt/example-app/wsgi.py

2. 创建 Python 虚拟环境并安装依赖

使用应用账户创建虚拟环境,避免把依赖安装到系统 Python:

sudo -u app python3 -m venv /opt/example-app/.venv

sudo -u app /opt/example-app/.venv/bin/python \
  -m pip install --disable-pip-version-check \
  -r /opt/example-app/requirements.txt

真实业务上线时,建议使用项目锁定文件或内部制品仓库,并在测试环境先完成安装。不要在生产环境临时升级一个关键框架后直接重启应用。

3. 使用 systemd 管理应用

创建服务单元:

sudo tee /etc/systemd/system/example-app.service >/dev/null <<'EOF'
[Unit]
Description=Example Python Web Application
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/example-app
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/example-app/.venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 wsgi:app
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

sudo chmod 0644 /etc/systemd/system/example-app.service
sudo systemctl daemon-reload
sudo systemctl enable --now example-app.service

检查服务状态和本机健康检查:

sudo systemctl status example-app.service --no-pager
sudo systemctl is-enabled example-app.service
curl -fsS http://127.0.0.1:8000/healthz

预期结果类似:

{"status":"ok"}

如果服务启动失败,先查看日志,不要反复执行重启命令:

sudo journalctl -u example-app.service -n 100 --no-pager
sudo systemctl cat example-app.service
sudo ss -lntp | grep ':8000'

六、配置 Nginx 作为对外入口

1. 创建站点目录和反向代理配置

创建站点配置时,把 example.com 和 www.example.com 替换为实际域名:

sudo tee /etc/nginx/sites-available/example.com >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location = /healthz {
        proxy_pass http://127.0.0.1:8000/healthz;
        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;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}
EOF

sudo chmod 0644 /etc/nginx/sites-available/example.com
sudo ln -s /etc/nginx/sites-available/example.com \
  /etc/nginx/sites-enabled/example.com

如果目标路径已经存在,不要直接覆盖。先检查:

ls -l /etc/nginx/sites-available/
ls -l /etc/nginx/sites-enabled/

2. 验证配置后重新加载

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl enable nginx
sudo systemctl is-active nginx

nginx -t 失败时不要 reload。常见原因包括域名配置拼写错误、花括号不匹配、重复监听 default_server 或上游地址写错。

在 DNS 尚未生效时,可以通过 Host 头验证 Nginx 是否选择了目标站点:

curl -i -H 'Host: example.com' http://127.0.0.1/healthz

也可以从本地终端绕过 DNS,将域名解析到服务器地址:

curl --resolve "$DOMAIN:80:$SERVER_IP" \
  -fsS "http://$DOMAIN/healthz"

这个方式同时验证了目标 IP、Host 头和 Nginx 路由,比直接访问 IP 更接近真实上线请求。

七、配置 DNS 和 HTTPS

1. 添加 DNS 记录

在域名管理平台创建:

记录类型主机记录指向使用条件
A@ 或空主机名香港服务器 IPv4服务器有可达 IPv4
Awww香港服务器 IPv4需要 www 域名时
AAAA@服务器 IPv6IPv6 路由和防火墙已验证
AAAAwww服务器 IPv6同上

使用 dig 检查解析:

dig +short A example.com
dig +short A www.example.com
dig +short AAAA example.com

A 记录已经返回正确地址后,再进行 HTTPS 配置。若 DNS 使用代理或 CDN,证书申请、源站访问和真实客户端 IP 处理方式会不同,应先确认回源端口和证书模式。

2. 申请 HTTPS 证书

HTTPS 不是修改 SSH 或 UFW 的替代品,证书申请前仍需确保 80 端口可以从公网访问。先备份 Nginx 配置:

sudo tar -C /etc -czf \
  "/root/nginx-before-certbot-$(date +%Y%m%d-%H%M%S).tgz" \
  nginx

安装证书工具:

sudo apt install -y certbot python3-certbot-nginx

申请证书并让工具配置 Nginx:

sudo certbot --nginx \
  -d example.com \
  -d www.example.com \
  --redirect

如果没有使用 www,不要把它加入命令。--redirect 会将 HTTP 请求重定向到 HTTPS,并可能修改站点配置。执行后检查:

sudo nginx -t
sudo systemctl reload nginx

curl -I https://example.com/healthz
sudo certbot renew --dry-run

预期应看到 HTTPS 返回 200,且健康检查内容正确。证书申请失败时,先确认:

  • DNS 的 A/AAAA 记录是否已经指向当前服务器。
  • 云平台安全组和 UFW 是否允许 TCP 80。
  • Nginx 是否正在监听 80。
  • 域名是否被其他代理、旧服务器或错误的 AAAA 记录接管。
  • 证书命令中的域名是否与 Nginx server_name 一致。

八、按层次完成可用性验证

验证顺序应由内到外,但故障排查时通常由外到内。部署结束后,至少执行以下检查:

层级检查命令或动作通过标准
系统systemctl --failed没有与业务相关的失败服务
时间timedatectl statusNTP 已同步,时区符合约定
磁盘df -h、df -ih根分区和 inode 有足够余量
内存free -h没有持续的内存耗尽或异常 swap
SSH新终端密钥登录deploy 可登录并执行 sudo
SSH 加固使用密码测试密码登录被拒绝,密钥登录正常
防火墙ufw status verbose22 来源受限,80/443按目标开放
进程systemctl is-active example-app返回 active
应用curl 127.0.0.1:8000/healthz返回 HTTP 200 和预期内容
Nginxnginx -t配置语法通过
站点curl --resolve ...域名 Host 路由正常
DNSdig +short返回当前服务器地址
HTTPScurl -I https://...TLS 握手成功,健康检查返回 200
自启systemctl is-enabled nginx example-app均为 enabled

还应检查端口是否符合设计:

sudo ss -lntup

理想情况下,Gunicorn 不应监听公网地址:

LISTEN ... 127.0.0.1:8000 ... users:(("gunicorn",...))

如果看到 0.0.0.0:8000 或 [::]:8000,应立即检查 systemd 的 ExecStart 参数和应用自身配置,并在确认不需要公网访问后改为本机监听。

九、常见失败处理

1. SSH 加固后无法登录

先不要重装系统。通过云控制台进入服务器,检查:

sudo sshd -t
sudo systemctl status ssh --no-pager
sudo journalctl -u ssh -n 100 --no-pager
sudo ss -lntp | grep ':22'

如果是密钥权限问题:

sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keys

如果是配置错误,暂时移走自定义文件但不要删除,以便保留证据:

sudo mv \
  /etc/ssh/sshd_config.d/99-hardening.conf \
  /etc/ssh/sshd_config.d/99-hardening.conf.disabled

sudo sshd -t
sudo systemctl reload ssh

如果恢复后可以登录,说明问题集中在自定义 drop-in。应逐项恢复配置,每次修改后先执行 sshd -t,并使用第二个终端测试。

2. 启用 UFW 后新连接被拒绝

通过云控制台检查规则:

sudo ufw status numbered

如果确认当前出口地址,补充正确的放行规则:

sudo ufw allow from 198.51.100.25 to any port 22 proto tcp
sudo ufw reload

将示例地址替换成实际管理出口。只有在控制台可用且必须紧急恢复访问时,才临时执行:

sudo ufw disable

恢复访问后应立即补充正确规则并重新启用,不能把关闭 UFW 作为最终修复。

3. 应用服务启动失败

先区分“服务没有启动”和“启动后立即退出”:

sudo systemctl status example-app.service --no-pager
sudo journalctl -u example-app.service -n 100 --no-pager
sudo systemctl cat example-app.service

再检查虚拟环境、依赖和端口:

sudo -u app /opt/example-app/.venv/bin/python --version
sudo -u app /opt/example-app/.venv/bin/python -c 'import gunicorn; print(gunicorn.__version__)'
sudo ss -lntp | grep ':8000'

常见原因包括:

  • WorkingDirectory 路径不存在。
  • wsgi:app 中的模块名或对象名不匹配。
  • 依赖安装到了错误的 Python 环境。
  • 8000 端口被其他进程占用。
  • 应用账户无权读取代码或配置文件。
  • 环境变量没有写入 systemd 单元。

不要直接给整个 /opt/example-app 执行 chmod -R 777。应根据日志只修正目录属主和最小读取权限。

4. Nginx 返回 502

502 通常表示 Nginx 无法连接上游应用。依次检查:

sudo systemctl is-active example-app.service
curl -i http://127.0.0.1:8000/healthz
sudo ss -lntp | grep ':8000'
sudo tail -n 100 /var/log/nginx/error.log

判断方式:

九、常见失败处理配图

  • 本机访问 8000 失败:先修复应用服务。
  • 本机访问 8000 成功,Nginx 返回 502:检查 proxy_pass、端口和 Nginx 配置。
  • Nginx 配置测试失败:不要 reload,修复语法后再执行。
  • 本机 Nginx 正常,公网访问失败:检查 DNS、安全组、UFW 和客户端网络路径。

5. DNS 正确但 HTTPS 失败

先使用 curl --resolve 分离 DNS 问题:

curl -vk --resolve example.com:443:203.0.113.10 \
  https://example.com/healthz

如果 --resolve 成功而普通访问失败,优先检查 DNS 缓存、AAAA 记录和代理层。如果两者都失败,再检查 Nginx 的 443 监听、证书文件和证书工具输出:

sudo nginx -t
sudo ss -lntp | grep ':443'
sudo journalctl -u nginx -n 100 --no-pager

十、回滚方案与变更边界

1. SSH 配置回滚

如果只是本次新增的 drop-in 导致问题,优先采用保留文件的方式回滚:

sudo mv \
  /etc/ssh/sshd_config.d/99-hardening.conf \
  /etc/ssh/sshd_config.d/99-hardening.conf.rollback

sudo sshd -t
sudo systemctl reload ssh

如果主配置也被修改,应使用对应时间点的备份恢复。恢复前确认备份目录,不要把不确定的路径直接复制到 /etc/ssh:

sudo find /root -maxdepth 1 -type d -name 'ssh-backup-*' -printf '%f\n'

恢复配置会覆盖当前 SSH 设置,必须在控制台或保持原有管理会话的情况下进行。恢复后仍要执行 sshd -t,确认无语法错误,再 reload。

2. Nginx 和应用回滚

Nginx 配置回滚前先保留当前版本:

sudo cp -a /etc/nginx/sites-available/example.com \
  /root/example.com.nginx.failed.$(date +%Y%m%d-%H%M%S)

如果要停用本次新增站点,不要直接删除配置:

sudo mv \
  /etc/nginx/sites-enabled/example.com \
  /etc/nginx/sites-enabled/example.com.disabled

sudo nginx -t
sudo systemctl reload nginx

应用回滚可以先停止应用,再切换代码目录或恢复已验证的发布版本:

sudo systemctl stop example-app.service
sudo systemctl disable example-app.service

停止服务会使依赖该应用的站点不可用,Nginx 可能返回 502。执行前应确认业务影响,并准备好恢复命令:

sudo systemctl enable --now example-app.service

如果 HTTPS 工具已经改写 Nginx 配置,应优先通过站点配置备份恢复相关文件,而不是覆盖整个 /etc/nginx 目录。证书、私钥和其他站点配置可能位于同一目录,粗暴恢复会造成新的中断。

3. 使用快照恢复的边界

当系统更新、磁盘损坏或多项配置同时失控时,可以考虑恢复部署前快照,但要明确:

  • 恢复快照通常会停止或重启实例。
  • 快照之后产生的日志、上传文件和数据库写入可能丢失。
  • 公网 IP 是否保留取决于云平台资源模型。
  • DNS TTL 不会因为快照恢复而自动改变。
  • 恢复后仍需重新检查 SSH、UFW、Nginx、应用服务和证书状态。

因此,快照适合回退系统和配置,不适合替代业务数据备份。数据库、上传文件、证书私钥和应用制品应按照各自的备份策略保存。

十一、上线与验收检查清单

上线前逐项确认,未通过的项目不要用“先上线再修”代替:

  • [ ] 操作系统版本、CPU 架构、主机名和时区符合部署记录。
  • [ ] 已有云平台快照或可恢复备份,且知道恢复入口。
  • [ ] deploy 可以使用指定 SSH 私钥登录。
  • [ ] deploy 可以通过 sudo 管理服务。
  • [ ] root 不能直接 SSH 登录。
  • [ ] SSH 密码认证已关闭,且新终端密钥登录测试成功。
  • [ ] 当前 SSH 连接来源已经加入 UFW 和云平台安全组。
  • [ ] UFW 仅开放必要端口,应用内部端口未对公网监听。
  • [ ] systemctl --failed 没有未处理的业务失败服务。
  • [ ] 应用进程使用独立账户运行。
  • [ ] 应用只监听 127.0.0.1:8000 或预定的本机端口。
  • [ ] curl http://127.0.0.1:8000/healthz 返回预期结果。
  • [ ] nginx -t 通过,Nginx 已设置开机启动。
  • [ ] 使用 curl --resolve 验证了域名 Host 路由。
  • [ ] A/AAAA 记录与实际服务器地址一致,未留下错误旧记录。
  • [ ] HTTPS 证书覆盖实际访问域名。
  • [ ] curl -I https://域名/healthz 返回成功状态。
  • [ ] certbot renew --dry-run 或实际证书续期机制已验证。
  • [ ] 应用和 Nginx 日志位置已记录,出现 502、403、连接拒绝时知道查看哪一层。
  • [ ] 已记录 SSH、UFW、Nginx 和应用配置的回滚位置。
  • [ ] 已安排后续补充项,包括系统安全更新、磁盘空间监控、服务异常告警和异机数据备份。