2026-07-11 20:39:00
近几年我越来越不爱用搜索引擎找博客了,搜一个技术问题,前几页经常是采集站、营销软文和批量生成的内容。
社交平台也差不多。算法觉得您喜欢什么,就追着喂什么。刷起来很省事,想顺着一个人的文章慢慢读下去却不容易。
我认识的那些博客作者其实还在写,只是不太容易在搜索和信息流里碰到他们。于是,我做了一个叫「博客大联盟」的网站。
博客大联盟 是一个独立博客目录,顺便聚合每个博客的 RSS。
它的前身是我手工维护的朋友博客友链聚合 peng.you。后来实在嫌手工维护太麻烦,干脆写了个小系统,把博客名片和 RSS 聚合都自动化了。
每个博客都有一张名片,上面是名称、作者、简介、标签和最新文章。系统定时读取 RSS,新文章按照发布时间排列。没有猜你喜欢,也没有什么神奇的推荐算法,您可以翻目录、看标签,或者直接搜博客名称和作者。

目前暂时没打算把这货做成另一个信息流平台。先把各处的独立博客放到一个目录里,让大家能互相找到,这就够了。
整个流程是登录、填写资料、自动检查、验证域名,最后等管理员审核。
打开 bo.ke,使用 GitHub 或 Google 一键登录即可,不需要另外注册账号和记一套新密码。第一次登录时,系统会自动创建账号,只读取昵称、头像和邮箱这些基本资料。您也可以通过我的邀请链接注册哦。
登录后点击「提交博客」,填写下面这些信息:
一个账号最多可以收录 5 个博客。
目前加入的条件如下:
example.com 或 blog.example.com,或者使用 PSL 列表的二三级域名,比如 example.github.io。提交后,系统会检查博客链接能不能打开、RSS 能不能解析、头像能不能加载。页面会列出每一项的结果,有问题就改完再提交。全部通过以后,才会进入域名所有权验证。
自动检查通过后,需要证明这个域名确实属于您。完成验证,提交才会进入管理员审核。这一步稍微麻烦一点,下面仔细说。
DNS TXT、DNS CNAME 和文件验证三选一。页面会生成一个 token,下面的写法只是示例,实际操作时直接复制页面给出的内容。
假设您的域名是 example.com,那么有三种验证方式:
在域名的 DNS 解析中给您博客的域名添加一条 TXT 记录,内容如下:
bo-ke-verify=<您的 token>
不同 DNS 服务商的填写方式不太一样,有些要给 TXT 内容加引号,有些不需要,添加后记得看一眼服务商显示的完整记录哦。
加完解析记录后可以用 dig 或 nslookup 检查解析结果,确认 TXT 记录已经生效。
DNS 记录生效可能需要几分钟到几小时。等解析生效后,回到验证页面点击「开始验证」即可。
为域名添加下面这条 CNAME 记录:
bo-ke-verify.example.com. CNAME verify-<token>.bo.ke.
不同 DNS 服务商的填写方式不太一样。有些只需要填 bo-ke-verify,有些要填完整域名,有些要给 CNAME 域名最后加点号,添加后记得看一眼服务商显示的完整记录哦。
加完以后可以用 dig 或 nslookup 检查解析结果,确认 CNAME 指向了 verify-<token>.bo.ke.。等解析生效后,回到验证页面点击「开始验证」即可。
如果不方便修改 DNS,也可以使用文件验证。在网站根目录放置下面这个纯文本文件:
bo-ke-verify-<token>.txt
文件内容就是 token 本身。验证页面会直接生成文件供您下载,上传到网站根目录后,确认下面这样的地址可以公开访问:
https://example.com/bo-ke-verify-<token>.txt
如果您的博客开了 WAF、防火墙或反爬虫的话,记得放行带有 bo.ke 关键字的 User-Agent,不然验证请求可能直接被挡在门外。
无论选择哪一种方式,验证通过后都不要删除 DNS 记录或验证文件哦。
系统每 7 天重新检查一次。连续 3 次找不到验证记录,博客就会和账号解除关联。这个设计主要是防止域名转手以后,旧站长还能修改新站的资料。
所以不要在验证成功后顺手删掉记录,不然三个星期后还得重新认领。
以前 peng.you 里的数据有一部分导入了新站,管理员偶尔也会手工添加博客。这些条目还没有绑定账号,页面上会显示「认领此博客」。
如果发现自己的博客已经在里面,点击认领,再走一遍域名验证。通过后条目就会归到您的账号名下,以后可以自己修改资料。
您可以在博客首页、友链页面或者任意内页,放一个指向 https://bo.ke/ 的链接。
互链不是收录条件,不放也能正常提交。系统会定期检查,找到链接后会提高博客的展示权重。
可以参考我们的友情链接页面,里面也放了博客大联盟。
审核通过后,博客会出现在目录和标签页里,也可以按照名称、域名或作者搜索。博客名片下面会显示最新一篇文章。
如果填写了 RSS / Atom 地址,系统会每 6 小时抓取一次,每个博客保留最近 20 篇。全站文章也有提供 RSS 和 Atom 订阅,丢进任何一个支持 RSS 的阅读器就能订阅。您也可以定制自己的专属订阅,屏蔽不想看到的博客。
如果你博客的资料有变化可以直接在控制台编辑,不用重新审核。
如果不想使用这个平台,在控制台删除博客即可,博客资料、聚合文章和认领记录会一起清除。
博客大联盟有一套公开 JSON API,可以读取博客列表、全站最新文章和单个博客的最近文章。接口不用认证,支持跨域,做展示页或小工具都够用。数据每 6 小时才更新一次,别按秒轮询哦。
品牌资源页面放了深浅两色的 Logo 和徽标,提供 SVG、PNG、WebP 格式。颜色也可以在页面上改,做友链图标时比较省事。
访问统计用的是自建 Plausible,没有统计 Cookie,也不做跨站跟踪。
如果您也在写博客,欢迎来加入博客大联盟。
登录、填资料、验证域名,几分钟就能弄完,剩下的交给管理员审核。
如有问题可以随时联系我们。
2026-07-07 10:00:00
这篇文章从一次博客重构说起,反思这些年从 WordPress、静态博客到 AI vibe coding 的不断折腾。技术和工具当然能让博客更快、更漂亮,但它们终究只是载体。真正能留下来的,不是框架、封面图或动画,而是一个人真实写下的文字和思考。与其追逐每一个新工具,不如回归初心,慢慢写博客,让文字重新成为博客的核心。
从大学开始算起,本人写博客也快 20 年了。
这 20 年里,我见过各种各样的博客站点,也见过不少小伙伴从入坑到弃坑,再入坑,再弃坑。有的人写博客,是为了记录技术和生活上的点滴;有的人写博客,是为了 SEO,提高排名,顺便推广自己的产品;有的人写博客,是为了赚钱;更多的人,可能和我一样,纯属折腾。
从折腾 WordPress 主题和插件,到折腾静态化 HTML 博客程序,再到如今用 AI vibe coding 定制自己的博客系统,工具一代一代换,框架一轮一轮火。等夜深人静的时候,你大概也应该停下来问问自己:写博客的目的到底是什么?
是为了把网站做得更炫吗?是为了用上最新的框架吗?是为了让 Lighthouse 跑满分吗?还是为了把脑子里那些乱七八糟的想法,尽量诚实地留下来?
前段时间,我又开始折腾自己的博客首页。
再加上最近 AI 作图流行,我还给每篇文章都做了统一风格的封面图。刚开始看着还挺像那么回事,时间一久,越看越不顺眼。
一个好端端的分享技术的博客网站,要那么多图片干嘛?
更何况我又没什么艺术细胞,AI 的确能作图,但没艺术细胞的人用 AI 做出来的图,基本上一眼也能看出是 AI 做的。它可能很精致,也可能很「正确」,但就是少了点人味。甚至有些时候,那些图并不是在帮文章表达什么,而是在证明我也跟上了某种潮流。
于是趁着这个周末,我又一次折腾了自己的博客。
这次从 Next.js 换到了构建速度更快、也更适合纯静态内容的 Astro。同时,我撤掉了所有文章封面图,OG 图片也改成由程序自动生成。至于 SEO、文章内页 ToC 这些东西,交给 AI 来处理就行了,不再让它们喧宾夺主。
最后选定的架构大概如下:
| 层 | 选型 | 版本 |
|---|---|---|
| 运行时 / 包管理 | Bun | 1 |
| 框架 | Astro(纯静态输出,零运行时框架) | 7 |
| 样式 | Tailwind CSS(@tailwindcss/vite 插件,CSS-first 配置) |
4 |
| 内容格式 | MDX(@astrojs/mdx,optimize: true) |
7 |
| 交互 | 原生 JS(<script> 块,无 React/Vue 运行时) |
- |
放在以前,这种事情不折腾几个星期大概是做不完的。现在有了 AI,一晚上就能搞定。
这当然很爽。
但爽完以后,我又不得不问自己:这么折腾来折腾去,目的到底是什么?
是为了体验最新科技?是为了追逐更快的 build 速度?是为了证明自己还没有被前端时代抛弃?还是只是因为我们这些老网民,总觉得网站不改点什么就浑身难受?
技术当然重要。一个轻量、快速、结构清晰的博客系统,可以让写作这件事变得更舒服。但技术终究只是水管,不是水本身。框架再新,页面再快,动画再丝滑,如果文章本身没有内容,那也只是一个加载速度很快的空壳。
博客最重要的东西,始终不是首页长什么样,不是封面图够不够统一,也不是用了哪个新框架,而是文字本身有没有留下你的思考。
现在的互联网越来越像信息流水线。
社交平台追求即时反馈,短视频追求几秒钟的刺激,AI 负责把内容变得更快、更满、更像样。每个人都可以很快生成一篇文章、一张图、一个视频,甚至一个看起来完整的个人网站。
但也正因为如此,真正稀缺的东西反而变了。
以前稀缺的是发布能力。你得会建站,会写 HTML,会折腾服务器,才能拥有自己的一个小角落。现在稀缺的不是发布,而是判断;不是表达能力,而是诚实表达;不是内容数量,而是你到底有没有真正想过。
AI 可以帮你写得更顺,帮你修语法,帮你生成摘要,甚至帮你做图。但 AI 不能替你活过那些日子,也不能替你在某个深夜突然想明白一件小事。
博客的价值,恰恰在这里。
它不一定要服务算法,也不一定要讨好读者。它可以很慢,可以很个人,可以有点啰嗦,甚至可以有点笨。它像是你在互联网上留给自己的一个锚点:过了几年再回头看,你会知道当时的自己在想什么,关心什么,又为什么要这样折腾。
这也是我越来越觉得,博客不应该被做成一个小型媒体站,也不一定非要变成作品集、流量入口或者商业落地页。
博客首先应该是一个人说话的地方。
我们这些互联网老人的折腾精力和速度,已经越来越跟不上科技的发展了。
现在 AI 大爆发,几乎每周,甚至每天都有新的模型、新的工具、新的框架、新的工作流冒出来。你当然可以一直追,也可以一直改,一直重构,一直把自己的博客当成前端实验田。
但到最后,真正能留下来的,大概率还是那些你亲手写下来的文字。
所以,就让我们回归初心吧。
少一点花里胡哨的 JS、CSS 动画和 AI 作图,少一点为了折腾而折腾的冲动,多一点安静写字的耐心。
毕竟,博客不是展示工具链的地方,而是留下思考的地方。
文字,才是自己真正的原创。
不是嘛?
2026-05-20 03:12:04
本文将指导如何升级 Ubuntu 24.04 Noble Numbat 到 Ubuntu 26.04 Resolute Raccoon。
相关教程:Ubuntu 22.04 Jammy 升级 Ubuntu 24.04 Noble。
除非你是物理服务器,以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机,否则升级大版本更新内核是有一定机率导致 Grub 加载失败的,切记备份重要数据!
OpenVZ 和 LXC 构架的 VPS 是无法升级的,因为他们没有自己独立的内核
再强调一遍,一定要备份重要数据!
Ubuntu 26.04 LTS 已经正式发布,不过 Ubuntu LTS 版本的自动升级提示通常会等到第一个小版本发布后才会开放。如果你在 Ubuntu 26.04.1 LTS 发布前从 Ubuntu 24.04 LTS 升级,do-release-upgrade 需要加上 -d 参数。
以下操作需要 root 权限,本文命令均已带上 sudo 前缀,请确保当前用户拥有 sudo 权限;或者直接使用 root 用户操作并省略命令中的 sudo。
首先需要更新你当前的系统
sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt autoclean
sudo apt autoremove -y
如果内核更新了,可以重启让最新的内核生效,也可以直接进行升级。
这里有两种升级系统的方法,第一种是使用 do-release-upgrade 命令,第二种是手动更新 apt 源文件。
do-release-upgrade 命令首先需要安装 ubuntu-release-upgrader-core 包:
sudo apt install ubuntu-release-upgrader-core
然后修改 /etc/update-manager/release-upgrades 文件,确保 Prompt 值为 lts:
cat /etc/update-manager/release-upgrades | grep lts
显示如下内容即可:
root@ubuntu ~ # cat /etc/update-manager/release-upgrades | grep lts
# lts - Check to see if a new LTS release is available. The upgrader
Prompt=lts
最后执行以下命令升级系统:
sudo do-release-upgrade -d
等 Ubuntu 26.04.1 LTS 发布并开放 LTS 自动升级后,可以直接使用:
sudo do-release-upgrade
apt 源文件Ubuntu 24.04 的默认软件源配置文件已经变更为 DEB822 格式,路径为 /etc/apt/sources.list.d/ubuntu.sources。我们可以直接替换 noble 为 resolute:
sudo sed -i 's/noble/resolute/g' /etc/apt/sources.list.d/ubuntu.sources
如果你的系统里仍然有传统 One-Line-Style 的源文件,也可以一并替换:
sudo sed -i 's/noble/resolute/g' /etc/apt/sources.list
sudo sed -i 's/noble/resolute/g' /etc/apt/sources.list.d/*.list
如果没有对应文件会提示诸如 sed: can't read /etc/apt/sources.list: No such file or directory 的错误,忽略即可。
或者直接一行命令:
sudo sed -i 's/noble/resolute/g' /etc/apt/sources.list /etc/apt/sources.list.d/*.{list,sources} 2>/dev/null
使用 DEB822 格式的源文件 /etc/apt/sources.list.d/ubuntu.sources 应该是类似这样的:
Types: deb
URIs: https://archive.ubuntu.com/ubuntu
Suites: resolute resolute-updates resolute-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: resolute-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
国内服务器可以替换 archive.ubuntu.com 和 security.ubuntu.com 为 mirrors.tuna.tsinghua.edu.cn
如果你有第三方源或 PPA,建议先备份并根据情况临时禁用,升级完成后再确认是否支持 Ubuntu 26.04:
sudo mkdir -p /root/apt-sources-backup
sudo cp -a /etc/apt/sources.list.d /root/apt-sources-backup/
第三方源通常需要单独检查,不能简单把 noble 替换成 resolute。
然后我们再次执行更新系统:
sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
更新过程中会提示一些软件是否需要自动重启,选 Yes 即可,以及一些软件的配置文件是否需要更新,按照自己的情况选择即可,默认回车即视为使用旧的配置文件,一般会出现在 OpenSSH 等软件的更新上。
更新后删除不必要的软件和依赖:
sudo apt autoclean
sudo apt autoremove -y
然后我们使用 sudo reboot 命令重启系统,耐心等待后,查看最新的系统版本:
root@ubuntu ~ # lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 26.04 LTS
Release: 26.04
Codename: resolute
root@ubuntu ~ # uname -a
Linux Nana 7.0.0-15-generic #15-Ubuntu SMP PREEMPT_DYNAMIC Wed Apr 22 16:06:43 UTC 2026 x86_64 GNU/Linux
这时我们就已经更新到了最新的 Ubuntu 26.04 Resolute 和内核了。
2026-01-21 12:53:46
本文介绍在 WSL 2 中使用 Docker 的桥接模式(bridge network)访问 HTTPS 时出现超时问题的解决方法。
最近一直在 Windows 下使用基于 WSL 2 的 Debian 进行开发。许多场景下需要使用 Docker 构建镜像,但过程中遇到一个奇怪的问题,在 Docker 容器内部访问 HTTP 站点时一切正常:
$ docker run --rm curlimages/curl time curl -s http://ip.gs
192.0.2.2
real 0m 0.02s
user 0m 0.00s
sys 0m 0.00s
一旦切换为 HTTPS,访问就会明显变慢,并且有一定概率出现超时:
$ docker run --rm curlimages/curl time curl -s https://ip.gs
192.0.2.2
real 0m 6.52s
user 0m 0.00s
sys 0m 0.00s
然而,如果将 Docker 切换到 Host 模式(--network=host),访问又恢复正常:
$ docker run --rm --network=host curlimages/curl time curl -s https://ip.gs
192.2.0.2
real 0m 0.28s
user 0m 0.00s
sys 0m 0.00s
在排查了 WSL 2 的 Debian 系统配置后,最终请教 ChatGPT 得到结论:
Path MTU 与防火墙流量检查(inspection)不兼容导致问题
解决方法很简单:将 WSL 2 虚拟网络的 MTU 下调为 1400 即可。
在 WSL 2 的系统里,使用 root 权限(sudo)修改 /etc/docker/daemon.json 文件:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "3"
},
"mtu": 1400,
"dns": [
"8.8.8.8",
"1.1.1.1"
]
}
其中 "mtu": 1400, 是关键配置。
然后在 Windows 下把 WSL 2 关闭:
wsl --shutdown
在 Windows 下使用管理员身份运行 Powershell,检查网卡名称:
PS C:\Users\showfom> Get-NetAdapter | Where-Object { $_.Name -like "*WSL*" }
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
vEthernet (WSL (Hyper-V … Hyper-V Virtual Ethernet Adapter #3 41 Up 12-34-56-78-AB-CD 10 Gbps
PS C:\Users\showfom> Get-NetAdapter | Format-Table -AutoSize
Name InterfaceDescription ifIndex Status MacAddress Lin
kSp
eed
---- -------------------- ------- ------ ---------- ---
vEthernet (Default Switch) Hyper-V Virtual Ethernet Adapter 35 Up 12-34-56-78-AB-AB …ps
vEthernet (WSL (Hyper-V firewall)) Hyper-V Virtual Ethernet Adapter #3 41 Up 12-34-56-78-AB-CD …ps
输出可以看到完整名称为 vEthernet (WSL (Hyper-V firewall)),然后为其设置 MTU:
netsh interface ipv4 set subinterface "vEthernet (WSL (Hyper-V firewall))" mtu=1400 store=persistent
出现 Ok. 字样即代表设置成功。
然后重新运行 WSL 2 虚拟机:
wsl -d debian
再次测试:
$ docker run --rm curlimages/curl time curl -s https://ip.gs
192.0.2.2
real 0m 0.28s
user 0m 0.00s
sys 0m 0.00s
问题已完美解决,可以继续愉快地 Vibe Coding 了! ★,°:.☆( ̄▽ ̄)/$:.°★ 。
2026-01-16 11:05:21
本文将介绍在 Debian 或 Ubuntu 下使用 nginx-acme 自动签发并配置 SSL 证书的方法。
以下操作需要 root 权限,本文命令均已带上 sudo 前缀,请确保当前用户拥有 sudo 权限;或者直接使用 root 用户操作并省略命令中的 sudo。
nginx-acme 是 Nginx 官方开发的基于 ACME 协议自动签发 SSL 证书的模块,隔壁 Caddy 都出了几百年的功能, Nginx 也终于赶上了。
和 Nginx 使用 C 语言开发不同,这个模块是用 Rust 语言开发的,这里就不做评价。
这个模块支持 RFC8555、RFC8737、RFC8738 和 draft-ietf-acme-profiles 等规范,目前我实际测试下来已经基本可以用于生产环境。
这里我们使用烧饼博客打包的 N.WTF,这个项目已经集成了 nginx-acme 模块,可以做到开箱即用。
首先,安装一些必要的软件包:
sudo apt update
sudo apt upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates
然后加入 N.WTF 的 GPG 公钥和 apt 源:
curl -sSL https://n.wtf/public.key | sudo bash -c 'gpg --dearmor > /usr/share/keyrings/n.wtf.gpg'
sudo bash -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror-cdn.xtom.com/sb/nginx/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/n.wtf.list'
国内机器可以用清华 TUNA 的国内源:
curl -sSL https://mirrors.tuna.tsinghua.edu.cn/n.wtf/public.key | sudo bash -c 'gpg --dearmor > /usr/share/keyrings/n.wtf.gpg'
sudo bash -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirrors.tuna.tsinghua.edu.cn/n.wtf/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/n.wtf.list'
Debian 下也可以直接使用 extrepo:
sudo apt update
sudo apt install extrepo -y
sudo extrepo enable n.wtf
接着更新系统并安装 Nginx:
sudo apt update
sudo apt install nginx-extras -y
准备工作很简单,我们需要建立一个目录来存放 SSL 证书并给予正确的权限,这里我们以 /var/cache/nginx/letsencrypt 为演示:
sudo mkdir -p /var/cache/nginx
sudo mkdir -p /var/cache/nginx/letsencrypt
sudo chown 33:33 /var/cache/nginx -R
然后别忘了把域名解析到你的服务器哦!
我们以 example.com 为例,假设你的邮箱是 [email protected],需要配置的域名是 example.com 和 www.example.com,并且希望访问 www.example.com 跳转到 example.com:
直接修改 /etc/nginx/sites-enabled/default 文件:
resolver 8.8.8.8:53 ipv6=off valid=5s;
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact [email protected];
state_path /var/cache/nginx/letsencrypt;
accept_terms_of_service;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
ssl_verify on;
}
acme_shared_zone zone=ngx_acme_shared:1M;
server {
# Listen on port 80 for all IPv4 and IPv6 addresses
listen 80 default_server;
listen [::]:80 default_server;
# Match all domain names
server_name _;
location /.well-known/ {
return 404;
}
location / {
# Redirect all other HTTP requests to HTTPS using 301 permanent redirect
return 301 https://$host$request_uri;
}
}
server {
# Standard TLS listening
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
# HTTP/2 protocol support
http2 on;
# HTTP/3 QUIC protocol support
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header X-Protocol $server_protocol always;
server_name example.com;
root /var/www/html;
index index.html;
# modern configuration
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_prefer_server_ciphers off;
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
# do not parse the certificate on each request
ssl_certificate_cache max=2;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
listen 443 quic;
listen [::]:443 quic;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header X-Protocol $server_protocol always;
server_name www.example.com;
return 301 https://example.com$request_uri;
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_prefer_server_ciphers off;
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
# do not parse the certificate on each request
ssl_certificate_cache max=2;
}
然后验证 Nginx 配置并重新加载:
sudo nginx -t
sudo nginx -s reload
此时,在默认的 Nginx 日志文件 /var/log/nginx/access.log 中可以看到 Let's Encrypt 验证服务器的请求记录:
23.178.112.210 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
34.212.137.78 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
18.222.179.58 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
16.171.19.61 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
13.228.72.222 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2600:3000:2710:200::81 - - [15/Jan/2026:16:08:20 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2600:1f14:804:fd02:f7fd:3c68:dec7:a062 - - [15/Jan/2026:16:08:20 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2600:1f16:269:da00:c9ce:508c:ea69:9c2 - - [15/Jan/2026:16:08:20 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2a05:d016:39f:3101:8b83:62b8:2603:d15d - - [15/Jan/2026:16:08:20 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2406:da18:85:1401:1f69:967a:a988:cdc8 - - [15/Jan/2026:16:08:20 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
等待数秒后即可访问 https://example.com/ 了。
如果要给 IP 地址签发证书,则需要在 acme_issuer letsencrypt {} 段里添加一行 profile shortlived;,并且 server_name 必须写入完整的 IP 地址,不能直接用 server_name _ 哦。
不过目前 nginx-acme 最新版本 0.3.1 签发 IP 证书还是会失败,开发版已经修复,应该需要等下一个版本才能使用。
目前最新版本已经支持 IP 地址证书的签发,可以在 accept_terms_of_service; 下方加入一条 profile shortlived; 并且自行修改 server_name example.com; 为你的 IP 地址,比如 server_name 192.0.2.2 2001:db8::2; 即可。
读者们在使用过程中如果遇到问题,可以在 V2EX 交流讨论:
2025-12-18 00:13:37
本文将介绍使用 acme.sh 配置 Let's Encrypt 为 IP 地址签发 SSL 证书。
acme.sh 本身不强制 root 运行(官方也写了 "You don't have to be root then, although it is recommended"),但本文的证书需要写入 /etc 且 --reloadcmd 需要重载服务,均需要 root 权限,所以建议先使用 sudo -i 切换到 root 用户再执行本文命令。
之前写过一篇使用 acme.sh 签发证书的教程,但在很长一段时间里,Let's Encrypt 只能给域名签发证书。
经过几个月的测试之后,现在终于可以对 IP 地址 下手了。
在很多场景下,我们并不一定需要域名,但确实需要 HTTPS。比如:
直接通过 IP 提供 DoH 服务,避免「为了安全先做一次不安全的域名解析」这种哲学问题。
默认站点只暴露 IP,不暴露真实域名,顺便还能挡掉一部分不太礼貌的爬虫。
临时起个服务,只想加个锁,不想再去 DNS 那边折腾。
有些域名不太想出现在公开日志里,低调一点总是好的。
首先更新 acme.sh 到最新版本:
acme.sh --upgrade
因为 IP 证书目前只能通过 http-01 和 tls-alpn-01 方式进行验证,所以你需要检查服务器的防火墙,设置允许 TCP 80 和 TCP / UDP 443 端口在公网可以访问。
这里我只介绍在 Nginx 下的配置吧,我们可以直接写入 80 端口的默认配置:
如果你在 Debian 或 Ubuntu 下安装 Nginx,可以直接覆盖 /etc/nginx/sites-available/default 文件:
server {
# Listen on port 80 for all IPv4 and IPv6 addresses
listen 80 default_server;
listen [::]:80 default_server;
# Match all domain names
server_name _;
# Merge Let's Encrypt and SSL verification path configuration
location ~ ^/.well-known/(acme-challenge|pki-validation)/ {
add_header Content-Type text/plain;
root /var/www/letsencrypt;
}
# Redirect all other HTTP requests to HTTPS using 301 permanent redirect
location / {
return 301 https://$host$request_uri;
}
}
然后创建两个目录并重新加载 Nginx:
mkdir -p /var/www/letsencrypt
mkdir -p /etc/nginx/ssl
nginx -t
nginx -s reload
假设你服务器的 IP 地址是 192.0.2.2 和 2001:db8::2:
acme.sh --issue --server letsencrypt -d 192.0.2.2 -d 2001:db8::2 \
-w /var/www/letsencrypt \
--certificate-profile shortlived \
--days 3
注意这里我们必须使用 shortlived 这个 Profile,因为 Let's Encrypt 的 IP 证书有效期只有 6.66666 天(160 小时),同时 acme.sh 需要更短的时间来进行检查更新证书,所以可以设置 --days 3 参数,让它 3 天检查并更新一次,你也可以设置 4 或 5,但是不要设置 6,否则可能证书过期了都没更新哦。
执行命令以后会看到类似的申请成功返回:
[Wed Dec 17 05:46:28 AM UTC 2025] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Wed Dec 17 05:46:28 AM UTC 2025] Multi domain='IP:192.0.2.2,IP:2001:db8::2'
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain='192.0.2.2'
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain='2001:db8::2'
[Wed Dec 17 05:46:30 AM UTC 2025] Verifying: 192.0.2.2
[Wed Dec 17 05:46:31 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:34 AM UTC 2025] Success
[Wed Dec 17 05:46:34 AM UTC 2025] Verifying: 2001:db8::2
[Wed Dec 17 05:46:35 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:38 AM UTC 2025] Success
[Wed Dec 17 05:46:38 AM UTC 2025] Verification finished, beginning signing.
[Wed Dec 17 05:46:38 AM UTC 2025] Let's finalize the order.
[Wed Dec 17 05:46:38 AM UTC 2025] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/blablablablablablablabla/blablablablablablablabla'
[Wed Dec 17 05:46:41 AM UTC 2025] Downloading cert.
[Wed Dec 17 05:46:41 AM UTC 2025] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/blablablablablablablabla'
[Wed Dec 17 05:46:42 AM UTC 2025] Cert success.
-----BEGIN CERTIFICATE-----
blablablablablablablablablablablablablablablablablablabla
-----END CERTIFICATE-----
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.cer
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert key is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.key
[Wed Dec 17 05:46:42 AM UTC 2025] The intermediate CA cert is in: /root/.acme.sh/192.0.2.2_ecc/ca.cer
[Wed Dec 17 05:46:42 AM UTC 2025] And the full-chain cert is in: /root/.acme.sh/192.0.2.2_ecc/fullchain.cer
然后我们可以把申请好的证书放在 /etc/nginx/ssl 目录:
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d 192.0.2.2 \
--key-file /etc/nginx/ssl/ip.key \
--fullchain-file /etc/nginx/ssl/ip.crt \
--ca-file /etc/nginx/ssl/ip.ca.crt \
--reloadcmd "systemctl restart nginx"
安装完证书后我们即可配置 Nginx 默认的 443 端口了,你可以把这段配置一起放入 /etc/nginx/sites-available/default 文件:
# HTTPS Server block - Handle all HTTPS requests
server {
# Standard TLS listening
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
# HTTP/2 protocol support
http2 on;
# HTTP/3 QUIC protocol support
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header X-Protocol $server_protocol always;
# Match all domain names
server_name _;
return 403;
# modern configuration
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_prefer_server_ciphers off;
ssl_certificate /etc/nginx/ssl/ip.crt;
ssl_certificate_key /etc/nginx/ssl/ip.key;
}
然后检查并重新加载 Nginx:
nginx -t
nginx -s reload
一切就绪以后就可以直接访问 https://192.0.2.2/ 并返回 403 错误页面,我们可以看到证书里 Subject Alt Names 字段也显示 IP Address 了:

读者们在使用过程中如果遇到问题,可以在 V2EX 交流讨论或在下方评论: