这两天把自己的博客搭起来了,地址是 040012.xyz。

一开始的想法很简单:找个自己的地方,放学习笔记、项目复盘,还有一些平时折腾东西留下的记录。文章可以慢慢写,先把地方准备好。

最后选的是 Hugo + PaperMod + Nginx,在 Mac 上写文章、生成网页,再上传到 Ubuntu 服务器。域名和 HTTPS 也一起配好了。

过程不算复杂,但从“服务器本机能访问”到“别人打开域名也能访问”,中间还是踩了几个坑。趁着还记得,记录一下。

为什么最后选了 Hugo

我最先考虑的其实是服务器资源。

选型时,我是按 2 核、2 GB 内存的小服务器来考虑的,还想在上面部署自己的 Java 语音购物项目。如果博客再常驻一个后端、再加数据库,后面几个服务一起跑,内存就会比较紧。

这里也只是部署前的判断。我还没有做过博客和 Java 项目同时运行的负载测试,不能因为博客现在能打开,就说整台服务器能稳定承载多少人。

当时主要看了几种方案:

方案我当时的考虑
WordPress有网页后台,扩展也多,但我暂时只想写文章,不需要那么多功能
Halo后台管理方便,但还要再运行一个 Java 服务,对当前服务器来说需要考虑内存余量
Typecho如果一定要浏览器后台写文章,我会优先考虑它,PHP 加 SQLite 的组合也比较轻
Hexo同样可以生成静态网页,主题选择丰富,也是合适的方案
Hugo静态部署,PaperMod 的样子又刚好符合我想要的简洁风格,最后就选了它

我喜欢 Hugo 的地方是,生成完之后就是一堆 HTML、CSS 和 JavaScript 文件。服务器用 Nginx 把这些文件提供出去就行,不需要一直运行 Hugo,也不用为了博客单独维护数据库。

当然,这也有取舍。Hugo 没有自带一个让我登录进去发文章、拖拽装修页面的后台。文章要在电脑上写,发布时再生成和上传。常用设置改配置文件,更深的页面调整需要接触 CSS 和模板。

目前我主要想记录技术内容,这个写作方式可以接受。文章也能用 Typora 这类编辑器来写,不需要一直对着纯文本。

这套博客是怎么运行的

我一开始还把“服务器上部署 Hugo”和“服务器上一直运行 Hugo”混在了一起。实际用起来,分工很清楚:

Mac 上写 Markdown
        ↓
Hugo 生成 public 文件夹
        ↓
上传到服务器 /var/www/html/
        ↓
Nginx 提供网页,浏览器通过域名访问

本地的 hugo server 只是方便预览。正式网站已经上传到服务器,所以本地终端关掉、电脑关机,都不会让线上博客停止运行。

这点对我来说挺省心。以后博客的运行环境主要就是 Nginx,写作环境则留在自己的电脑上。

先在 Mac 上把页面跑起来

我用 Homebrew 安装了 Hugo:

brew install hugo
hugo version

然后创建博客,下载 PaperMod 主题。下面这组命令是首次创建时用的,已经建好的目录不用重复执行:

hugo new site ~/my-blog
cd ~/my-blog
git clone --depth 1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

我就遇到过“目录已经存在且不为空”的提示。检查后发现博客和主题都已经下载好了,直接继续配置就行,没必要强行重新创建。

博客的设置放在 hugo.toml 里。目前主要是这些:

baseURL = 'https://040012.xyz/'
languageCode = 'zh-cn'
defaultContentLanguage = 'zh'
title = 'ZYJ 的博客'
theme = 'PaperMod'

[params]
  defaultTheme = 'auto'
  ShowReadingTime = true
  ShowCodeCopyButtons = true

[params.homeInfoParams]
  Title = 'ZYJ 的博客'
  Content = '记录技术、学习与生活。'

这是现在启用 HTTPS 之后的配置。最开始还没有证书时,baseURL 用的是 HTTP。

当前 Hugo 对 languageCode 会给出弃用提示,后面整理配置时需要按新版文档迁移到 locale。这次构建没有因此失败,所以我先把网站部署流程走通了。

本地预览用:

cd ~/my-blog
hugo server -D --disableFastRender

浏览器打开 http://localhost:1313/ 就能看效果。-D 表示预览时也显示草稿。

第一篇测试文章加进去后,我碰到过首页没有显示文章的情况。排查发现,文章文件存在,直接访问文章页也正常,原配置完整构建后同样能生成首页文章列表。问题出在当时运行的预览没有更新完整。

最后重启预览,并加上 --disableFastRender。这次排查也提醒我:页面没显示,不一定就是文章格式写错了,先确认文件、生成结果和预览状态,别急着到处改配置。

服务器装好了 Nginx,为什么外面还是进不来

服务器用的是 Ubuntu 24.04 LTS。我先检查了正在监听的端口,确认 80、443 没有被其他服务占用,再安装 Nginx:

apt update && apt install -y nginx

这里的服务器命令按管理员权限的 root shell 写法展示,所以没有加 sudo;这是权限说明,不代表公开真实 SSH 登录账号。使用普通管理账号时,应按需加 sudo。

安装完,在服务器里测试:

curl -I http://127.0.0.1

返回了 200 OK,但电脑浏览器访问却没有成功,起初还显示了 502。

我没有直接把 502 当成 Nginx 配置错误,而是先看访问日志:

tail -n 20 /var/log/nginx/access.log

里面只有服务器本机那次请求,看不到刚才浏览器的访问。接着在 Mac 上绕过代理,直接测试公网 IP 的 80 端口,结果是连接超时。

又检查了两项:

ufw status verbose
ss -lntp '( sport = :80 )'

UFW 没有启用,Nginx 也确实监听在 0.0.0.0:80 和 [::]:80。

最后在阿里云控制台看安全组,才发现入方向规则里根本没有开放 80 端口。加上允许 TCP 80 的规则后,Mac 再请求就收到了 200 OK,浏览器也能打开 Nginx 欢迎页。

这是这次最有用的一次排查:服务器本机访问成功,只能说明本机这一段正常,不能直接说明公网链路也通了。

以后碰到类似问题,我会分别看服务有没有运行、监听地址对不对、系统防火墙和云安全组有没有放行,而不是只盯着网页报错。

从欢迎页换成自己的博客

域名指向服务器后,打开 040012.xyz,最初看到的还是 Welcome to nginx!。

这说明域名访问已经到了 Nginx,但博客文件还没有上传。

我先在 Mac 上设置好 baseURL,再执行构建和上传。下面保留当时的手动发布流程,其中 Nginx 的默认网站目录是已经检查过的 /var/www/html/。真实服务器地址和登录账号已替换为文档示例:192.0.2.10 属于专供文档使用的 TEST-NET-1 地址段,your-ssh-user 是用户名占位符,不能直接用于实际部署。

实际操作时需替换两个变量,并确认该账号有目标目录的写入权限;如果没有,应上传到该账号可写的暂存目录,再由管理员安装到网站目录。这里没有假定普通用户可以直接写入 /var/www/html/。

cd ~/my-blog
SERVER_IP='192.0.2.10'
SERVER_USER='your-ssh-user'
hugo --minify && scp -r public/. "$SERVER_USER@$SERVER_IP":/var/www/html/

hugo --minify 生成用于发布的网页,scp 再把 public 里的内容传到服务器。中间用 &&,是为了构建成功后再上传。

上传的是生成后的网页,不需要把整套 Hugo 开发环境搬到服务器。

现在这个方式适合我手动更新博客,但它不会删除服务器上的旧文件。以后如果删除文章或者修改文章文件名,需要同步处理服务器上的旧页面,否则旧网址可能仍然可以访问。后面文章多了,再考虑专用部署目录和更完整的同步流程。

网站已经能访问了,为什么还要配 HTTPS

我一开始也问过这个问题:输入域名已经能看到文章,为什么还要继续折腾?

后来理解了,能打开和连接是否加密是两回事。HTTP 没有为传输内容提供加密和完整性保护,HTTPS 则能让浏览器验证域名证书,并保护浏览器与服务器之间传输的内容。

我的博客目前没有登录和支付,HTTPS 的紧迫程度没那么高。但既然准备长期使用、把网址分享出去,还是配好比较合适。证书可以免费申请,当前这套环境也不需要因此升级服务器。

实际操作先是在阿里云安全组放行 TCP 443,同时保留 80。443 用于 HTTPS 访问;这次使用的 HTTP-01 域名验证仍然需要 80,原来的 HTTP 入口也可以负责跳转。

然后明确 Nginx 的域名配置:

server_name 040012.xyz;

原来的 server_name _; 是占位值。之前域名能打开,是因为这个站点被设置成了默认站点,不代表已经明确配置了这个域名。

修改前备份配置,改完检查语法,通过后再重新加载:

nginx -t && systemctl reload nginx

证书工具使用 Certbot 和它的 Nginx 插件:

apt update && apt install -y certbot python3-certbot-nginx
certbot --nginx -d 040012.xyz --redirect

Certbot 验证域名后,申请了 Let’s Encrypt 证书,并自动修改 Nginx 配置。--redirect 让 HTTP 请求自动跳转到 HTTPS。

签发完成后,实际验证到的结果是:

  • https://040012.xyz/ 返回 200 OK,证书校验正常。
  • http://040012.xyz/ 返回 301,跳转到 HTTPS。

我还做了一次续期演练,并检查定时任务:

certbot renew --dry-run
systemctl status certbot.timer --no-pager

模拟续期成功,定时任务也是 enabled、active (waiting)。它会定期检查是否需要续期,并不是每次运行都重新申请证书。

最后把 Mac 上 hugo.toml 的 baseURL 改为 https://040012.xyz/,重新生成、上传,检查文章链接也保持 HTTPS,这一步才算收尾。

以后怎么写,以及还要维护什么

写新文章时,在 Mac 上执行:

cd ~/my-blog
hugo new content posts/文章文件名.md

正文写在 content/posts/ 下面。准备发布时,把文章顶部的 draft 改为 false,日期也不要设到未来。图片可以放到 static/images/,正文里用 /images/图片名 引用。

本地预览满意后,再运行构建和上传命令。发布之后,用线上地址检查一遍文章和图片。

运维方面,目前主要关心域名续费、系统更新、磁盘空间,以及证书续期是否正常。自动续期已经配置好,但以后改了域名解析、端口规则或 Nginx 配置,还是要重新检查,不能把“自动”理解成永远不用管。

源文件备份我还没有正式配置。前面搭建时只有测试文章,所以暂时没急着做。开始积累正式内容后,准备把文章、图片、配置和自定义主题文件一起做独立备份,至少不要只放在同一台电脑上。

服务器上的 public 网页可以重新生成,但它不能代替原始 Markdown、配置和主题修改记录。

折腾完这一轮,我觉得这套方案目前挺适合我:页面简单,写作方式也直接,服务器端需要维护的东西比较少。代价是没有现成的网页管理后台,主题定制也需要自己接触配置和样式。

先把它用起来。接下来比继续换主题更重要的,应该是往里面认真写几篇文章。