从零搭一个自己的博客:Astro + Caddy + 腾讯云
你现在看到的这个站,就是这篇文章要讲的东西。与其说是教程,不如说是一次复盘:我把“选什么、怎么部署、哪里踩坑”按顺序记下来,既给可能想做同样事情的人一点参考,也给未来的自己留个备忘。
整套技术栈很轻:Astro 生成静态站 → rsync 传到服务器 → Caddy 提供 HTTPS。没有数据库,没有后台,写文章就是往仓库里加一个 Markdown 文件。
一、为什么是 Astro
个人博客的核心诉求是内容优先、加载快、维护省心。Astro 默认输出纯静态 HTML,没有运行时框架的负担,首屏几乎是即时的;而写作体验就是 Markdown,够直接。
视觉上我参考了 OpenAI Codex 页面那种近乎单色的编辑风格:纯白/近黑、大量留白、用排版层级而不是色块和阴影去承载信息,等宽字体只留给日期、代码这类技术性内容。字体选了 Geist 做正文、JetBrains Mono 做等宽,浅色为主、深色自适应。
克制是这里的关键——一个博客,最该被看见的是文字本身。
二、本地开发
Astro 的内容集合(content collections)约定很清楚:文章就是 src/content/blog/ 下的 Markdown 文件,顶部写 frontmatter:
---
title: '文章标题'
description: '一句话摘要,会显示在列表页'
pubDate: '2026-08-04'
---
正文用 Markdown 写。
文件名就是 URL,比如 building-this-blog.md 对应 /blog/building-this-blog/。本地起个 dev server 就能实时预览:
npm run dev
保存文件自动热更新,所见即所得。
三、部署:一条命令搞定
静态站部署的本质特别简单:构建出一堆静态文件,传到服务器,让 Web 服务指向它。
Astro 构建后产物在 dist/:
npm run build
然后用 rsync 同步到服务器。我把这两步写成了一个 deploy.sh:
#!/usr/bin/env bash
set -euo pipefail
REMOTE_USER="ubuntu"
REMOTE_HOST="blog.chlsmile.cn"
REMOTE_PATH="/var/www/blog"
SSH_KEY="$HOME/.ssh/id_xxx"
npm run build
rsync -avz --delete \
-e "ssh -i ${SSH_KEY} -o IdentitiesOnly=yes" \
dist/ "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/"
--delete 会把服务器上多余的旧文件一并清掉,保持和本地完全一致。以后发新文章,写完 ./deploy.sh 一条命令,几秒后线上就更新了。rsync 只传变化的部分,所以很快。
四、Caddy:自动 HTTPS + 多服务共存
服务器这边我用 Caddy,最大的好处是自动申请和续期 HTTPS 证书,你几乎什么都不用管。
我的服务器上本来还跑着别的应用,所以要解决“一台机器、一个域名、多个服务“的共存问题。思路是让 Caddy 独占 80/443 作为唯一入口,再按域名分发。用子域名区分最干净——博客走 blog.chlsmile.cn,别的服务走别的子域名或反向代理:
blog.chlsmile.cn {
root * /var/www/blog
encode zstd gzip
file_server
}
www.chlsmile.cn {
reverse_proxy localhost:8080
}
前者把博客当静态文件服务,后者把请求反代给本机 8080 端口上的另一个应用。两个站点块靠 { } 各自划分边界,之间不需要任何分隔符。改完校验并重载:
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
新增一个子域名,只要去 DNS 加一条 A 记录指向服务器、再在 Caddyfile 里加一段,就完事了。
五、备案:大陆服务器绕不开的一步
有一条铁律值得记住:要不要备案,取决于服务器在哪,而不是域名后缀。服务器在中国大陆,无论 .com 还是 .cn,想用 80/443 正常访问就必须做 ICP 备案,否则云厂商会直接拦截——这也是为什么备案通过前,站点只能靠非标端口(如 :8080)访问。
ICP 备案通过后,还需要在页脚展示备案号并链接到工信部备案系统,这是硬性要求。我把备案号抽到了配置文件里,页脚自动渲染,将来公安联网备案的号也预留了位置。
六、踩坑复盘
真正花时间的从来不是“正常流程”,而是两个坑。
坑一:SSH 免密没配好,部署脚本连不上。
deploy.sh 是非交互的,没法在中途弹出来让你输密码,于是 rsync 直接报 Permission denied。解决办法是把本机公钥加到服务器的 ~/.ssh/authorized_keys(ssh-copy-id 一步搞定),并在脚本的 ssh 命令里用 -i 指定对应私钥。另外首次连接新主机还会卡在指纹确认上,给 ssh 加 -o StrictHostKeyChecking=accept-new 让它首次自动接受即可。
坑二:HTTPS 死活签不出证书。 现象很怪:HTTP 能访问(还自动 308 跳转到 HTTPS),但 HTTPS 就是连不上。翻 Caddy 日志才看明白,证书签发一直在报:
challenge failed ... "DNS problem: NXDOMAIN looking up A for blog.chlsmile.cn"
根因是签证书前 DNS 还没在公网生效。我在 DNS 刚配好、还没完全传播时就让 Caddy 去签,Let’s Encrypt 那边解析不到域名,反复失败后 Caddy 退避到几小时才重试一次,还一度卡在了 staging 测试环境。等 DNS 在公网真正生效后(用 dig @8.8.8.8 blog.chlsmile.cn 确认能解析到服务器 IP),强制重启一次让它立刻重签就好了:
sudo systemctl restart caddy
日志里出现 certificate obtained successfully,浏览器的小锁就亮了。
这个坑的教训:证书签发依赖的是“公网能不能解析到你的域名”,本机和服务器能 ping 通不代表全球的 DNS 都生效了。改完 DNS,先用公共解析器验证,再去碰证书。
写在最后
回头看,真正的工作量分布很典型:选型和写代码很快,卡住的都是环境和外部依赖——SSH、DNS、证书、备案。这些东西平时不显山露水,出问题时又特别容易让人怀疑是不是代码写错了。
但也正因为踩过一遍,下次就快了。而把它写下来,就是把“当时想明白的东西”固定住——这本来就是我想用这个博客做的事。