一台 2 核 2G 的入门云服务器,最常见的失败方式不是“性能不够”,而是把不需要的东西装了上去:数据库、Redis、Node 常驻进程、容器编排。每多一层,就多一个会半夜挂掉、需要备份、需要升级的组件。

静态站点是这类机器上性价比最高的形态:构建在本地或 CI 完成,服务器只负责把文件发出去。

一、先决定“什么在服务器上运行”

静态站点的运行时只有两件事:TLS 终止和文件读取。这意味着服务器上不需要 Node、不需要包管理器以外的运行时,甚至不需要数据库连接池。

方案常驻内存复杂度适用
Nginx低中(配置项多)已有运维习惯
Caddy低低(自动 HTTPS)单站、追求少配置
对象存储 + CDN极低低有预算、要弹性

对单台小机器,Caddy 的收益很直接:证书签发、续期、HTTP→HTTPS 跳转都由它完成,配置只有十几行。

二、构建产物要“自包含”

构建阶段最容易踩的坑是把绝对路径写死。检查三件事:

  1. 资源引用使用相对或根路径,部署到子目录时不至于全挂;
  2. 不在构建期请求外网接口(构建机与服务器网络环境不同);
  3. 产物里的 sitemap、canonical 使用最终域名,而不是 localhost。
# 构建后做一次静态自检
grep -rIl "localhost" dist/ || echo "OK: no localhost leaked"
du -sh dist/
find dist -type f | wc -l

三、systemd 资源约束比“优化配置”更有效

在 2G 内存的机器上,真正危险的是内存碎片和突发流量,而不是稳态占用。给 Web 服务加上硬性上限,可以让它“优雅地慢”,而不是把整机拖进 OOM。

[Service]
MemoryMax=256M
MemoryHigh=200M
TasksMax=256
LimitNOFILE=65535
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/www /var/lib/caddy

MemoryMax 触发的是 cgroup 级别的回收,服务会返回错误而不是拖垮 sshd —— 后者才是小机器上最致命的连锁反应。

另外建议加 1–2G swap。它不是“性能优化”,而是防止构建或日志轮转时的瞬时峰值直接杀死进程。

fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
sysctl -w vm.swappiness=10

四、缓存策略决定“带宽账单”

静态资源文件名带哈希时,可以放心长缓存;HTML 必须短缓存或不缓存,否则用户会看到旧页面。

@immutable path /_astro/* /art/* /fonts/*
header @immutable Cache-Control "public, max-age=31536000, immutable"

@html path / *.html
header @html Cache-Control "public, max-age=0, must-revalidate"

压缩交给 Caddy 的 encode zstd gzip 即可,不必在构建期预压缩两套文件。

五、监控只要三行

小站不需要 Prometheus。一个每天跑一次的脚本,检查磁盘、内存、证书到期,异常时发一封邮件,已经能覆盖 95% 的故障。

#!/usr/bin/env bash
set -euo pipefail
df -h / | awk 'NR==2 && int($5) > 85 {print "disk>85%"; exit 1}'
free -m | awk '/Mem:/ && $7 < 120 {print "mem low"; exit 1}'

六、结论

2 核 2G 不是瓶颈,组件数量才是。把构建放到本地、把运行时压到只剩一个反向代理、把故障面收敛到 systemd 和磁盘,这台机器就能稳定跑到下一次续费。

本站正是按这个思路搭建的:静态产物 + 单进程 Web 服务 + 每日自检。后续文章会给出完整的部署与回滚脚本。