
Shipping an application is easy. Proving that the server can survive a reboot, a failed login attempt, a full disk, or a broken backup is the real work.
把应用部署到云服务器并不难,真正困难的是证明它能经受重启、暴力登录、磁盘写满和备份失效等生产场景。
This bilingual checklist gives you a vendor-neutral baseline for an Ubuntu/Debian cloud server running Docker and Nginx. Every section includes a command and a verification step—because a configuration is not complete until you can verify it.
这份中英双语清单适用于运行 Docker 与 Nginx 的 Ubuntu/Debian 云服务器,不绑定任何云厂商。每一项都包含命令和验证方法:配置完成不等于验证完成。
Safety note / 安全提示:Keep the current SSH session open while changing SSH or firewall settings. Test a second session before closing the first one. 修改 SSH 或防火墙时保留当前会话,并先用第二个终端验证新连接,避免把自己锁在服务器外。
The target architecture|目标架构

The public network should reach only the intended entry points. Nginx terminates HTTP/HTTPS traffic, the application stays behind it, the database is not exposed publicly, and backups leave the failure domain of the server.
公网只应访问必要入口。Nginx 负责 HTTP/HTTPS,应用容器位于其后,数据库不暴露到公网,备份则必须离开本机故障域。
1. Patch the operating system|更新操作系统
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y
Check whether a reboot is required:
test -f /var/run/reboot-required && cat /var/run/reboot-required || echo "No reboot required"
确认安全更新已经安装,并检查是否需要重启。若生产业务不能立即重启,应创建维护窗口,而不是长期忽略内核更新。
2. Use a non-root operator account|使用非 root 运维账号
sudo adduser deploy
sudo usermod -aG sudo deploy
id deploy
Daily operations should not run as root. The id output must show the expected user and sudo group.
日常操作不应直接使用 root。通过 id deploy 确认用户和 sudo 组已经生效。
3. Verify SSH keys before disabling passwords|先验证密钥,再关闭密码登录
From your local computer:
ssh-keygen -t ed25519 -a 64
ssh-copy-id deploy@SERVER_IP
ssh deploy@SERVER_IP
After the key login succeeds in a second terminal, create a drop-in configuration:
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
EOF
sudo sshd -t
sudo systemctl reload ssh
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|pubkeyauthentication'
sshd -t must return no error. Do not close the original session until the second key-based login works.
sshd -t 必须无报错。第二个终端使用密钥登录成功之前,不要关闭原会话。
4. Apply a default-deny firewall|设置默认拒绝的防火墙
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Only ports you intentionally expose should appear in the status output. If SSH uses a custom port, allow it before enabling UFW.
状态中只应出现你明确开放的端口。如果 SSH 使用自定义端口,必须先放行该端口再启用 UFW。
5. Audit listening ports|审计监听端口
sudo ss -lntup
sudo lsof -nP -iTCP -sTCP:LISTEN
Common public ports are 22, 80, and 443. A database bound to 0.0.0.0:3306 or 0.0.0.0:5432 deserves immediate investigation.
常见公网端口为 22、80 和 443。如果数据库监听在 0.0.0.0:3306 或 0.0.0.0:5432,应立即检查安全组、防火墙和应用连接方式。
6. Do not assume Docker obeys UFW|不要假设 Docker 自动遵守 UFW
Docker can publish ports through its own iptables/nftables rules. Prefer binding internal services to loopback:
services:
app:
ports:
- "127.0.0.1:3000:3000"
Then verify locally and externally:
curl -I http://127.0.0.1:3000
sudo ss -lntp | grep ':3000'
From another machine, SERVER_IP:3000 should be unreachable while Nginx can still proxy to 127.0.0.1:3000.
从外部机器访问 SERVER_IP:3000 应失败,但 Nginx 仍应能代理到 127.0.0.1:3000。
7. Put Nginx in front of the app|让 Nginx 作为统一入口
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
sudo nginx -t
sudo systemctl reload nginx
curl -I -H 'Host: example.com' http://127.0.0.1
Replace example.com with your real domain. Add TLS only after DNS resolves correctly.
将 example.com 替换为真实域名。先确认 DNS 正确解析,再配置 TLS,能减少证书签发失败和错误排查成本。
8. Set resource limits|设置资源限制
Without limits, one container can consume all memory and trigger the OOM killer.
services:
app:
mem_limit: 1g
cpus: 1.0
restart: unless-stopped
Verify the effective limits and recent OOM events:
docker inspect app --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}'
journalctl -k --since "24 hours ago" | grep -i -E 'oom|out of memory' || true
没有资源限制时,单个容器可能吃光内存并触发 OOM。限制值应根据真实负载测试调整,而不是照抄示例。
9. Watch disk and inode usage|同时监控磁盘容量和 inode
df -hT
df -ih
sudo du -xhd1 /var | sort -h
docker system df
A server can fail with free gigabytes remaining if it runs out of inodes. Set alerts for both percentage used and projected growth.
即使还有可用容量,inode 耗尽也会导致无法创建文件。监控应同时覆盖容量、inode 和增长速度。
10. Configure log rotation|配置日志轮转
For Docker:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
Save this as /etc/docker/daemon.json, validate it, and restart Docker during a controlled maintenance window:
python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info --format '{{.LoggingDriver}}'
日志轮转能避免容器日志无限增长。重启 Docker 会影响正在运行的容器,应安排维护窗口并验证重启策略。
11. Back up off-server—and test restore|异机备份,并验证恢复
A backup on the same server is not disaster recovery. Keep a database dump plus application data in object storage or another host.
mkdir -p /var/backups/app
pg_dump -Fc -U appuser appdb > /var/backups/app/appdb-$(date +%F).dump
sha256sum /var/backups/app/appdb-*.dump
Test the restore into an isolated database:
createdb -U postgres appdb_restore_test
pg_restore -U postgres -d appdb_restore_test /var/backups/app/appdb-YYYY-MM-DD.dump
psql -U postgres -d appdb_restore_test -c 'select now();'
备份成功日志不能证明数据可恢复。至少定期恢复到隔离环境,并记录恢复耗时、数据校验结果和负责人。
12. Add monitoring with actionable alerts|建立可行动的监控告警
At minimum, monitor:
- CPU saturation and load average
- Available memory and swap activity
- Disk usage, inode usage, and I/O latency
- HTTP error rate and latency
- Container restart count
- TLS certificate expiry
- Backup age and restore-test status
至少监控 CPU、内存、磁盘、I/O 延迟、HTTP 错误率、容器重启、证书到期时间、备份新鲜度和恢复演练状态。告警必须对应明确动作,而不是只产生噪声。
Final verification script|最终验证脚本
This read-only snapshot helps you collect evidence before deployment:
#!/usr/bin/env bash
set -u
echo '== OS =='
uname -a
echo '== Resources =='
free -h
df -hT
df -ih
echo '== Listening ports =='
sudo ss -lntup
echo '== Firewall =='
sudo ufw status verbose
echo '== Failed services =='
systemctl --failed --no-pager
echo '== Docker =='
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
echo '== Recent OOM events =='
journalctl -k --since '24 hours ago' | grep -i -E 'oom|out of memory' || true
The script does not change the server. Save the output with your deployment record and compare it after changes.
该脚本只读,不修改服务器。可以把输出保存到上线记录中,并在变更后再次执行,用于对比验证。
Where this baseline stops|这套基线的边界
This checklist is suitable for a single cloud server or small production workload. It is not a substitute for high availability, multi-region disaster recovery, compliance controls, or a tested incident-response process.
这份清单适合单台云服务器或小型生产负载,但不能替代高可用、多地域容灾、合规控制和经过演练的故障响应流程。
Use real metrics to decide when to add capacity or split services: sustained CPU saturation, memory pressure, disk latency, connection limits, recovery-time targets, and failure-domain requirements are stronger signals than a generic sizing table.
是否扩容或拆分服务,应依据真实指标:持续 CPU 饱和、内存压力、磁盘时延、连接数限制、恢复时间目标和故障域要求,比通用配置表更可靠。
TL;DR|总结
- Patch first. 先更新系统。
- Verify key login before disabling passwords. 关闭密码登录前先验证密钥。
- Expose only intended ports. 只开放必要端口。
- Treat Docker networking separately. 单独检查 Docker 网络规则。
- Set resource and log limits. 设置资源与日志限制。
- Back up off-server and test a restore. 异机备份并实际恢复。
- Monitor symptoms that lead to action. 监控必须能触发明确行动。
Tags: #devops #cloud #linux #tutorial

Top comments (0)