这篇文章记录一种简单、可维护的静态博客部署方式:用 Hugo 生成静态页面,用 Caddy 对外提供访问和自动 HTTPS。

示例中统一使用 blog.example.com 作为占位域名。实际部署时,把它替换成自己的域名即可。

架构思路

Hugo 和 Caddy 各自负责一件事:

  • Hugo:把 Markdown、主题和配置生成成静态 HTML/CSS/JS。
  • Caddy:监听 80/443,对外提供静态文件服务,并自动申请和续签 HTTPS 证书。

推荐目录结构:

/opt/blog        # Hugo 源码目录:配置、文章、主题
/var/www/blog    # Hugo 生成后的静态文件目录,Caddy 对外读取这里
/etc/caddy       # Caddy 配置目录

日常只编辑 /opt/blog/var/www/blog 是构建产物,不手动修改。

权限模型

一个容易踩坑的点是 /var/www/blog 的权限。

Caddy 只是读取静态文件,不需要拥有发布目录。因此推荐:

  • /opt/blog 归日常登录用户所有。
  • /var/www/blog 也归日常登录用户所有。
  • Caddy 只需要通过普通文件权限读取 /var/www/blog

这样每次发布时,Hugo 可以直接写入 /var/www/blog,不需要反复 chown

假设日常登录用户是 deploy,可以这样设置:

sudo mkdir -p /opt/blog /var/www/blog
sudo chown -R deploy:deploy /opt/blog /var/www/blog
find /var/www/blog -type d -exec chmod 755 {} \;
find /var/www/blog -type f -exec chmod 644 {} \;

如果是个人服务器,也可以直接把 deploy 替换成自己的用户名。

前置条件

部署前需要准备:

  1. 一台 Linux 服务器。
  2. 一个域名,例如 blog.example.com
  3. 域名 A 记录已经指向服务器公网 IP。
  4. 服务器公网可访问 80443
  5. 有 sudo 权限。

Caddy 自动 HTTPS 依赖域名解析和端口可达。如果域名还没解析好,Caddy 证书申请会失败。

安装 Caddy

Debian/Ubuntu 上可以先用系统包安装:

sudo apt-get update
sudo apt-get install -y caddy git

确认 Caddy 正常:

caddy version
systemctl status caddy

Caddy 会在后台自动管理 HTTPS 证书。正常情况下,不需要手动运行 certbot 或单独维护证书续签任务。

安装 Hugo

发行版仓库里的 Hugo 可能偏旧,容易和新主题不兼容。建议安装 Hugo 官方 extended 版本。

到 Hugo 的 release 页面选择最新的 hugo_extended_*_linux-amd64.deb,然后安装:

curl -L -o /tmp/hugo.deb \
  https://github.com/gohugoio/hugo/releases/download/vX.Y.Z/hugo_extended_X.Y.Z_linux-amd64.deb
sudo apt-get install -y /tmp/hugo.deb
rm -f /tmp/hugo.deb

X.Y.Z 替换成实际版本号。

确认版本:

hugo version

如果使用 PaperMod 这类现代主题,Hugo 版本建议保持较新。

创建 Hugo 站点

创建目录并进入源码目录:

sudo mkdir -p /opt/blog /var/www/blog
sudo chown -R "$USER:$USER" /opt/blog /var/www/blog
hugo new site /opt/blog --force
cd /opt/blog

安装主题。这里以 PaperMod 为例:

git clone --depth 1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

配置 Hugo

编辑 /opt/blog/hugo.toml

baseURL = "https://blog.example.com/"
locale = "zh-cn"
title = "My Blog"
theme = "PaperMod"
timeZone = "Asia/Shanghai"

[pagination]
pagerSize = 10

[params]
env = "production"
defaultTheme = "auto"
ShowReadingTime = true
ShowPostNavLinks = true
ShowBreadCrumbs = true
ShowCodeCopyButtons = true
ShowToc = true

[params.homeInfoParams]
Title = "My Blog"
Content = "记录技术、生活和想法。"

[[menu.main]]
identifier = "posts"
name = "文章"
url = "/posts/"
weight = 10

[outputs]
home = ["HTML", "RSS", "JSON"]

最重要的是 baseURL,它应该和实际访问域名一致。

新增第一篇文章

创建文章:

cd /opt/blog
hugo new posts/hello-hugo.md

编辑 content/posts/hello-hugo.md

---
title: "Hello Hugo"
date: 2026-05-26T17:00:00+08:00
draft: false
---

第一篇文章。这个博客由 Hugo 生成,并由 Caddy 托管。

注意 draft: false。如果还是 draft: true,默认构建不会发布这篇文章。

构建并发布

执行:

hugo -s /opt/blog -d /var/www/blog --cleanDestinationDir

这会把 /opt/blog 里的源码构建到 /var/www/blog

因为 /var/www/blog 归日常用户所有,所以不需要每次发布后再 chown 给 Caddy。Caddy 能读这些文件即可。

配置 Caddy

编辑 /etc/caddy/Caddyfile

blog.example.com {
    root * /var/www/blog
    encode gzip zstd
    file_server
}

格式化并重载配置:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo systemctl reload caddy

如果 DNS 和防火墙都正确,访问:

https://blog.example.com

Caddy 会自动把 HTTP 跳转到 HTTPS,并自动维护证书。

做一个一键发布命令

为了减少日常操作,可以创建一个发布脚本:

sudo tee /usr/local/bin/publish-blog >/dev/null <<'EOF'
#!/usr/bin/env bash
cd /opt/blog || exit 1
hugo -s /opt/blog -d /var/www/blog --cleanDestinationDir
EOF
sudo chmod 755 /usr/local/bin/publish-blog

之后发布只需要:

publish-blog

如果只是新增或修改文章,不需要重载 Caddy。Caddy 服务的是静态目录,文件更新后会直接生效。

只有修改 /etc/caddy/Caddyfile 时,才需要:

sudo systemctl reload caddy

日常写作流程

新增文章:

cd /opt/blog
hugo new posts/my-new-post.md
nano content/posts/my-new-post.md
publish-blog

修改文章:

cd /opt/blog
nano content/posts/my-new-post.md
publish-blog

删除文章:

cd /opt/blog
rm content/posts/my-new-post.md
publish-blog

核心原则是:

  • 文章源文件放在 /opt/blog/content/posts/
  • 发布产物生成到 /var/www/blog
  • 不直接编辑 /var/www/blog

常见问题

Caddy 申请证书失败

优先检查:

  • 域名是否解析到服务器公网 IP。
  • 80/443 是否对公网开放。
  • 是否有其他进程占用了 80/443
  • Caddyfile 里的域名是否写错。

查看日志:

journalctl -u caddy --no-pager -n 100

Hugo 构建失败

常见原因是 Hugo 版本太旧,主题要求更高版本。

检查版本:

hugo version

如果版本过旧,升级到 Hugo 官方 extended 版本。

修改文章后网页没变化

确认是否重新发布:

publish-blog

也可以确认 Caddy 服务的目录:

grep -n "root" /etc/caddy/Caddyfile

root 后面的目录应该是 Hugo 的输出目录,例如 /var/www/blog

发布时遇到权限错误

如果看到 Hugo 无法写入 /var/www/blog,通常是目录归属不对。修正为日常发布用户即可:

sudo chown -R "$USER:$USER" /var/www/blog
find /var/www/blog -type d -exec chmod 755 {} \;
find /var/www/blog -type f -exec chmod 644 {} \;

Caddy 不需要拥有这些文件,只要能读取即可。

总结

这套部署方式的核心是:

  • /opt/blog 保存 Hugo 源码和文章。
  • /var/www/blog 保存 Hugo 生成后的静态网站。
  • /var/www/blog 归发布用户所有,Caddy 只读。
  • Caddy 负责 HTTP/HTTPS 和静态文件服务。
  • 日常写作只需要编辑 Markdown,然后运行 publish-blog

这样配置后,个人博客的维护成本很低:写文章、构建、发布,HTTPS 交给 Caddy 自动处理。