Hugo 简介

Hugo 是一个开源的静态站点生成器,支持通过 Markdown 文件快速生成静态 HTML 页面。其主要优势包括:

  1. 构建速度快:Hugo 采用 Go 语言开发,单线程构建速度极快,适合处理大规模内容。
  2. 灵活性强:支持多种主题和模块化设计,用户可根据需求定制站点外观与功能。
  3. 易于部署:生成的静态文件可直接托管于任意 Web 服务器,无需复杂的后端支持。
  4. 学术友好性:Hugo 支持 Markdown 格式,与学术写作常用的编辑工具(如 Obsidian)无缝衔接,便于内容管理和版本控制。

官网导航

官方网站

官方论坛

中文官网

流程概览

Obsidian (创作) → Hugo (本地预览) → Git (推送至 GitHub) → GitHub Actions (自动编译部署) → Nginx (静态服务) → Cloudflare (全球加速与HTTPS) → 全球用户访问

站点初始化

 1# 创建一个新的 Hugo 站点,命名为 404-blog
 2hugo new site 404-blog
 3
 4# 进入新创建的站点目录
 5cd 404-blog
 6
 7# 初始化 Git 仓库,用于版本管理和代码托管
 8git init
 9# 设置 Git 的提交用户信息
10git config user.name "404"
11git config user.email "[email protected]"
12# 暂存所有文件,准备提交
13git add .
14# 提交更改,记录初始化的站点结构
15git commit -m "Initial commit"
16
17# 添加 Hugo PaperMod 主题作为 Git 子模块,方便管理和更新主题
18git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
19# 在配置文件 hugo.toml 中设置主题为 PaperMod
20echo "theme = 'PaperMod'" >> hugo.toml
21
22# (此处手动添加 .gitignore 文件,用于忽略不必要的文件,如 node_modules 或临时文件)
23
24# 暂存所有更改,包括主题和 .gitignore 文件
25git add .
26# 提交更改,记录主题和忽略文件的添加
27git commit -m "Add PaperMod theme and .gitignore file"
28
29# 启动 Hugo 本地服务器,预览站点效果
30hugo server

其中 .gitignore 可参考 PaperMod 作者提供的文件。

# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test

/public
.DS_Store
.hugo_build.lock
resources/_gen/

自动化部署与服务器配置

要实现每次推送到 GitHub 仓库时,自动构建 Hugo 博客并部署到云服务器的 Nginx 下,需要完成以下步骤:

1. 准备云服务器

首先,确保您的云服务器已安装 Nginx:

1# Ubuntu/Debian
2sudo apt update && sudo apt full-upgrade -y
3sudo apt install nginx

2. 配置 Nginx

修改 Nginx 主配置文件:

  1# --- 基本运行环境配置 ---
  2user www-data; # 指定 Nginx 运行的用户,确保权限安全
  3worker_processes auto; # 自动匹配 CPU 核心数,优化并发处理
  4worker_cpu_affinity auto; # 自动绑定 CPU 核心,提升缓存命中率
  5pid /run/nginx.pid; # 定义主进程 ID 文件路径
  6error_log /var/log/nginx/error.log warn; # 设置错误日志路径及警告级别
  7include /etc/nginx/modules-enabled/*.conf; # 引入额外的模块配置文件
  8
  9# --- 事件处理模块配置 ---
 10events {
 11	worker_connections 1024; # 每个进程最大连接数,适配中型流量
 12	multi_accept on; # 启用多连接同时接受,提升并发效率
 13	use epoll; # 使用 epoll 事件模型,优化高并发性能
 14}
 15
 16# --- HTTP 服务核心配置 ---
 17http {
 18	# 性能优化参数
 19	sendfile on; # 启用高效文件传输,适合静态文件
 20	tcp_nopush on; # 优化数据包传输,减少报文数量
 21	tcp_nodelay on; # 禁用 Nagle 算法,降低传输延迟
 22	types_hash_max_size 2048; # 优化 MIME 类型哈希表大小
 23	server_tokens off; # 隐藏 Nginx 版本信息,增强安全性
 24
 25	server_names_hash_bucket_size 64; # 域名哈希桶大小,支持长域名解析
 26
 27	# MIME 类型定义
 28	include /etc/nginx/mime.types; # 引入标准 MIME 类型配置
 29	default_type application/octet-stream; # 默认文件类型为二进制流
 30
 31	# SSL/TLS 安全优化
 32	ssl_protocols TLSv1.2 TLSv1.3; # 启用安全协议,优先 TLS 1.3
 33	ssl_prefer_server_ciphers on; # 优先使用服务器指定的加密套件
 34	ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH; # 高安全加密套件
 35	ssl_session_timeout 1d; # SSL 会话缓存有效期,减少握手开销
 36	ssl_session_cache shared:SSL:10m; # 共享 SSL 会话缓存,适配中型流量
 37	ssl_session_tickets off; # 禁用会话票据,增强安全性
 38	ssl_stapling on; # 启用 OCSP 装订,加速证书验证
 39	ssl_stapling_verify on; # 验证 OCSP 响应,确保安全
 40	resolver 8.8.8.8 8.8.4.4 valid=300s; # 指定 DNS 服务器,用于 OCSP 查询
 41	resolver_timeout 5s; # 设置 DNS 解析超时时间
 42
 43	# 日志记录配置
 44	log_format main # 定义标准日志格式,记录请求详情
 45		'$remote_addr - $remote_user [$time_local] "$request" '
 46		'$status $body_bytes_sent "$http_referer" '
 47		'"$http_user_agent" "$http_x_forwarded_for"';
 48	access_log /var/log/nginx/access.log main buffer=32k flush=5s; # 访问日志,优化写入性能
 49
 50	# Gzip 压缩优化
 51	gzip on; # 启用内容压缩,减少传输数据量
 52	gzip_vary on; # 添加 Vary 头,适配代理缓存
 53	gzip_proxied any; # 对所有代理请求启用压缩
 54	gzip_comp_level 6; # 压缩级别 6,平衡性能与效果
 55	gzip_buffers 16 8k; # 设置压缩缓冲区,优化内存使用
 56	gzip_http_version 1.1; # 最低支持 HTTP 1.1 进行压缩
 57	gzip_min_length 256; # 最小压缩文件长度,避免小文件开销
 58	gzip_types text/plain # 定义支持压缩的文件类型
 59		text/css
 60		application/json
 61		application/javascript
 62		text/xml
 63		application/xml
 64		application/xml+rss
 65		text/javascript
 66		application/x-font-ttf
 67		font/opentype
 68		image/svg+xml;
 69
 70	# 客户端请求限制
 71	client_max_body_size 10m; # 限制请求体大小,适配静态站点
 72	client_body_buffer_size 128k; # 请求体缓冲区,优化上传性能
 73
 74	# 文件缓存优化
 75	open_file_cache max=2000 inactive=20s; # 缓存文件句柄,加速静态文件访问
 76	open_file_cache_valid 30s; # 文件缓存有效性检查周期
 77	open_file_cache_min_uses 2; # 文件至少访问 2 次才缓存
 78	open_file_cache_errors on; # 缓存错误信息,减少重复检查
 79
 80	# Cloudflare 真实 IP 获取
 81	set_real_ip_from 173.245.48.0/20; # Cloudflare IP 范围,用于获取真实 IP
 82	set_real_ip_from 103.21.244.0/22;
 83	set_real_ip_from 103.22.200.0/22;
 84	set_real_ip_from 103.31.4.0/22;
 85	set_real_ip_from 141.101.64.0/18;
 86	set_real_ip_from 108.162.192.0/18;
 87	set_real_ip_from 190.93.240.0/20;
 88	set_real_ip_from 188.114.96.0/20;
 89	set_real_ip_from 197.234.240.0/22;
 90	set_real_ip_from 198.41.128.0/17;
 91	set_real_ip_from 162.158.0.0/15;
 92	set_real_ip_from 104.16.0.0/13;
 93	set_real_ip_from 104.24.0.0/14;
 94	set_real_ip_from 172.64.0.0/13;
 95	set_real_ip_from 131.0.72.0/22;
 96	real_ip_header CF-Connecting-IP; # 使用 Cloudflare 传递的真实客户端 IP
 97
 98	# 引入其他配置文件
 99	include /etc/nginx/conf.d/*.conf; # 加载额外的配置目录
100	include /etc/nginx/sites-enabled/*; # 加载启用的站点配置文件
101}

创建一个站点配置文件:

1sudo vim /etc/nginx/sites-available/404-blog

添加以下配置(根据需要调整):

  1# --- 非 www 域名 HTTP 重定向配置 ---
  2server {
  3	listen 80; # 监听 HTTP 80 端口
  4	server_name 404blog.org; # 匹配非 www 域名
  5
  6	access_log /var/log/nginx/404blog_redirect_access.log
  7		main
  8		buffer=16k; # 访问日志记录,设置缓冲
  9	error_log /var/log/nginx/404blog_redirect_error.log warn; # 错误日志记录,警告级别
 10
 11	return 301 https://www.404blog.org$request_uri; # 永久重定向到 HTTPS 的 www 域名
 12}
 13
 14# --- 非 www 域名 HTTPS 重定向配置 ---
 15server {
 16	listen 443 ssl; # 监听 HTTPS 443 端口并启用 SSL
 17	http2 on; # 启用 HTTP/2 协议,提升性能
 18	server_name 404blog.org; # 匹配非 www 域名
 19
 20	include /etc/nginx/snippets/ssl-404blog.conf; # 引入预配置的 SSL 设置
 21
 22	access_log /var/log/nginx/404blog_ssl_redirect_access.log
 23		main
 24		buffer=16k; # 访问日志记录
 25	error_log /var/log/nginx/404blog_ssl_redirect_error.log warn; # 错误日志记录
 26
 27	return 301 https://www.404blog.org$request_uri; # 永久重定向到 HTTPS 的 www 域名
 28}
 29
 30# --- 主域名 www 的 HTTP 重定向配置 ---
 31server {
 32	listen 80; # 监听 HTTP 80 端口
 33	server_name www.404blog.org; # 匹配 www 域名
 34
 35	access_log /var/log/nginx/404blog_www_http_access.log main buffer=16k; # 访问日志记录
 36	error_log /var/log/nginx/404blog_www_http_error.log warn; # 错误日志记录
 37
 38	return 301 https://$host$request_uri; # 重定向到 HTTPS,保留主机名和路径
 39}
 40
 41# --- 主域名 www 的 HTTPS 服务配置 ---
 42server {
 43	listen 443 ssl; # 监听 HTTPS 443 端口并启用 SSL
 44	http2 on; # 启用 HTTP/2 协议,加速传输
 45	server_name www.404blog.org; # 匹配 www 域名
 46
 47	include /etc/nginx/snippets/ssl-404blog.conf; # 引入 SSL 相关配置片段
 48
 49	access_log /var/log/nginx/404blog_www_https_access.log
 50		main
 51		buffer=32k
 52		flush=5s; # 访问日志,设置更大缓冲和刷新时间
 53	error_log /var/log/nginx/404blog_www_https_error.log warn; # 错误日志记录
 54
 55	# 安全相关的 HTTP 响应头配置
 56	add_header X-Content-Type-Options "nosniff" always; # 防止浏览器嗅探 MIME 类型
 57	add_header X-Frame-Options "DENY" always; # 禁止页面被嵌入到 iframe 中
 58	add_header X-XSS-Protection "1; mode=block" always; # 启用 XSS 防护机制
 59	add_header Referrer-Policy
 60		"strict-origin-when-cross-origin"
 61		always; # 限制跨域时的 Referrer 信息
 62	add_header Content-Security-Policy
 63		"default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; font-src 'self' https://cdn.jsdelivr.net; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self';"
 64		always; # CSP 策略,增强安全性
 65	add_header Strict-Transport-Security
 66		"max-age=63072000; includeSubDomains; preload"
 67		always; # HSTS 强制 HTTPS 连接
 68	add_header Permissions-Policy
 69		"geolocation=(), microphone=(), camera=()"
 70		always; # 限制浏览器敏感权限
 71
 72	root /var/www/404-blog; # 设置网站根目录,指向静态文件
 73	index index.html; # 默认首页文件
 74
 75	if ($request_method !~ ^(GET|HEAD)$) { # 限制请求方法,仅允许 GET 和 HEAD
 76		return 405; # 其他方法一律返回 405 错误
 77	}
 78
 79	# 基本路由配置
 80	location / {
 81		try_files $uri $uri/ =404; # 尝试匹配文件或目录,无匹配则返回 404
 82	}
 83
 84	# 特殊文件(如 robots.txt 和 favicon.ico)的处理
 85	location ~ ^/(robots\.txt|favicon\.ico)$ {
 86		access_log off; # 关闭访问日志,减少磁盘 I/O
 87		log_not_found off; # 关闭未找到文件的日志记录
 88		expires 30d; # 设置 30 天缓存过期时间
 89		add_header Cache-Control "public, immutable"; # 设置公开且不可变缓存
 90	}
 91
 92	# RSS 和 Sitemap 文件缓存策略
 93	location ~* \.(xml|json)$ {
 94		expires 12h; # 缓存时间设为 12 小时
 95		add_header Cache-Control "public, must-revalidate"; # 公开缓存但需验证
 96	}
 97
 98	# 图片等静态资源的缓存策略
 99	location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
100		expires 30d; # 图片资源缓存 30 天
101		add_header Cache-Control "public, immutable"; # 不可变缓存,适合 CDN
102		access_log off; # 关闭访问日志
103	}
104
105	# CSS 和 JS 文件缓存策略
106	location ~* \.(css|js)$ {
107		expires 7d; # 缓存时间为 7 天
108		add_header Cache-Control "public, immutable"; # 不可变缓存
109		access_log off; # 关闭访问日志
110	}
111
112	# 字体文件缓存策略
113	location ~* \.(woff|woff2|ttf|eot|otf)$ {
114		expires 90d; # 字体资源缓存 90 天
115		add_header Cache-Control "public, immutable"; # 不可变缓存
116		access_log off; # 关闭访问日志
117	}
118
119	# HTML 文件缓存策略
120	location ~* \.html$ {
121		expires 1h; # HTML 文件缓存 1 小时
122		add_header Cache-Control "public, must-revalidate"; # 公开缓存但需验证
123	}
124
125	# 禁止访问隐藏文件和敏感目录
126	location ~ /\. {
127		deny all; # 拒绝访问以 . 开头的文件或目录
128		access_log off; # 关闭访问日志
129		log_not_found off; # 关闭未找到日志
130	}
131
132	# 自定义错误页面配置
133	error_page 404 /404.html; # 404 错误页面指向自定义文件
134	error_page 500 502 503 504 /50x.html; # 5xx 错误页面指向自定义文件
135
136	location = /404.html {
137		root /var/www/404-blog; # 404 页面文件所在根目录
138		internal; # 仅限内部访问
139	}
140
141	location = /50x.html {
142		root /var/www/404-blog; # 5xx 页面文件所在根目录
143		internal; # 仅限内部访问
144	}
145}

启用站点并创建部署目录:

1sudo ln -s /etc/nginx/sites-available/your-blog /etc/nginx/sites-enabled/
2
3sudo mkdir -p /var/www/your-blog
4
5sudo chown -R $USER:$USER /var/www/your-blog  # 确保部署用户有权限
6
7sudo nginx -t  # 测试配置
8
9sudo systemctl reload nginx

3. 准备 SSH 密钥对

为了让 GitHub Actions 能够安全地连接到您的服务器,创建一个专用的 SSH 密钥:

1ssh-keygen -t rsa -b 4096 -C "github-actions-deploy" -f ~/.ssh/github-actions

在您的服务器上,将公钥添加到 authorized_keys:

1cat ~/.ssh/github-actions.pub >> ~/.ssh/authorized_keys

4. 配置 GitHub 仓库 Secrets

将私钥和其他必要信息添加到 GitHub 仓库的 Secrets 中:

  1. 在 GitHub 仓库页面,点击 “Settings” → “Secrets and variables” → “Actions” → “New repository secret”
  2. 添加以下 Secrets:
    • SSH_PRIVATE_KEY: 您生成的私钥内容(整个文件内容)
    • SERVER_HOST: 您服务器的 IP 地址或域名
    • SERVER_USERNAME: SSH 登录用户名
    • SERVER_PORT: SSH 端口(通常是 22)
    • SERVER_DEPLOY_PATH: 部署路径(如 /var/www/your-blog)

5. 创建 GitHub Actions 工作流

在您的 Hugo 项目中创建 .github/workflows/deploy.yml 文件:

 1name: Deploy Hugo site to Server
 2
 3on:
 4  push:
 5    branches:
 6      - master
 7
 8jobs:
 9  build-and-deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - name: Checkout
13        uses: actions/checkout@v3
14        with:
15          submodules: true
16          fetch-depth: 0
17
18      - name: Setup Hugo
19        uses: peaceiris/actions-hugo@v2
20        with:
21          hugo-version: "latest"
22          extended: true
23
24      - name: Build
25        run: hugo --minify
26
27      - name: Install SSH Key
28        uses: shimataro/ssh-key-action@v2
29        with:
30          key: ${{ secrets.SSH_PRIVATE_KEY }}
31          known_hosts: "just-a-placeholder"
32
33      - name: Adding Known Hosts
34        run: ssh-keyscan -H -p ${{ secrets.SERVER_PORT }} ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
35
36      - name: Deploy with rsync
37        run: |
38          rsync -avz --delete -e "ssh -p ${{ secrets.SERVER_PORT }}" \
39            ./public/ \
40            ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:${{ secrets.SERVER_DEPLOY_PATH }}

上述工作流在每次推送到主分支时触发,执行代码检出、Hugo 构建及静态文件部署等步骤,最终通过 rsync 工具将 public 目录同步到服务器。

图片管理策略

因为我这边更期望在 Obsidian 中进行编辑,所以只研究了两种图片管理方式:

1. 本地存储方案

此方案来源于 Hugo 论坛的 jmooring 最佳解决方案:

Obsidian 配置

1{
2  "attachmentFolderPath": "attachments",
3  "useMarkdownLinks": true,
4  "newLinkFormat": "absolute"
5}

对应了设置中的如下配置,我图片存储在了 images 下:

Hugo 配置

 1[markup.goldmark.renderHooks.image]
 2enableDefault = true
 3
 4[markup.goldmark.renderHooks.link]
 5enableDefault = true
 6
 7[[module.mounts]]
 8source = 'assets'
 9target = 'assets'
10
11[[module.mounts]]
12source = 'attachments'
13target = 'assets/attachments'

目录结构

attachments/
├── documents/
│   ├── a.pdf
│   └── b.pdf
└── images/
    ├── kitten-a.jpg
    └── kitten-b.jpg

2. 图床存储方案

此时可以使用 Obsidian 的插件 Image Upload Toolkit。特别感谢作者,当我想使用此方案时,暂时还不支持 R2 上传,作者了解后,很快进行了适配。

我的配置如下:

上图重 Attachment location 需为 /,否则不适配插件会有找不到图片的异常信息。

上图所需要的配置在 R2 配置界面均可以找到:

存储桶设置中,推荐自定义域,当然前提时你需要在 Cloud flare 进行域名托管。才可以使用子域。

缓存加速

本设计主要聚焦于 Ng 和 Cloudflare 的配置,操作简单,基本通过点击即可完成。重点内容通过截图展示,一目了然。如果您想深入学习更专业的使用方法,建议参考相关教程。

1. Ng 加速配置

使用上述 Ng 配置已经处理好了大多数缓存配置和安全策略。

2. Cloud flare 缓存

将域名托管到 Cf 后,可以新增 Cache Rules。

具体规则如下:

3. SSL/TLS 加密

CloudFlare 为用户提供的源服务器证书是由 Cloudflare 签名的免费 TLS 证书,该域名证书属于泛域名证书,最长支持 15 年,主要用于源服务器和 Cloudflare 之间的流量加密。但是这个证书属于自签名证书,证书链不完整,缺少根证书。

使用如下网址下载 CloudFlare 的根证书/证书链文件,并上传到您的源 Web 服务器。请注意, CloudFlare 提供了 ECC 和 RSA 版本两个文件,具体下载哪一个参考上图,根据自己申请源服务器证书时选择的“私钥类型”来决定。

Cloud flare 根证书下载

RSA 下载

根证书下载上传后 Ng 需要对应的配置,我是放到了 snippets 配置片段下,进行统一的引用。