静态站不需要数据库,也不需要常驻的应用进程。对一台小服务器来说,这意味着更少的资源占用、更小的攻击面和更稳定的响应。
这个博客跑在一台很普通的云服务器上: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
自检不是为了证明“没问题”,而是为了在出问题时留下证据。
安全上的几个小动作
静态站天然少了很多入口,但仍有几件事值得做:
- 禁用 PHP 执行。 站点是纯 HTML,没有任何理由让
.php被解析。 - 屏蔽隐藏文件。
.git、.env、.user.ini不应该通过 HTTP 访问。 - 加安全响应头。
nosniff、frame-ancestors、Referrer-Policy都是低成本的兜底。 - 最小权限。 站点目录归
www:www,上传后统一修正权限。 - 保留备份。 每次上传前打一个 tar 包,占用很小,恢复很快。
关于域名和 HTTPS
如果域名还没有完成 ICP 备案,部分云厂商会在边缘拦截域名访问,表现是返回一个“Non-compliance ICP Filing”的页面;这时候直接用服务器 IP 通常仍能访问。
HTTPS 还需要 443 端口在安全组里放行,并申请证书。等这两件事都就绪,再把 Nginx 的 80 端口重定向到 443,同时加 HSTS。不要在证书还没配好时就急着开强制跳转。
一份可以重复的清单
- 构建产物与源码分离
- 图片生成多尺寸、多格式
- HTML 不缓存,静态资源长缓存
- gzip_static 预压缩
- 自定义 404
- 部署前备份
- 部署后循环自检
- 域名备案
- HTTPS
把这份清单跑通之后,换一台服务器、换一个域名,成本其实很低。真正花时间的,还是写东西。