BUILD · 3 MIN READ · 1495 字

把博客放上 2 核 2G 云服务器:一份静态站部署清单

静态站不需要数据库,也不需要常驻的应用进程。对一台小服务器来说,这意味着更少的资源占用、更小的攻击面和更稳定的响应。

静态站不需要数据库,也不需要常驻的应用进程。对一台小服务器来说,这意味着更少的资源占用、更小的攻击面和更稳定的响应。

这个博客跑在一台很普通的云服务器上:2 核 CPU、不到 2GB 内存、40GB 系统盘,Ubuntu 20.04,Nginx 1.28。没有 Node 常驻进程,没有 MySQL 查询,也没有 PHP 页面。

这篇文章记录一下部署流程,方便下次换机器时直接照做。

目录结构

本地项目大致是这样:

blog/
├─ src/
│  ├─ assets/          # 原图与生成后的响应式图片
│  ├─ content/         # 文章元数据与 Markdown
│  ├─ scripts/         # 浏览器端 JS
│  └─ styles/          # 样式
├─ scripts/
│  ├─ optimize_images.py
│  └─ selfcheck.mjs
├─ build.mjs           # 零运行时依赖的静态构建
├─ dist/               # 构建产物,只上传这一层
└─ reports/            # 构建与线上自检记录

服务器上的站点目录是 /www/wwwroot/www.lanr.online。Nginx 的站点配置由面板生成,我不直接改它,而是在面板提供的扩展目录里放一个独立配置文件:

/www/server/panel/vhost/nginx/extension/www.lanr.online/lanr-blog.conf

这样面板升级或重新保存站点时,自定义规则不会被覆盖。

Nginx 配置

核心只有几段:压缩、缓存、安全响应头和静态文件回退。

server_tokens off;
charset utf-8;

gzip on;
gzip_vary on;
gzip_static on;
gzip_comp_level 6;
gzip_min_length 512;
gzip_types text/plain text/css application/javascript
           application/json application/xml image/svg+xml;

# 带版本号的静态资源,缓存一年
location ^~ /assets/ {
    expires 1y;
    access_log off;
}

# HTML 保持协商缓存,更新后立即生效
location ~* \.html$ {
    expires -1;
}

# 纯静态站,不需要执行任何 PHP
location ~* \.php$ {
    return 404;
}

# 支持 /posts/foo 这类无扩展名地址
location / {
    try_files $uri $uri.html $uri/ =404;
}

error_page 404 /404.html;

静态站最常见的事故不是“性能不够”,而是缓存配置写错:HTML 被缓存了一年,文章更新后读者看到的还是旧版本。我的原则是:带内容哈希的资源可以永久缓存,HTML 永远不要。

上传

先备份,再同步:

TS=$(date +%Y%m%d-%H%M%S)
tar czf /www/backup/site/www.lanr.online-$TS.tar.gz \
  -C /www/wwwroot www.lanr.online

rsync -avz --delete \
  --exclude='.user.ini' \
  --exclude='.htaccess' \
  ./dist/ root@47.108.77.40:/www/wwwroot/www.lanr.online/

ssh root@47.108.77.40 \
  "chown -R www:www /www/wwwroot/www.lanr.online && nginx -t && nginx -s reload"

--delete 会清理上一次构建留下的旧文件,但排除掉面板生成的两个隐藏文件。nginx -t 必须通过才 reload;配置错误时,旧进程会继续服务,不会把站点直接弄挂。

上线自检

部署完不能只看首页有没有打开。我写了一个很小的自检脚本,循环检查:

  • 首页、文章库、归档、标签、关于、404 的状态码;
  • 每篇文章页是否返回 200,标题是否存在;
  • CSS、JS、首屏图片是否可访问,缓存头是否正确;
  • gzip 是否生效;
  • 关键页面是否存在 “Lanr” 和文章标题;
  • 每一轮的响应时间,以及失败重试。

脚本会跑若干轮,把结果写到 reports/selfcheck-*.json 和一份 Markdown 摘要,然后退出。这样即使人不在电脑前,也能在事后知道那一晚站点是否稳定。

node scripts/selfcheck.mjs --rounds 5 --interval 15

自检不是为了证明“没问题”,而是为了在出问题时留下证据。

安全上的几个小动作

静态站天然少了很多入口,但仍有几件事值得做:

  1. 禁用 PHP 执行。 站点是纯 HTML,没有任何理由让 .php 被解析。
  2. 屏蔽隐藏文件。 .git、.env、.user.ini 不应该通过 HTTP 访问。
  3. 加安全响应头。 nosniff、frame-ancestors、Referrer-Policy 都是低成本的兜底。
  4. 最小权限。 站点目录归 www:www,上传后统一修正权限。
  5. 保留备份。 每次上传前打一个 tar 包,占用很小,恢复很快。

关于域名和 HTTPS

如果域名还没有完成 ICP 备案,部分云厂商会在边缘拦截域名访问,表现是返回一个“Non-compliance ICP Filing”的页面;这时候直接用服务器 IP 通常仍能访问。

HTTPS 还需要 443 端口在安全组里放行,并申请证书。等这两件事都就绪,再把 Nginx 的 80 端口重定向到 443,同时加 HSTS。不要在证书还没配好时就急着开强制跳转。

一份可以重复的清单

  • 构建产物与源码分离
  • 图片生成多尺寸、多格式
  • HTML 不缓存,静态资源长缓存
  • gzip_static 预压缩
  • 自定义 404
  • 部署前备份
  • 部署后循环自检
  • 域名备案
  • HTTPS

把这份清单跑通之后,换一台服务器、换一个域名,成本其实很低。真正花时间的,还是写东西。