<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DNS on Sisy's Blog</title><link>https://blog.sisy.cc/tags/dns/</link><description>Recent content in DNS on Sisy's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Fri, 17 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.sisy.cc/tags/dns/index.xml" rel="self" type="application/rss+xml"/><item><title>自部署 Moe-Counter</title><link>https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/</link><pubDate>Fri, 17 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/</guid><description>&lt;img src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cover.png" alt="Featured image of post 自部署 Moe-Counter" /&gt;&lt;!-- markdownlint-disable MD033 --&gt;
&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;起因是看到 osu! 玩家 &lt;a class="link" href="https://osu.ppy.sh/users/1856463" target="_blank" rel="noopener"
 &gt;IamKwaN&lt;/a&gt; 的 profile 上有一个很像 shields.io 风格的访问计数器，让我想起非常多人都喜欢在自己的博客或者 GitHub Profile 上挂一个类似的 hit counter 来统计访问量。&lt;/p&gt;
&lt;p&gt;&lt;img alt="IamKwaN’s profile" class="gallery-image" data-flex-basis="640px" data-flex-grow="267" height="197" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/bitly.png" width="526"&gt;&lt;/p&gt;
&lt;p&gt;他这个计数器点击之后会跳转到一个 &lt;code&gt;bit.ly&lt;/code&gt; 托管的短链接（估计是为了隐藏真实链接防止屏蔽之类的），最终重定向落在一个叫 &lt;a class="link" href="https://s05.flagcounter.com/more/T31K/" target="_blank" rel="noopener"
 &gt;Flag Counter&lt;/a&gt; 的服务上，显示他这个 profile 关于访问次数和访客所属地区分布的详细信息。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flag Counter" class="gallery-image" data-flex-basis="205px" data-flex-grow="85" height="1146" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/flag-counter.png" srcset="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/flag-counter_hu_f4ac3c2a35b33450.png 800w, https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/flag-counter.png 981w" width="981"&gt;&lt;/p&gt;
&lt;p&gt;我还是蛮喜欢这个小巧的 mini counter 的，比起他们普通的 flag counter 要美观很多。这个服务免费、很完善，且似乎自带一定的流量控制，短时间内重复访问不会多次计数————美中不足之处就是这个服务不是开源的（）。于是我就自己注册账号生成了一个唯一链接，结果又发现一个头疼的问题：&lt;/p&gt;
&lt;p&gt;&lt;img alt="My Pageviews Case" class="gallery-image" data-flex-basis="723px" data-flex-grow="301" height="77" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/my-pageviews.png" width="232"&gt;&lt;/p&gt;
&lt;p&gt;访问次数很少的时候，这个图片的长度居然与访问次数 6 位数时看起来差不多长！这样右边就有一坨空白，而且官方给的 url 链接配置参数里也没有什么能控制图片宽度的选项，感觉有点逆天。。。&lt;/p&gt;
&lt;p&gt;于是我试图找到类似的 website hit counter 的开源替代品，结果回想起来很多 booru 站点甚至包括 Steam，都有类似 n 个二次元人物手举数字牌子的访问计数器，最后终于找到了 &lt;a class="link" href="https://github.com/journey-ad/Moe-Counter" target="_blank" rel="noopener"
 &gt;Moe-Counter&lt;/a&gt; 这个项目（没想到居然是国人写的项目）。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://github.com/journey-ad/Moe-Counter" target="_blank" rel="noopener"
 &gt;Moe-Counter&lt;/a&gt; 是一个支持多种主题的萌萌计数器，支持直接用他们自己的服务器和已部署到公网的 WebUI 生成一个唯一 id 的链接来用，另外 README 里也写了本身支持 Docker 部署。&lt;/p&gt;
&lt;p&gt;看起来搭建不算复杂，但当我真正想把它跑在自己的服务器上、对公网开放访问、挂上自定义域名、提供 HTTPS 安全连接的时候，才发现从&amp;quot;能跑&amp;quot;到&amp;quot;能用&amp;quot;之间还有不少坑要踩。不过正好能顺便学一下用 Nginx 反代一个服务，以及用 Certimate 来自动化管理 HTTPS 证书，另外似乎还有一些 Cloudflare DNS 配置的细节也需要注意，还能练练 Docker compose ———— 嗯嗯。。。不错的实践机会。&lt;/p&gt;
&lt;p&gt;那么下面就开始吧！&lt;/p&gt;
&lt;h2 id="预备步骤"&gt;预备步骤
&lt;/h2&gt;&lt;p&gt;先拉下来熟悉一下结构。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/journey-ad/Moe-Counter.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; Moe-Counter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ll
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="项目结构"&gt;项目结构
&lt;/h3&gt;&lt;p&gt;项目结构比较清晰，几个关键目录和文件：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;路径&lt;/th&gt;
 &lt;th&gt;用途&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;views/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;页面模板（Pug），首页展示逻辑在这里&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;assets/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;静态资源，包括各种主题的数字图片&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;utils/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;工具函数，主题加载逻辑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;db/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;数据库适配层（SQLite / MongoDB）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;入口文件，路由定义&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.env.example&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;环境变量配置模板&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Docker 构建文件&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Docker Compose 配置&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看样子还能自己加主题，只要在 &lt;code&gt;assets/&lt;/code&gt; 里放一套 1-9 的数字图片，然后在 &lt;code&gt;views/index.pug&lt;/code&gt; 里加个选项提供到 WebUI 首页上就行了。&lt;/p&gt;
&lt;p&gt;现代的 Docker Compose 已经（详见&lt;a class="link" href="https://docs.docker.com/compose/intro/compose-application-model/#the-compose-file" target="_blank" rel="noopener"
 &gt;这篇&lt;/a&gt;和&lt;a class="link" href="https://docs.docker.com/reference/compose-file/" target="_blank" rel="noopener"
 &gt;这篇&lt;/a&gt;）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;弃用了 &lt;code&gt;docker-compose.yml&lt;/code&gt; 文件名，改使用 &lt;code&gt;compose.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;compose 文件中也不推荐再使用 &lt;code&gt;version&lt;/code&gt; 字段了，直接写服务定义就行了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过这个项目里还是用的一些旧标准，不过也不影响使用————虽然我还是把这两个都改掉了。&lt;/p&gt;
&lt;h3 id="配置一下"&gt;配置一下
&lt;/h3&gt;&lt;p&gt;env 先复制一份：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp .env.example .env
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; 文件的主要配置项：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这个 URL 是给生成的访问计数器图片链接用的，必须是公网可访问的 URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# WebUI 中的各个示例计数器图片也都是基于这个 URL 拼接的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 后面会用到 Cloudflare 反代，所以这里直接填最终访问的 URL 就行了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;APP_SITE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://moe.sisy.cc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;APP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;DB_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sqlite&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;DB_INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它还支持 MongoDB，不过如果只是个人用，感觉 SQLite 完全够了。&lt;/p&gt;
&lt;p&gt;因为我考虑后续需要编辑项目内容（增加访问次数去重逻辑、添加自定义主题之类的），所以不能直接 &lt;code&gt;docker pull&lt;/code&gt; 官方镜像，而是要从源码本地构建。另外，他这个 &lt;code&gt;docker-compose.yml&lt;/code&gt; 的内容本来也唐完，因此 &lt;code&gt;compose.yaml&lt;/code&gt; 也得改，先看看原先的 &lt;code&gt;docker-compose.yml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 已经不推荐写 version 了&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;moe-counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 得加一条 image 指定构建目标是本地镜像&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3000:3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/app/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 额，首先这不优雅，其次你不是有 .env 文件了么&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;APP_PORT=3000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DB_TYPE=sqlite&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;改成下面这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;moe-counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;moe-counter:local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;moe-counter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3000:3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 注意这里，后面用 Nginx 反代后会更优雅一些&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/app/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样就差不多能起了。&lt;/p&gt;
&lt;h2 id="实现公网可达"&gt;实现公网可达
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker logs -f moe-counter &lt;span class="c1"&gt;# 也可以不看日志，这个服务比较简单。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://localhost:3000 &lt;span class="c1"&gt;# 应该返回 HTTP/1.1 200 OK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://localhost:3000 &lt;span class="c1"&gt;# 或者用 Termius 的 Port Forwarding 直接在浏览器看效果也行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时不仅能在 VPS 本地 curl 访问，而且其实已经可以成功从远端使用 &lt;code&gt;http://&amp;lt;服务器 IP&amp;gt;:3000&lt;/code&gt; 访问到 WebUI 了，只不过还没有域名和 HTTPS，比较丑陋。&lt;/p&gt;
&lt;p&gt;而且直接用 3000 端口将服务暴露出去也不太漂亮，后面用 Nginx 反代一下，把 80/443 的流量转到 3000，这样就能在单端口开多个服务，非常优雅。&lt;/p&gt;
&lt;p&gt;不过在 Nginx 配置之前，先把这个服务的 3000 端口绑定在回环地址上，这样外网就无法直接访问了，所有流量都得走 Nginx 反代进来，也更安全。调整一下 compose 配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;moe-counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;127.0.0.1:3000:3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;依旧验证：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:3000 &lt;span class="c1"&gt;# return HTTP/1.1 200 OK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="实现域名解析"&gt;实现域名解析
&lt;/h2&gt;&lt;p&gt;既然已经能从公网访问了，下一步就是把域名指过来。&lt;/p&gt;
&lt;h3 id="cloudflare-dns-配置"&gt;Cloudflare DNS 配置
&lt;/h3&gt;&lt;p&gt;DNS 服务用的是 Cloudflare，所以在 Cloudflare 里添加一条 A 记录：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;类型&lt;/th&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;内容&lt;/th&gt;
 &lt;th&gt;代理状态&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;moe&lt;/td&gt;
 &lt;td&gt;114.514.91.69&lt;/td&gt;
 &lt;td&gt;仅 DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;访问 &lt;code&gt;http://moe.sisy.cc&lt;/code&gt;，发现无法连接。这时反应过来浏览器访问域名时默认连的是 80 端口，而我的服务跑在 3000。自建服务跑在非标端口时，中间必须有一层反代来转发流量。&lt;/p&gt;
&lt;p&gt;不过 Cloudflare DNS 侧已经无需再动，接下来把 Nginx 反代调通就完成域名解析了。&lt;/p&gt;
&lt;h3 id="nginx-反代"&gt;Nginx 反代
&lt;/h3&gt;&lt;p&gt;解决端口问题的标准做法是用 Nginx 做反向代理：浏览器访问 80/443 → Nginx 接住 → 转发到本地 3000 → Moe-Counter 处理 → 原路返回。由于暂时没有证书，我们的思路就是先监听 80，确认反代没问题后再配置证书，然后把 Nginx 配置也同步升级到 443 与 HTTPS 支持。&lt;/p&gt;
&lt;p&gt;先安装一下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo apt install -y nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;新建 &lt;code&gt;/etc/nginx/sites-available/moe-counter&lt;/code&gt; 来放本站的 Nginx 配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;moe.sisy.cc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;启用并重载：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo ln -s /etc/nginx/sites-available/moe-counter /etc/nginx/sites-enabled/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nginx -t &lt;span class="c1"&gt;# 测试配置正确&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl reload nginx &lt;span class="c1"&gt;# 热重载新配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时浏览器访问 &lt;code&gt;http://moe.sisy.cc&lt;/code&gt; 就能返回 WebUI了，HTTP 就通了，接着往下做 HTTPS。&lt;/p&gt;
&lt;h2 id="https-证书与-acme-自动化"&gt;HTTPS: 证书与 ACME 自动化
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Certimate Workflow" class="gallery-image" data-flex-basis="246px" data-flex-grow="102" height="806" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-workflow.png" srcset="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-workflow_hu_ef745fdd8656b323.png 800w, https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-workflow.png 827w" width="827"&gt;&lt;/p&gt;
&lt;p&gt;HTTPS 这一步，感觉真是懒得渐进式优化了，直接上 ACME 自动化证书维护吧。横向对比了一圈，最终还是决定使用 &lt;a class="link" href="https://github.com/usual2970/certimate" target="_blank" rel="noopener"
 &gt;Certimate&lt;/a&gt; 来管理证书。瞧瞧这优雅的工作流！（虽然我由于时间原因把工作流中的错误处理节点删了，等后续有机会再加回来吧）&lt;/p&gt;
&lt;h3 id="为什么不用-certbot"&gt;为什么不用 Certbot？
&lt;/h3&gt;&lt;p&gt;Certbot 是最常用的 Let&amp;rsquo;s Encrypt 客户端，单机单域名场景下非常省事——一条命令就能申请证书并自动改写 Nginx 配置。但我选择了 &lt;a class="link" href="https://github.com/usual2970/certimate" target="_blank" rel="noopener"
 &gt;Certimate&lt;/a&gt;，主要出于几个考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DNS-01 验证&lt;/strong&gt;：HTTP-01 验证需要 CA 直接访问 80 端口，而且没法签发通配符证书。DNS-01 只需要在 DNS 里加一条 TXT 记录，完全不走 HTTP 流量，还支持通配符证书。并且 Certimate 对 Cloudflare 的 DNS API 支持得很好，能自动增删改查 TXT 记录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集中管理&lt;/strong&gt;：其他域名、服务器需要证书，Certimate 可以统一管理，不需要每台机器都装一遍 Certbot。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web 面板&lt;/strong&gt;：比起 Certbot 的命令行，有个面板看着更直观，工作流的触发记录、证书状态一目了然。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定时任务&lt;/strong&gt;：据可靠消息，Certimate 的定时任务应该比 Certbot 的更可靠一些。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然 Certbot 本身完全够用，如果你只有一个域名、一台服务器，用 Certbot 反而更简单。&lt;/p&gt;
&lt;h3 id="部署-certimate"&gt;部署 Certimate
&lt;/h3&gt;&lt;p&gt;Certimate 本身也是一个 Web 应用，为优雅性，自然是用 Docker 跑。因为它能 SSH 你的服务器、调用 Cloudflare API 改 DNS，权限很大，并且 README 里提供了初始 Admin 账户的邮箱和密码，所以&lt;strong&gt;绝对不能把管理面板暴露到公网&lt;/strong&gt;，否则被攻击就惨了。&lt;/p&gt;
&lt;p&gt;先拉一下 Docker Compose。注意最好不要用官网的一键起容器的脚本，因为它会把面板暴露在 8090 端口上，直接暴露在公网是非常危险的。我们要改成只绑定在回环地址上，需要拉下来之后先改 compose 文件，这样外网就无法访问了，所有流量都得走 SSH 隧道进来。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p ~/certimate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/certimate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -O https://raw.githubusercontent.com/certimate-go/certimate/refs/heads/main/docker/docker-compose.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv docker-compose.yml compose.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vim compose.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;改一下回环和版本号，其他默认：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# version: &amp;#34;3.0&amp;#34; 不要这个&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;certimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;certimate/certimate:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;certimate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# - 8090:8090 不要这个&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;127.0.0.1:8090:8090&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/app/pb_data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;-p 127.0.0.1:8090:8090&lt;/code&gt; 保证面板只能从服务器本机访问。管理时，通过 SSH 端口转发就行了。保存之后直接起面板：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker logs -f certimate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:8090
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我用的访问终端是 Termius，在 Port Forwarding 里加一条 Local 规则：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;值&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Label&lt;/td&gt;
 &lt;td&gt;Certimate&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Local Port number&lt;/td&gt;
 &lt;td&gt;8090&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Local address&lt;/td&gt;
 &lt;td&gt;127.0.0.1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Intermediate Host&lt;/td&gt;
 &lt;td&gt;Host Name&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Destination Host&lt;/td&gt;
 &lt;td&gt;127.0.0.1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Destination Port&lt;/td&gt;
 &lt;td&gt;8090&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;保持 SSH 连接后，在本地浏览器打开 &lt;code&gt;http://127.0.0.1:8090&lt;/code&gt; 就能进入 Certimate 面板。断开 SSH 后面板自动不可达，安全性拉满。&lt;/p&gt;
&lt;h3 id="熟悉一下-certimate-面板"&gt;熟悉一下 Certimate 面板
&lt;/h3&gt;&lt;p&gt;进来之后第一件事当然是先改掉默认的 Admin 账户和密码了。设置里还有一些全局的 ACME 配置项，基本不太需要动，之后写新的工作流的时候，直接细调工作流内局部的配置就行。&lt;/p&gt;
&lt;p&gt;Certimate 的核心功能是&lt;strong&gt;工作流&lt;/strong&gt;，它把申请证书、部署证书、续期检查这些操作都抽象成一个个节点，然后串成一个流程。我们的思路就是写一个工作流来实现以下流程的自动化：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;申请证书 → 部署到 Nginx → 定时续期 (→ 检查和续签时发现错误 → 告警)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;至于其他页面，都可以视为工作流涉及的其他附属内容了。比如证书页面能看到每个证书的详细信息和状态，仪表盘页面能看到每次工作流执行的日志，授权凭证页面能看到工作流中涉及的各种第三方服务的授权信息（Cloudflare API Token、SSH 密钥之类的），等等。&lt;/p&gt;
&lt;h3 id="工作流各节点配置"&gt;工作流各节点配置
&lt;/h3&gt;&lt;p&gt;先从一个默认模板开始就好。&lt;/p&gt;
&lt;h4 id="开始节点"&gt;开始节点
&lt;/h4&gt;&lt;p&gt;&lt;img alt="Cron" class="gallery-image" data-flex-basis="332px" data-flex-grow="138" height="530" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-cron.png" width="735"&gt;&lt;/p&gt;
&lt;p&gt;工作流的触发方式有分手动和自动，那显然是自动啊！所以选一下定时触发。Certimate 的定时任务使用 Cron 表达式，先设置成 &lt;code&gt;30 1 * * *&lt;/code&gt;，每天凌晨 1:30 执行一次。至于为什么每天走一遍工作流，可以看下面的 &lt;a class="link" href="#tls-%e7%bb%ad%e6%9c%9f%e7%ad%96%e7%95%a5" &gt;TLS 续期策略&lt;/a&gt; 部分。&lt;/p&gt;
&lt;h4 id="申请节点"&gt;申请节点
&lt;/h4&gt;&lt;p&gt;&lt;img alt="Apply Certificate" class="gallery-image" data-flex-basis="157px" data-flex-grow="65" height="1060" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-application.png" width="696"&gt;&lt;/p&gt;
&lt;p&gt;首先配置证书颁发给哪些域：这里除了用通配符 &lt;code&gt;*.sisy.cc&lt;/code&gt; 之外，别忘了要加上根域 &lt;code&gt;sisy.cc&lt;/code&gt;。&lt;/p&gt;
&lt;h5 id="验证质询"&gt;验证质询
&lt;/h5&gt;&lt;p&gt;由于 HTTP-01 质询需要 CA 直接访问 80 端口，而且不支持通配符证书，所以质询方式一般选 DNS-01，配合 Cloudflare API 来自动添加 TXT 记录。因此 DNS 提供商选 Cloudflare。&lt;/p&gt;
&lt;p&gt;此时还需要添加一个 Cloudflare 颁发的 API Token 来授权 Certimate 通过这个 API Token 修改 Cloudflare 上的 DNS 记录。&lt;/p&gt;
&lt;h6 id="配置-cloudflare-api-token"&gt;配置 Cloudflare API Token
&lt;/h6&gt;&lt;p&gt;&lt;em&gt;主要还是参考这一篇文章：&lt;a class="link" href="https://developers.cloudflare.com/fundamentals/api/get-started/create-token/" target="_blank" rel="noopener"
 &gt;Cloudflare Fundamentals - Create API token&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;a class="link" href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" rel="noopener"
 &gt;Cloudflare → My Profile → API Tokens → Create Token&lt;/a&gt; 里用 &amp;ldquo;Edit zone DNS&amp;rdquo; 模板创建：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create Cloudflare API Token" class="gallery-image" data-flex-basis="252px" data-flex-grow="105" height="1263" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-create.png" srcset="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-create_hu_15aee6af2883de40.png 800w, https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-create.png 1330w" width="1330"&gt;&lt;/p&gt;
&lt;p&gt;权限保持默认的 &lt;code&gt;Edit&lt;/code&gt; 权限，因为我们需要 Certimate 自动增删改查 TXT 记录来完成 DNS-01 验证。唯一需要改的就是 &lt;code&gt;Zone Resources&lt;/code&gt;，选择 &lt;code&gt;Specific zone&lt;/code&gt; 并且只选 &lt;code&gt;sisy.cc&lt;/code&gt; 这个域，而不是 &lt;code&gt;All zones&lt;/code&gt;，否则一旦 Token 泄露了，攻击者就能更改 Cloudflare 账号下所有域名的 DNS 记录。最小权限原则是几乎零成本的安全加固。&lt;/p&gt;
&lt;p&gt;不过客户端 IP 地址筛选可以不动他，限制一台部署 Certimate 的主机访问的确可以，但没什么必要。TTL 也不需要额外修改，此时点击 &lt;code&gt;继续以显示摘要&lt;/code&gt; 按钮，确认权限和资源范围都正确后，点击 &lt;code&gt;创建令牌&lt;/code&gt;，就能拿到这个 API Token 的值了。把它复制下来，填到 Certimate 的授权凭证里：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add Cloudflare API Token" class="gallery-image" data-flex-basis="222px" data-flex-grow="92" height="1380" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-add.png" srcset="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-add_hu_96211188983e9778.png 800w, https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/cloudflare-api-token-add.png 1281w" width="1281"&gt;&lt;/p&gt;
&lt;h5 id="证书设置"&gt;证书设置
&lt;/h5&gt;&lt;p&gt;Certimate 的证书设置里有几个参数需要选择，简单说一下每个怎么填：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;私钥来源 → 随机生成&lt;/strong&gt;。每次续期都生成新的私钥，万一某一代私钥泄露，影响范围更小，具有向前安全性。&amp;ldquo;复用私钥&amp;quot;是给 HPKP（已废弃）和 DANE/TLSA 这类特殊场景用的，普通网站不需要。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;em&gt;&lt;strong&gt;注意，以下颁发机构均使用 Google Trust Services，情欲先了解ACME EAB 账号获取的相关信息，难以完成则使用 Let&amp;rsquo;s Encrypt 即可。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;颁发机构 → Google Trust Services&lt;/strong&gt;。具体原因依旧可以看下面的 &lt;a class="link" href="#tls-%e7%bb%ad%e6%9c%9f%e7%ad%96%e7%95%a5" &gt;TLS 续期策略&lt;/a&gt; 部分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;颁发机构授权&lt;/strong&gt;：这个由于我们用的是 Google Trust Services，所以需要一个 ACME EAB 账号，拿到这个授权的 ID 和 Key 后把它们填到申请节点的对应字段里。关于如何获取，请参考 &lt;a class="link" href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank" rel="noopener"
 &gt;使用 Public CA 和 ACME 客户端请求证书&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有效期 → 47 天&lt;/strong&gt;。具体原因依旧可以看下面的 &lt;a class="link" href="#tls-%e7%bb%ad%e6%9c%9f%e7%ad%96%e7%95%a5" &gt;TLS 续期策略&lt;/a&gt; 部分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;首选链 → 留空/默认&lt;/strong&gt;。以前有人为了兼容老旧 Android 手动选链到 DST Root CA X3，但那条链 2024 年 9 月已经过期了，现在默认的 ISRG Root X1 兼容所有主流设备。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ACME 配置文件 → classic（默认）&lt;/strong&gt;。classic 对应长有效期的传统证书。另一个选项 &lt;code&gt;shortlived&lt;/code&gt; 是 7 天有效期的超短期证书，对自动化系统稳定性要求极高，个人项目没必要用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻止 CSR 包含主题通用名称 → 不勾选&lt;/strong&gt;。这个选项会禁止在 CSR 里包含通配符证书的通用名称（CN），只把域名放在 Subject Alternative Name (SAN) 里。这个选项是为了兼容一些老旧 CA 的奇葩要求的，现代 CA 都没问题，所以不选。&lt;/p&gt;
&lt;h5 id="重复申请"&gt;重复申请
&lt;/h5&gt;&lt;p&gt;设置为当上次申请证书成功后、且证书剩余有效期大于 17 天，再次运行工作流时跳过此申请节点。具体原因依旧可以看下面的 &lt;a class="link" href="#tls-%e7%bb%ad%e6%9c%9f%e7%ad%96%e7%95%a5" &gt;TLS 续期策略&lt;/a&gt; 部分。&lt;/p&gt;
&lt;h4 id="踩坑容器文件隔离"&gt;踩坑：容器文件隔离
&lt;/h4&gt;&lt;p&gt;起初以为“部署节点”部分不怎么需要动，结果Certimate 第一次执行工作流后，虽然面板提示证书签发成功，路径是 &lt;code&gt;/etc/ssl/certimate/cert.crt&lt;/code&gt; 和 &lt;code&gt;/etc/ssl/certimate/cert.key&lt;/code&gt;，但是我直接在 Nginx 配置里填这两个路径，&lt;code&gt;nginx -t&lt;/code&gt; 直接报错：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cannot load certificate &amp;#34;/etc/ssl/certimate/cert.crt&amp;#34;: BIO_new_file() failed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(SSL: error:80000002:system library::No such file or directory)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;排查了一下，发现问题是，事实上证书写在了 Certimate 容器内部的文件系统里，宿主机根本看不到。Docker 容器的文件系统和宿主机是完全隔离的，除非做了 &lt;code&gt;volumes&lt;/code&gt; 挂载，否则两边的 &lt;code&gt;/etc/ssl/certimate/&lt;/code&gt; 是两个毫无关系的目录。然而即使挂载到宿主机，每次重新申请证书后，也无法触发宿主机上的 Nginx 重新加载，这样就需要在 Certimate 中配置一个 Webhook，还需要自己写一个脚本来监听这个 Webhook，收到通知后再执行 &lt;code&gt;nginx -t &amp;amp;&amp;amp; systemctl reload nginx&lt;/code&gt;，感觉极其麻烦。（理论上应该可以通过“部署后事件”来解决）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：在 Certimate 的工作流里改用 &lt;strong&gt;SSH 部署&lt;/strong&gt;节点，让 Certimate 通过 SSH 把证书从容器内部推送到宿主机的真实路径。操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在宿主机上生成一对 SSH 密钥：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen -t ed25519 -f ~/.ssh/certimate_deploy -N &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; -C &lt;span class="s2"&gt;&amp;#34;certimate-deploy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat ~/.ssh/certimate_deploy.pub &amp;gt;&amp;gt; ~/.ssh/authorized_keys
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat ~/.ssh/certimate_deploy &lt;span class="c1"&gt;# 用于填充到 Certimate 面板里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Certimate 部署节点中，部署目标选择 SSH&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主机提供商授权中输入服务器 IP、端口、用户，以及上一步生成的 SSH 私钥内容。&lt;/p&gt;
&lt;p&gt;&lt;img alt="SSH Config" class="gallery-image" data-flex-basis="357px" data-flex-grow="149" height="937" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-ssh-config.png" srcset="https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-ssh-config_hu_fcb839aaa33633c4.png 800w, https://blog.sisy.cc/p/%E8%87%AA%E9%83%A8%E7%BD%B2-moe-counter/img/certimate-ssh-config.png 1397w" width="1397"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;证书和证书密钥的输出路径还填 &lt;code&gt;/etc/ssl/certimate/cert.crt&lt;/code&gt; 和 &lt;code&gt;/etc/ssl/certimate/cert.key&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;再次执行工作流，这次证书成功落到宿主机的目录下，Nginx 终于能读到了。&lt;/p&gt;
&lt;h3 id="升级-nginx-配置为-https"&gt;升级 Nginx 配置为 HTTPS
&lt;/h3&gt;&lt;p&gt;证书到位后，把 Nginx 配置升级为 HTTPS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Redirect HTTP to HTTPS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;moe.sisy.cc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# HTTPS main service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;moe.sisy.cc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certimate/cert.crt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certimate/cert.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Recommended SSL params
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_session_timeout&lt;/span&gt; &lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;测试并重载：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo nginx -t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl reload nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;访问 &lt;code&gt;https://moe.sisy.cc&lt;/code&gt;，浏览器里的证书锁和证书详情应该都会出现，这样 HTTPS 就大功告成了！&lt;/p&gt;
&lt;h2 id="tls-续期策略"&gt;TLS 续期策略
&lt;/h2&gt;&lt;p&gt;我申请到的证书有效期是 47 天，截至我写这篇博客的时候，只有 Google Trust Services 一家提供了 47 天有效期的证书。秒选啊秒选。&lt;/p&gt;
&lt;p&gt;关于为什么现代网络正在推行 90 -&amp;gt; 47 天的更短期证书，可以参考 &lt;a class="link" href="https://cabforum.org/working-groups/server/baseline-requirements/requirements/#632-certificate-operational-periods-and-key-pair-usage-periods" target="_blank" rel="noopener"
 &gt;CA/Browser Forum 上投票决定的结果&lt;/a&gt; ，在这之前提出并推动此事的&lt;a class="link" href="https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/bvWh5RN6tYI" target="_blank" rel="noopener"
 &gt;这篇文章&lt;/a&gt;，以及&lt;a class="link" href="https://www.bleepingcomputer.com/news/security/ssl-tls-certificate-lifespans-reduced-to-47-days-by-2029/" target="_blank" rel="noopener"
 &gt;这篇分享此事的博客&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;简单来说，2025 年早些时候，Apple 公司提出了一项缩短证书有效期的动议，该动议得到了 Sectigo、Google Chrome 团队和 Mozilla 的支持。该提案将在未来四年内逐步缩短证书的有效期，从截至 2025 年的 398 天需求，在 2029 年 3 月之前缩短到 47 天需求。这个提案的目标是最大限度地降低因证书数据过期、加密算法过时以及长期暴露于泄露凭证而带来的风险。此外，它还鼓励企业和开发者利用自动化手段来更新和轮换 TLS 证书，从而降低网站运行在过期证书上的可能性。&lt;/p&gt;
&lt;p&gt;我的证书工作流的时间策略为：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;参数&lt;/th&gt;
 &lt;th&gt;值&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;工作流触发频率&lt;/td&gt;
 &lt;td&gt;每天 1 次&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;提前续期阈值&lt;/td&gt;
 &lt;td&gt;15 天&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;ldquo;每天触发&amp;quot;听起来很频繁，但 Certimate 会先检查证书的剩余有效期，没到阈值就跳过，不会真给 CA 发请求。这样设置的好处是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;失败后有足够的重试窗口——如果某次续期因为网络抖动失败了，明天还会再试。设 15 天阈值意味着有 15 次重试机会。&lt;/li&gt;
&lt;li&gt;失败后证书还有足够的剩余有效期，可以有充足的时间手动介入修复问题，不至于证书过期了才发现。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果把触发频率设成 30 天，万一到了续期的那一次恰好失败，下一次触发时证书已经过期了——47 天的证书根本扛不住 30 天的触发间隔。&lt;/p&gt;
&lt;p&gt;续期时间的把控则是另一种思路：既要保证充分利用证书的有效期，又要留足时间介入修复问题。根据大佬的经验法则，续期阈值设为&lt;strong&gt;证书有效期的 1/3&lt;/strong&gt; 左右，平衡利用率和安全缓冲。对我来讲，47 ÷ 3 ≈ 15 天，刚好。&lt;/p&gt;
&lt;p&gt;另外强烈建议在 Certimate 里配一个&lt;strong&gt;失败通知&lt;/strong&gt;（邮件、Webhook 等），这样续期失败时能第一时间收到告警，不至于证书悄悄过期了几天才发现（额，虽然我还没配）。&lt;/p&gt;
&lt;h2 id="最终架构"&gt;最终架构
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[浏览器]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ HTTPS (443)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Cloudflare DNS]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ HTTPS (443)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Nginx] ── Listening 80/443, TLS managed by Certimate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ HTTP (Reverse Proxy to 3000)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Docker: Moe-Counter] ── 127.0.0.1:3000, SQLite Persistent Volume
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Docker: Certimate] ── 127.0.0.1:8090, managed by SSH Port Forwarding
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ DNS-01 (Cloudflare API) Certimate in Docker deploys certs to host via SSH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Google Trust Services] [Host /etc/ssl/certimate/]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;整个架构里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公网只开放 80/443 和 22（SSH），Moe-Counter 的 3000 和 Certimate 的 8090 都只绑在回环地址上，外网不可达。&lt;/li&gt;
&lt;li&gt;HTTPS 证书由 Certimate 自动申请、部署、续期，不需要人工介入。&lt;/li&gt;
&lt;li&gt;Cloudflare 提供 CDN 和 DDoS 防护，SSL 模式用最安全的 Full (Strict)。&lt;/li&gt;
&lt;li&gt;通过 SSH 端口转发访问 Certimate 面板，管理界面零暴露。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;涉及到三种（理论上如果配了告警应该还有更多）第三方授权服务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare API Token：用于 Certimate 的 DNS-01 验证，权限最小化只允许编辑 &lt;code&gt;sisy.cc&lt;/code&gt; 的 DNS 记录。&lt;/li&gt;
&lt;li&gt;SSH 密钥：用于 Certimate 的 SSH 部署节点，用于把证书从容器内部推送到宿主机的真实路径。&lt;/li&gt;
&lt;li&gt;Google Trust Services EAB：CA 需要 EAB（外部账户绑定）来验证申请者的身份，Certimate 也支持配置 EAB 的 Key ID 和 Key Secret 来完成绑定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从&amp;quot;能跑&amp;quot;到&amp;quot;能用&amp;rdquo;，确实比想象中多了不少环节。但每一层都有其存在的意义：Docker 隔离运行环境，Nginx 统一入口和端口管理，Certimate 自动化证书生命周期，Cloudflare 提供边缘防护&amp;hellip;&amp;hellip; 搞清楚每一层在干什么之后，以后再部署别的服务也是同一套模式，而且熟悉了会快很多，换个容器就行了。&lt;/p&gt;
&lt;p&gt;长吁。。终于把这个博客的访问计数器部署好了，虽然过程比想象中复杂了不少，不过成果是令人欣慰的。之后就可以在自部署的环境中生成各种主题的访问计数器图片了！&lt;/p&gt;
&lt;p&gt;喂，别忘了配置一下 Certimate 的续期失败通知啊（&lt;/p&gt;
&lt;img src="https://moe.sisy.cc/@:sisy-blog-moe?name=%3Asisy-blog-moe&amp;theme=gelbooru-h&amp;padding=2&amp;offset=0&amp;align=top&amp;scale=1&amp;pixelated=1&amp;darkmode=auto" alt=":sisy-blog-moe" /&gt;</description></item><item><title>将博客迁移至 Hugo</title><link>https://blog.sisy.cc/p/%E5%B0%86%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3-hugo/</link><pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.sisy.cc/p/%E5%B0%86%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3-hugo/</guid><description>&lt;img src="https://blog.sisy.cc/p/%E5%B0%86%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3-hugo/img/cover.png" alt="Featured image of post 将博客迁移至 Hugo" /&gt;&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;我的博客之前用的是 &lt;a class="link" href="https://astro.build/" target="_blank" rel="noopener"
 &gt;Astro&lt;/a&gt; 的某个主题，但是 Astro 作为博客构建工具的使用体验并不好，因为它本身的设计哲学实际更侧重于为文档站点服务。&lt;/p&gt;
&lt;p&gt;前段时间 MoonWX 在 Q 群里分享 &lt;a class="link" href="https://ntzyz.space/" target="_blank" rel="noopener"
 &gt;ntzyz&lt;/a&gt; 大佬的博客站，感觉大佬这个很简洁干练，不是很花哨，但是又很有技术博客的 feeling，而且并不丑，相反还蛮好看的。（顺带一提这位还是 BGP 大神，之后有空还想 peer 一下）&lt;/p&gt;
&lt;p&gt;博客底部标注了由 &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
 &gt;Hugo&lt;/a&gt; 构建，使用 &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
 &gt;Hugo Theme Stack&lt;/a&gt; 主题，而且 Hugo Theme Stack 的 Github README 里提供了一个 &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;starter&lt;/a&gt; 项目，可以作为 template 使用。那么我就直接用这个 starter 来搭建我的博客吧，希望能有个更好的使用体验。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Stack 主题在 README 中提供了&lt;a class="link" href="https://stack.cai.im/" target="_blank" rel="noopener"
 &gt;双语文档&lt;/a&gt;，这里不多赘述。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="迁移过程"&gt;迁移过程
&lt;/h2&gt;&lt;h3 id="托管到-github-pages"&gt;托管到 GitHub Pages
&lt;/h3&gt;&lt;p&gt;首先按照常规操作，想要托管到 GitHub Pages 的话，使用 starter 的 template 来创建新的仓库的时候，需要注意仓库的命名最好为 &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;，这里我就是 &lt;code&gt;sisypheovo.github.io&lt;/code&gt;，以便于直接被识别为 GitHub Pages 的托管仓库。&lt;/p&gt;
&lt;p&gt;创建好仓库之后，在仓库的 &lt;code&gt;Settings -&amp;gt; Pages&lt;/code&gt; 页面中，将 &lt;code&gt;Build and deployment&lt;/code&gt; 中的 &lt;code&gt;Source&lt;/code&gt;选项设置为 &lt;code&gt;GitHub Actions&lt;/code&gt;，以便自动化部署，毕竟 starter 里已经提供了 GitHub Actions 的 workflow 文件了。&lt;/p&gt;
&lt;p&gt;这样就完成了托管，在 &lt;a class="link" href="https://sisypheovo.github.io/" target="_blank" rel="noopener"
 &gt;https://sisypheovo.github.io/&lt;/a&gt; 就可以直接访问到博客了，只不过内容还全是一些示例和模板，而且侧边栏之类的相比 ntzyz 大佬的博客也差远了.&lt;/p&gt;
&lt;h3 id="使用自定义域名"&gt;使用自定义域名
&lt;/h3&gt;&lt;p&gt;常规来讲，要将任何普通内容部署到自己的域名下，最常用的做法肯定还是通过面板，比如 1Panel 之类的。购买并配置域名之后，把编译产物上传到服务器上就行了，然后配一配 DNS 解析、HTTPS 和反向代理等等配置就行了。&lt;/p&gt;
&lt;p&gt;但是因为是使用 GitHub Pages 的静态博客，而且域名托管在 Cloudflare，就我知道的而言，有两种方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 CNAME 文件配置自定义域名，并在 Cloudflare 中添加 CNAME 记录，指向 &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;，服务器只做 DNS 解析。&lt;/li&gt;
&lt;li&gt;服务器使用反向代理，将自定义域名的请求转发到 GitHub Pages 的 URL 上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我个人感觉第一种方法更简单一些，毕竟不需要额外的服务器来做反向代理了，而且有 Github Pages 提供的各种自动化和安全服务，还有免费的 HTTPS 和证书。&lt;/p&gt;
&lt;p&gt;第二种就是可以自己做缓存、加速，自定义后端逻辑之类的，还能隐藏真实托管源，但是我也不太需要这些功能，所以就直接用第一种方法了。&lt;/p&gt;
&lt;p&gt;首先去到 Cloudflare 的&lt;a class="link" href="https://dash.cloudflare.com/" target="_blank" rel="noopener"
 &gt;管理面板&lt;/a&gt;，在 &lt;code&gt;域注册&lt;/code&gt; -&amp;gt; &lt;code&gt;管理域&lt;/code&gt; 中选择对应的域名（我这里就是 &lt;code&gt;sisy.cc&lt;/code&gt;），进入到域名的管理页面中，在 &lt;code&gt;DNS&lt;/code&gt; -&amp;gt; &lt;code&gt;记录&lt;/code&gt; 选项卡中添加一条 CNAME 记录：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;类型&lt;/th&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;内容&lt;/th&gt;
 &lt;th&gt;代理状态&lt;/th&gt;
 &lt;th&gt;TTL&lt;/th&gt;
 &lt;th&gt;操作&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CNAME&lt;/td&gt;
 &lt;td&gt;blog&lt;/td&gt;
 &lt;td&gt;sisypheovo.github.io&lt;/td&gt;
 &lt;td&gt;仅 DNS&lt;/td&gt;
 &lt;td&gt;自动&lt;/td&gt;
 &lt;td&gt;编辑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;添加完成之后，还是回到仓库的 &lt;code&gt;Settings -&amp;gt; Pages&lt;/code&gt; 页面中，将 &lt;code&gt;Build and deployment&lt;/code&gt; 中的 &lt;code&gt;Custom domain&lt;/code&gt;选项设置为 &lt;code&gt;blog.sisy.cc&lt;/code&gt;，勾选上 &lt;code&gt;Enforce HTTPS&lt;/code&gt; 选项，这样就完成了自定义域名的配置，过一段时间之后访问 &lt;code&gt;blog.sisy.cc&lt;/code&gt; 就可以访问到博客了。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;顺带一提，如果不是用 GitHub Pages，而是自己搭建的服务器，那么配置自定义域名的方式会有所不同，比如需要在编译产物的根目录下放置 CNAME 文件，内容是 &lt;code&gt;blog.sisy.cc&lt;/code&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="配置"&gt;配置
&lt;/h3&gt;&lt;p&gt;这个主题我说白了用明白还是蛮好用的（等于不好上手吧，额），但是前期配置比较痛苦，因为它各个功能的配置项都比较分散，文档也不全，配置项上的注释也没几个字，很多东西莫名其妙的。&lt;/p&gt;
&lt;h4 id="一些普通的配置解读"&gt;一些普通的配置解读
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/_default/markup.toml&lt;/code&gt; 文件中配置了 Markdown 的渲染方式，使用了 Goldmark 这个渲染器，并且启用了各种扩展功能，比如 Latex、表格、脚注、定义列表等等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/modules.toml&lt;/code&gt;: 这个是 Hugo Modules 的配置文件，里面指定了使用的 Hugo Theme Stack 主题的版本和路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/permalinks.toml&lt;/code&gt; 文件中配置了文章的 URL 结构，比如 posts 的 URL 就是 &lt;code&gt;/posts/:slug/&lt;/code&gt;，也就是 &lt;code&gt;https://blog.sisy.cc/posts/xxx/&lt;/code&gt; 这样的结构；page 的 URL 就是 &lt;code&gt;/:slug/&lt;/code&gt;，也就是 &lt;code&gt;https://blog.sisy.cc/xxx/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/_default/related.toml&lt;/code&gt;: 不知道，看不懂，先放着&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="configtoml"&gt;&lt;code&gt;config.toml&lt;/code&gt;
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;baseurl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://blog.sisy.cc/&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 博客的基础 URL，用于生成链接和资源路径&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;languageCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zh&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 博客的默认语言代码，这里设置为中文&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Sisy&amp;#39;s Blog&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 博客的标题，会显示在浏览器标签页、页脚的标注、头像下面的标题&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Theme i18n support&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# See all available values at https://github.com/CaiJimmy/hugo-theme-stack/tree/master/i18n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;defaultContentLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Set hasCJKLanguage to true if DefaultContentLanguage is in [zh ja ko]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# This will make .Summary and .WordCount behave correctly for CJK languages.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;hasCJKLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Disqus 评论系统的短名称，在 Disqus 上添加站点的时候会生成一个短名称，用于标识这个站点的评论系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;disqusShortname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sisypheovo&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pagerSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="_languagetoml"&gt;&lt;code&gt;_language.toml&lt;/code&gt;
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Rename this file to languages.toml to enable multilingual support&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;中文&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 语言名称，会显示在页面左下角的语言切换器中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;languagedirection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ltr&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 语言的书写方向，ltr 表示从左到右，rtl 表示从右到左&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Sisy&amp;#39;s Blog&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 博客标题，在中文环境下会覆盖掉 config.toml 中的 title 配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c"&gt;# 语言的权重，数值越小优先级越高。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;en&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;English&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;languagedirection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ltr&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Sisy&amp;#39;s Blog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;特别对于权重而言，像这样不是 en 权重最大的话，创建各个博客文章的 index markdown 文件的时候，必须要有 &lt;code&gt;index.en.md&lt;/code&gt;、&lt;code&gt;index.zh.md&lt;/code&gt;，而不是 &lt;code&gt;index.md&lt;/code&gt; 和 &lt;code&gt;index.zh.md&lt;/code&gt;，否则默认的 &lt;code&gt;index.md&lt;/code&gt; 文件会被视为 zh 的版本。也要记得跟普通的 i18n 情况不一样，&lt;code&gt;index.md&lt;/code&gt; 不再是 en 的默认版本了。&lt;/p&gt;
&lt;p&gt;另外，对于如上配置，各语言下内容的 URL 结构为 &lt;code&gt;/xxx/&lt;/code&gt; 和 &lt;code&gt;/en/xxx/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;非内容的各个字符串（比如页脚文本）的翻译，可以通过在 &lt;code&gt;i18n/&lt;/code&gt; 目录下创建对应语言的 TOML 文件来配置，比如 &lt;code&gt;i18n/zh.toml&lt;/code&gt; 中可以配置中文环境下的各种字符串的翻译。默认部分可以直接从示例仓库的&lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack/tree/master/i18n" target="_blank" rel="noopener"
 &gt;这里&lt;/a&gt;复制整个文件过来，非常方便。&lt;/p&gt;
&lt;h4 id="menutoml"&gt;&lt;code&gt;menu.toml&lt;/code&gt;
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 像这样在左侧菜单中添加一个不指向任何页面的链接菜单项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 像这里我就做了一个 RSS 的链接，权重设置为 99，确保它在菜单的最下面显示。不过 icon 需要自己找一找。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Hugo 的 RSS 订阅链接通常是 `/index.xml`，内容是由 Hugo 自动生成的，不需要手动创建。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rss&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RSS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/index.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rss&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 这个很好理解，是显示在头像和简介下方的社交链接，样例也给了两个 icon 状的示范。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GitHub&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/SisypheOvO&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;brand-github&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;关于自定义菜单内容，还可以参考&lt;a class="link" href="https://stack.cai.im/zh/config/menu" target="_blank" rel="noopener"
 &gt;这篇文档&lt;/a&gt;，除了在 &lt;code&gt;menu.toml&lt;/code&gt; 文件中配置以 &lt;code&gt;main&lt;/code&gt; 标识的这种非页面的链接菜单项之外，指向正常页面的菜单项是通过在各个页面（仓库中 &lt;code&gt;/content/page/&lt;/code&gt; 下的各个内容）各自的 &lt;code&gt;index.md&lt;/code&gt; 文件前言部分中的 &lt;code&gt;menu&lt;/code&gt; 字段来配置的。也就是说，各个 page 和每个菜单项是同步创建且一一对应的（除了在 &lt;code&gt;menu.toml&lt;/code&gt; 中配置的项）。如以下这个简单的配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 默认是 main 就行&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 标识这个 page 的排序，需要跨文件管理它们&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;people&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 显示在菜单项左边的图标，默认是 svg 格式，放在 `static/icons/` 目录下&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;需要注意的是，如果想要添加一些自定义布局的页面，还最好需要额外创建一个 &lt;code&gt;layouts/&lt;/code&gt; 目录，在里面放置对应的自定义布局 HTML 文件。&lt;/p&gt;
&lt;p&gt;以下是一个配置好的菜单示例：&lt;/p&gt;
&lt;p&gt;&lt;img alt="菜单示例" class="gallery-image" data-flex-basis="156px" data-flex-grow="65" height="397" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E5%B0%86%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3-hugo/img/menu.png" width="259"&gt;&lt;/p&gt;
&lt;h4 id="paramstoml"&gt;&lt;code&gt;params.toml&lt;/code&gt;
&lt;/h4&gt;&lt;p&gt;这个文件的配置项就比较多了，内容也比较杂乱，比如博客的主页显示、RSS 输出、favicon、文章排序、页脚信息、日期格式、侧边栏信息、文章信息、页面小部件、社交媒体信息、配色方案等等。关于这里配置的指导，在官方文档的&lt;a class="link" href="https://stack.cai.im/zh/config/" target="_blank" rel="noopener"
 &gt;这个 Config 部分&lt;/a&gt;解释的还是比较全面的，可以优先参考。&lt;/p&gt;
&lt;p&gt;以下我挑一些非常适合新手起步的配置项来讲，其他要么已经解释的很清楚，要么就无需过多调整：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;favicon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;img/favicon.png&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 显示在浏览器标签页上的图标，写过 web 都懂&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 显示在所有页脚的内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="c"&gt;# 博客开始运营的年份，最终会显示为 &amp;#34;© 2023 - 2026 博客标题&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;customText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sisy&amp;#39;s blog&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 显示在 since 这一行下方&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 显示在博客右半侧的各种小组件，分为主页显示的和在子页面显示的组件两种&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;homepage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;search&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;archives&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;categories&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tag-cloud&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;toc&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;disqus&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 评论系统提供商&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;各个页面是否开启评论也可以通过在对应文件的前言中添加 &lt;code&gt;comments: false&lt;/code&gt; 来单独控制，是否开启 toc 也是一样的，添加 &lt;code&gt;toc: false&lt;/code&gt; 就行了。&lt;/p&gt;
&lt;p&gt;以下是一个配置好的小组件示例：&lt;/p&gt;
&lt;p&gt;&lt;img alt="小组件示例" class="gallery-image" data-flex-basis="160px" data-flex-grow="66" height="932" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E5%B0%86%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E8%87%B3-hugo/img/widgets.png" width="623"&gt;&lt;/p&gt;
&lt;h3 id="优化"&gt;优化
&lt;/h3&gt;&lt;p&gt;配置阶段就基本完结了，接下来就可以简单优化一下美观性了，比如我很想要 select 元素原始的下拉箭头，可以在 &lt;code&gt;assets/scss/custom.scss&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;menu-bottom-section&lt;/span&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;i18n-switch&lt;/span&gt; &lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kp"&gt;-webkit-&lt;/span&gt;&lt;span class="k"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Stack 主题适合用的两个 icon 源：&lt;a class="link" href="https://tabler.io/icons" target="_blank" rel="noopener"
 &gt;Tabler&lt;/a&gt;、&lt;a class="link" href="https://www.svgrepo.com/" target="_blank" rel="noopener"
 &gt;SVG Repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;这一系列流程下来，博客就基本上安定下来，后续唯一的工作就是将旧的文章转移过来，然后写点新东西了。&lt;/p&gt;</description></item><item><title>使用 Cloudflare + Gmail 搭建邮件转发</title><link>https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/</link><pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/</guid><description>&lt;img src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cover.png" alt="Featured image of post 使用 Cloudflare + Gmail 搭建邮件转发" /&gt;&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;前段时间简单玩了一下 Cloudflare 的&lt;a class="link" href="https://dash.cloudflare.com/" target="_blank" rel="noopener"
 &gt;管理面板&lt;/a&gt;，其中一个 Email Routing 功能能够让用户通过 Cloudflare 的邮件服务器来转发邮件到自己的邮箱里。这一步从操作上实际比较简单，不过可以顺带了解一下邮件转发的原理和一些相关的概念，比如 MX 记录、SPF 记录、DKIM 记录等等。&lt;/p&gt;
&lt;p&gt;后续发现在使用自定义邮箱身份的时候，用其他邮箱来发邮件给别人会带来困惑，因为对方无法确定邮件的来源，所以就在想能不能通过自定义域名来代发邮件呢？（话说为什么我一开始没有想到这个问题。。。一个自定义邮件名称的邮箱能收邮件，就一定可以发邮件，还是很自然的）&lt;/p&gt;
&lt;p&gt;于是陆续把正反向邮件转发都搭建了一下。&lt;/p&gt;
&lt;h2 id="自定义域名代收"&gt;自定义域名代收
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Cloudflare Email Routing" class="gallery-image" data-flex-basis="411px" data-flex-grow="171" height="1013" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-routing.png" srcset="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-routing_hu_f0d23737a4238dd1.png 800w, https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-routing_hu_cde834e2df916088.png 1600w, https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-routing.png 1737w" width="1737"&gt;&lt;/p&gt;
&lt;p&gt;在 Cloudflare 面板的 &lt;code&gt;域注册&lt;/code&gt; -&amp;gt; &lt;code&gt;管理域&lt;/code&gt; 中选择对应的域名（我这里就是 &lt;code&gt;sisy.cc&lt;/code&gt;），进入到域名的管理页面中，在左侧选择 &lt;code&gt;电子邮件&lt;/code&gt; -&amp;gt; &lt;code&gt;电子邮件路由&lt;/code&gt; 选项卡。这个页面如果没用过的话先点击开始使用，进入真正管理电子邮件路由的页面。&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;路由规则&lt;/code&gt;，在 &lt;code&gt;自定义地址&lt;/code&gt; 版块中点击 &lt;code&gt;创建地址&lt;/code&gt;，输入自定义地址（比如以 &lt;code&gt;i@sisy.cc&lt;/code&gt; 代表本人），操作这一栏选择 &lt;code&gt;发送到电子邮件&lt;/code&gt;（另一个选项 &lt;code&gt;发送到 Worker&lt;/code&gt; 是用于将邮件发送到 Cloudflare Worker，以实现复杂邮件处理逻辑的），目标填写自己的 Gmail 邮箱地址，点击保存。这将会创建一个新的邮件转发规则：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;自定义地址&lt;/th&gt;
 &lt;th&gt;操作&lt;/th&gt;
 &lt;th&gt;目标&lt;/th&gt;
 &lt;th&gt;状态&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="mailto:i@sisy.cc" &gt;i@sisy.cc&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;发送到电子邮件&lt;/td&gt;
 &lt;td&gt;&lt;a class="link" href="mailto:sisyphe.dev@gmail.com" &gt;sisyphe.dev@gmail.com&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;活动&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样一来，他人向 &lt;a class="link" href="mailto:i@sisy.cc" &gt;i@sisy.cc&lt;/a&gt; 发送邮件时，邮件将会被转发到 &lt;a class="link" href="mailto:sisyphe.dev@gmail.com" &gt;sisyphe.dev@gmail.com&lt;/a&gt;。这一步的本质是，创建邮件转发规则后，Cloudflare 会自动生成一些 DNS 记录来支持邮件转发功能，包括 MX 记录、TXT 记录 (SPF 记录、DKIM 记录) 等。如下图中，可以看到 Cloudflare 已经自动添加了三条 MX 记录和两条 TXT 记录：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cloudflare Email Routing DNS Records" class="gallery-image" data-flex-basis="335px" data-flex-grow="139" height="1008" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-settings.png" srcset="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-settings_hu_8b8f0237bf32f7a8.png 800w, https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/cloudflare-email-settings.png 1408w" width="1408"&gt;&lt;/p&gt;
&lt;p&gt;三条 MX 记录分别指向 &lt;code&gt;route1.mx.cloudflare.net.&lt;/code&gt;、&lt;code&gt;route2.mx.cloudflare.net.&lt;/code&gt; 和 &lt;code&gt;route3.mx.cloudflare.net.&lt;/code&gt;，优先级分别为 4、88 和 84。这些 MX 记录告诉其他邮件服务器，当有人向 &lt;a class="link" href="mailto:i@sisy.cc" &gt;i@sisy.cc&lt;/a&gt; 发送邮件时，邮件将会被转发到 &lt;a class="link" href="mailto:sisyphe.dev@gmail.com" &gt;sisyphe.dev@gmail.com&lt;/a&gt;。而两条 TXT 记录则分别是 SPF 记录和 DKIM 记录，用于验证邮件的真实性，防止邮件被伪造或被标记为垃圾邮件。&lt;/p&gt;
&lt;h2 id="自定义域名代发"&gt;自定义域名代发
&lt;/h2&gt;&lt;p&gt;&lt;em&gt;简易的说明教程，可以看 &lt;a class="link" href="https://support.google.com/mail/answer/22370?authuser=0" target="_blank" rel="noopener"
 &gt;Gmail 帮助：通过其他地址或别名发送电子邮件&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;邮件代发的原理是，在发送邮件时，使用自定义域名作为发件人地址，并通过某个邮件服务器（比如 Cloudflare 的邮件服务器）来发送邮件。这样一来，收件人就能够看到邮件是从自定义域名发送的，而不是 Gmail 的地址。事实上 Cloudflare 也确实提供了邮件发送功能，只不过要氪金，所以还是从 Gmail 这一边来下手吧。&lt;/p&gt;
&lt;h3 id="添加发送地址"&gt;添加发送地址
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Add Send Address" class="gallery-image" data-flex-basis="1196px" data-flex-grow="498" height="231" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-add-send-address.png" srcset="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-add-send-address_hu_88a3783d20cb4a32.png 800w, https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-add-send-address.png 1152w" width="1152"&gt;&lt;/p&gt;
&lt;p&gt;在 Gmail 设置页面中，进入 &lt;code&gt;账户和导入&lt;/code&gt; 选项卡，找到 &amp;ldquo;发送邮件地址&amp;rdquo; 版块（如上图），点击&amp;quot;添加其他电子邮件地址&amp;quot;。这将会呼出一个弹窗。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add Sender Info Modal" class="gallery-image" data-flex-basis="282px" data-flex-grow="117" height="534" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-add-sender-info.png" width="628"&gt;&lt;/p&gt;
&lt;p&gt;在弹出的窗口中，输入计划用来代发的自定义邮件地址（比如 &lt;a class="link" href="mailto:i@sisy.cc" &gt;i@sisy.cc&lt;/a&gt; ），以及显示名称（比如 Sisy）。点击下一步，进入 SMTP 服务器设置页面。&lt;/p&gt;
&lt;p&gt;&lt;img alt="SMTP Server Settings" class="gallery-image" data-flex-basis="282px" data-flex-grow="117" height="534" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-smtp-settings.png" width="628"&gt;&lt;/p&gt;
&lt;p&gt;这里 SMTP 服务器地址默认会被识别为 Cloudflare 的收件地址 &lt;code&gt;route1.mx.cloudflare.net&lt;/code&gt;，端口 587，但是 Cloudflare Email Routing 只负责收件，它不提供 SMTP 发件服务。&lt;code&gt;route1.mx.cloudflare.net&lt;/code&gt; 是它的接收邮件服务器（MX），不是发件服务器（SMTP）。所以需要改为 Gmail 的 SMTP 服务器地址，例如 &lt;code&gt;smtp.gmail.com&lt;/code&gt; 或 &lt;code&gt;smtp.your-school.edu&lt;/code&gt;，端口还是填 587（TLS 加密发件的标准端口）。&lt;/p&gt;
&lt;p&gt;为什么希望用自定义域名代发邮件，这里却要填 Gmail 的 SMTP 服务器地址？这里的核心逻辑是：Gmail 本身不提供“直接用外部域名发信”的 SMTP 服务。当想用自定义域名发邮件时，Gmail 的机制是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由 Gmail 的服务器（smtp.gmail.com）实际执行发件操作&lt;/li&gt;
&lt;li&gt;但在邮件头里，把“发件人”声明为 &lt;code&gt;i@sisy.cc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里我就拿 &lt;code&gt;smtp.gmail.com&lt;/code&gt; 来管理。用户名和密码则是 Gmail 的账户名和密码，用以验证发件身份，证明这个发件请求确实是由本人授权的。输入完成后点击 &lt;code&gt;添加账号&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Tips: 这里很容易遇到问题，尤其是 Google 账号开启了两步验证的情况下。提示类似于：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;身份验证错误。请检查您的用户名和密码。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;服务器返回错误: &amp;#34;535-5.7.8 Username and Password not accepted. For more information, go to 535 5.7.8 https://support.google.com/mail/?p=BadCredentials 46e7d38a34.3 - gsmtp , code: 535&amp;#34;。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;即登录凭据被拒。这时候需要用到一个叫做 &amp;ldquo;应用专用密码&amp;rdquo; 的东西。Google 账号的安全设置里有一个 &amp;ldquo;应用专用密码&amp;rdquo; 的选项，可以生成一个专门用于第三方应用（比如 Gmail SMTP，额，为什么这算第三方应用）的密码。生成后把这个密码填到上面的密码输入框里，就可以成功验证了。点击&lt;a class="link" href="https://myaccount.google.com/apppasswords" target="_blank" rel="noopener"
 &gt;这个链接&lt;/a&gt;来生成应用专用密码。&lt;/p&gt;
&lt;h3 id="验证地址"&gt;验证地址
&lt;/h3&gt;&lt;p&gt;添加账号之后，Gmail 会向目标自定义邮箱地址（我这里就是 &lt;a class="link" href="mailto:i@sisy.cc" &gt;i@sisy.cc&lt;/a&gt;）发送一封验证邮件。因为之前已经设置了邮件转发，所以这封验证邮件会直接被转发到 Gmail 的收件箱里，这样就又回到了 Gmail。打开这封邮件，点击里面的验证链接，完成验证过程就行。这样一来，就成功把自定义域名添加为 Gmail 的一个发件地址了。之后通过 Gmail 发送邮件时，可以在发件人地址处选择 Gmail 地址或者自定义域名地址两种。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Select Sender" class="gallery-image" data-flex-basis="519px" data-flex-grow="216" height="166" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.sisy.cc/p/%E4%BD%BF%E7%94%A8-cloudflare--gmail-%E6%90%AD%E5%BB%BA%E9%82%AE%E4%BB%B6%E8%BD%AC%E5%8F%91/img/gmail-select-sender.png" width="359"&gt;&lt;/p&gt;</description></item></channel></rss>