在进行部署时,我以前一直使用的是 Nginx, 在使用过程中我发现 Nginx 配置较为繁琐重复,https 还要单独续签,有没有一种更简单轻便使用的 Web 服务器, 于是我发现了 Caddy。
部署个人博客、家庭服务器和各种自托管服务时,我们经常会遇到一个问题:不同的服务运行在不同的端口上,如何通过统一的域名访问它们?例如,博客运行在 3000 端口,NAS 管理界面运行在 5000 端口,另一个服务运行在 8080 端口。如果直接对外暴露这些端口,不仅访问地址不够美观,也会增加安全管理和证书维护的复杂度。
解决这类问题的常见方式是部署一个反向代理服务器。Nginx、Apache 和 Caddy 都可以完成这项工作,而 Caddy 的一个突出特点是:默认集成 HTTPS 自动化管理,配置简单,适合个人项目、家庭服务器和中小型 Web 服务。
本文从 Caddy 的基本原理开始,介绍它的核心功能、配置语法、反向代理、自动 HTTPS、Docker 部署,以及在家庭服务器和内网穿透场景中的应用。
一、Caddy 是什么
Caddy 是一个使用 Go 语言编写的 Web 服务器,支持 HTTP、HTTPS、反向代理、静态文件服务等功能。它的一个重要设计目标是让安全的 Web 服务更容易部署,尤其是通过自动 HTTPS 降低证书申请、续期和配置的维护成本。
传统部署方式通常需要自行准备 TLS 证书,配置证书路径、私钥、监听端口和续期脚本。Caddy 则可以在满足域名解析、网络连通性和证书颁发机构验证条件时,自动申请并续期证书。
例如,只需要一段简单的配置:
example.com {
reverse_proxy localhost:3000
}
Caddy 就可以将发往 example.com 的请求转发到本机的 3000 端口,并自动处理 HTTPS 相关配置。
这里有几个关键点:
example.com是客户端访问的域名。reverse_proxy表示将请求转发给后端服务。localhost:3000是后端应用的地址。- Caddy 会根据配置自动处理 HTTPS,但前提是域名和网络环境满足证书签发要求。
如果使用 Node.js、NestJS、Nuxt 或其他 Web 框架开发应用,通常不需要让应用自己承担公网 TLS 终止工作。可以由 Caddy 统一处理 HTTPS,再通过内部网络访问应用。
二、正向代理与反向代理的区别
理解 Caddy,首先要弄清楚代理服务器的两种常见工作方式。
正向代理主要代表客户端访问外部资源。客户端知道自己在使用代理,代理通常帮助客户端转发访问请求。
反向代理则位于服务器一侧,客户端访问的是代理服务器,代理再将请求转发给后端应用。客户端不需要知道真实后端的地址和端口。
例如,有一个运行在 3000 端口的 Nuxt 博客:
浏览器
│
│ https://blog.example.com
▼
Caddy :443
│
│ HTTP
▼
Nuxt 应用 :3000
浏览器只需要访问 https://blog.example.com,Caddy 就会将请求转发给 Nuxt。
这种架构的优势是,多个服务可以共用公网的 80 和 443 端口,通过域名区分不同应用:
blog.example.com → Nuxt 博客 :3000
api.example.com → NestJS API :4000
nas.example.com → NAS 管理服务 :5000
monitor.example.com → 监控服务 :8080
不需要为每个服务单独开放一个公网端口,也不必让每个后端应用分别管理公网证书。
三、安装与基础配置
Caddy 支持 Linux、macOS、Windows 和容器化部署。实际生产环境建议优先使用操作系统提供的官方安装方式或官方 Docker 镜像,并根据部署方式管理配置文件和数据目录。
在 Linux 环境中,安装完成后通常可以通过以下命令检查版本:
caddy version
检查配置文件是否存在语法或配置问题:
caddy validate --config /etc/caddy/Caddyfile
格式化 Caddyfile:
caddy fmt --overwrite /etc/caddy/Caddyfile
如果使用 systemd 管理 Caddy,常见操作如下:
sudo systemctl status caddy
sudo systemctl reload caddy
sudo systemctl restart caddy
sudo journalctl -u caddy -f
其中,reload 通常适合在修改配置后平滑加载新配置;restart 则会重新启动服务。实际使用时,应根据安装方式和服务管理方式选择对应命令。
Caddy 最常见的配置格式是 Caddyfile。它采用相对简洁的语法,不需要编写大量嵌套的 JSON 配置,就可以完成常用的 Web 服务部署。
四、Caddyfile 核心语法
1. 反向代理
假设本机运行着一个 NestJS 服务,监听 4000 端口:
api.example.com {
reverse_proxy localhost:4000
}
当用户访问 https://api.example.com/users 时,Caddy 会将请求转发给后端服务。
通常情况下,请求路径会保留。也就是说,后端收到的仍然是 /users,而不是被自动替换成其他路径。
如果后端服务运行在另一台服务器上,也可以指定内网地址:
api.example.com {
reverse_proxy 192.168.2.100:4000
}
这种方式适用于将 Caddy 与应用服务分开部署的场景。
2. 多域名代理
Caddy 可以在一个配置文件中配置多个站点:
blog.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:4000
}
monitor.example.com {
reverse_proxy localhost:9090
}
Caddy 会根据请求的主机名选择对应的站点配置。这使得一台服务器可以承载多个 Web 服务。
如果后端运行在 Docker 容器中,需要注意 localhost 的含义:它指的是 Caddy 自己所在的网络命名空间,而不是其他容器。跨容器访问时,通常应使用同一 Docker 网络中的服务名。
3. 静态文件服务
除了反向代理,Caddy 也可以直接提供静态文件。
例如,将 /srv/www 目录作为网站根目录:
static.example.com {
root * /srv/www
file_server
}
如果目录中存在 index.html,访问网站时就可以直接返回静态页面。
对于纯静态博客、文档站点、图片资源和下载文件服务,这种方式非常轻量,不需要额外运行 Node.js 应用。
4. 路径匹配与重写
有时候,我们希望把一个域名下的不同路径转发给不同服务:
example.com {
handle /api/* {
reverse_proxy localhost:4000
}
handle {
reverse_proxy localhost:3000
}
}
上面的配置将 /api/* 请求转发给后端 API,其余请求转发给前端应用。
需要注意,handle 主要负责匹配并选择处理逻辑,不会默认删除匹配到的路径前缀。如果后端只接受 /users 而不接受 /api/users,就需要根据实际需求使用 handle_path 或 uri strip_prefix 等方式调整路径。
五、自动 HTTPS 的工作原理
自动 HTTPS 是 Caddy 最有代表性的功能之一。
在传统 Nginx 部署中,通常需要先通过证书颁发机构获得证书,再在配置中指定证书和私钥路径,之后还要确保续期任务正常执行。
Caddy 将这套流程整合到了服务器中。在适用的配置下,它可以自动申请证书、配置 HTTPS,并在证书到期前自动续期。
其工作流程大致如下:
配置域名
│
▼
Caddy 检查 HTTPS 配置
│
▼
验证域名控制权
│
▼
申请 TLS 证书
│
▼
监听 HTTPS 请求
│
▼
自动续期证书
但是,自动 HTTPS 并不意味着任何网络环境都能直接申请证书。实际部署时需要满足证书颁发机构要求的验证条件。
常见的验证方式包括:
- HTTP-01:通常要求域名的公网
80端口能够访问验证服务。 - TLS-ALPN-01:通常要求公网
443端口能够完成相应的 TLS 验证。 - DNS-01:通过 DNS TXT 记录验证域名控制权,适用于通配符证书以及无法直接从公网访问验证端口的部分环境。
如果使用 DNS-01,还需要配置对应的 DNS Provider 模块及 API 凭据。并不是所有 Caddy 安装包都默认包含所有 DNS 插件。
使用通配符证书
如果希望多个子域名共用一张通配符证书,例如:
*.example.com
通常需要通过 DNS-01 验证完成证书申请。
Caddyfile 可以采用类似下面的结构:
*.example.com {
@blog host blog.example.com
handle @blog {
reverse_proxy localhost:3000
}
@api host api.example.com
handle @api {
reverse_proxy localhost:4000
}
}
这段配置演示的是通配符站点的请求分流思路。要让通配符证书正常签发和使用,还需要配置 DNS Provider 插件、凭据和对应的 DNS 验证流程。
另外,通配符证书通常只覆盖一层子域名。例如,blog.example.com 匹配 *.example.com,但 a.blog.example.com 不匹配。
六、Docker 部署 Caddy
在家庭服务器和自托管服务中,使用 Docker 部署 Caddy非常方便。配置文件可以通过挂载的方式管理,证书与运行状态则保存在持久化目录中。
一个常见的目录结构如下:
caddy/
├── compose.yaml
├── Caddyfile
└── data/
其中,data 目录用于保存 Caddy 的持久化数据。生产环境还应根据需要为配置目录和状态目录设置适当的权限。
compose.yaml 示例:
services:
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
networks:
- web
networks:
web:
external: true
这里假设已经创建了一个名为 web 的 Docker 网络:
docker network create web
如果后端应用也接入该网络,就可以使用容器服务名进行代理。例如,后端容器名或 Compose 服务名为 blog,监听 3000 端口,那么 Caddyfile 可以写成:
blog.example.com {
reverse_proxy blog:3000
}
Caddy 与后端应用需要处于能够互相通信的网络中。仅仅在 Caddy 的配置里写入容器名称,并不能自动让不同 Docker 网络中的容器互通。
启动服务:
docker compose up -d
查看日志:
docker compose logs -f caddy
修改配置后,可以尝试平滑加载:
docker compose exec caddy \
caddy reload --config /etc/caddy/Caddyfile
为什么必须持久化 /data
Caddy 会在数据目录中保存证书、私钥及其他运行数据。如果容器被删除或重建,而这些数据没有持久化,Caddy 可能需要重新申请证书或重新建立相关状态。
因此,Docker 部署时至少应该确保 /data 目录被正确持久化,并对其中的敏感内容做好权限控制和备份。
上面的示例使用官方镜像标签 caddy:2,适合演示基本部署。实际生产环境建议根据升级策略固定到经过测试的具体版本,并定期更新以获得安全修复。
七、Caddy 与 frp:家庭服务器的外网访问方案
对于没有公网 IPv4 地址的家庭网络,单独部署 Caddy 并不能直接解决外网访问问题。
例如,家庭网络中的服务运行在:
192.168.2.100:3000
如果路由器没有公网 IPv4,外部客户端通常无法直接访问这个地址。此时可以使用 frp、VPN 或具有公网入口的云服务器等方式建立访问通道。
一种常见架构是:
公网客户端
│
▼
域名 DNS 解析
│
▼
阿里云 ECS 公网 IP
│
▼
Caddy :80 / :443
│
▼
frps / frpc 建立的转发通道
│
▼
家庭服务器内网应用
在这种架构中,各个组件承担不同职责:
- DNS:将域名解析到云服务器的公网地址。
- Caddy:处理 HTTP/HTTPS 请求、证书和反向代理。
- frp:建立云服务器与家庭网络之间的转发通道。
- 家庭服务器:实际运行博客、NAS 周边应用和其他自托管服务。
这里有两种常见的部署方式。
方式一:Caddy 部署在云服务器上。
云服务器上的 Caddy 负责 HTTPS,随后将请求转发到 frp 暴露的本地转发端口。这种方式适合将公网入口集中管理在云服务器上。
方式二:Caddy 部署在家庭服务器上。
云服务器通过 frp 转发公网请求到家庭网络,家庭服务器上的 Caddy 再将请求分发给不同应用。这种方式便于在家庭网络内部管理多个服务,但需要仔细设计公网端口转发和 TLS 终止位置。
两种方式都可以使用,但不要在没有必要的情况下重复设置多个反向代理层。每增加一层,就需要额外考虑请求头、超时、日志、TLS 和故障排查。
使用阿里云 DNS
如果域名托管在阿里云 DNS,通常可以添加 A 记录,将域名指向云服务器公网 IPv4:
blog.example.com A 云服务器公网 IPv4
api.example.com A 云服务器公网 IPv4
当多个域名指向同一个公网 IP 时,Caddy 可以根据域名将请求分发给不同服务。
如果使用 DNS-01 申请证书,则还需要通过 DNS API 创建 TXT 验证记录。此时需要安装与所用 DNS Provider 对应的 Caddy 插件,并使用权限尽可能小的 API 凭据。
不要把 DNS API 密钥直接写入公开仓库,也不要为了申请证书而授予不必要的账号权限。
八、生产环境中的安全与运维注意事项
Caddy 配置简单,但并不意味着部署后就可以忽略安全问题。
首先,不要将所有内部管理服务直接暴露到公网。NAS 管理界面、数据库、监控面板和管理后台应根据使用场景设置访问控制,优先考虑 VPN、IP 限制、身份认证或专门的访问网关。
其次,要理解反向代理后的客户端 IP。后端应用可能需要从 X-Forwarded-For、X-Forwarded-Proto 等请求头中获取客户端信息,但这些信息必须结合可信代理配置进行判断。不要直接信任任意客户端传入的转发头,否则可能导致访问日志、IP 限制或安全审计出现错误。
再次,Caddy 的管理 API 不应该随意暴露到公网。默认管理接口通常在本地监听,修改监听地址时应明确访问边界,避免未授权用户更改代理配置。
最后,应持续监控 Caddy 的访问日志和运行日志。证书申请失败、DNS 解析异常、后端连接拒绝、超时和权限问题,都可能导致服务无法正常访问。对于重要服务,建议配合健康检查、告警和配置备份。
九、Caddy 与 Nginx 如何选择
Caddy 和 Nginx 都是成熟的 Web 服务器与反向代理方案,适用场景有所不同。
| 对比项 | Caddy | Nginx |
|---|---|---|
| 默认 HTTPS 自动化 | 内置自动 HTTPS | 通常需要额外配置证书与续期 |
| 配置风格 | 简洁的 Caddyfile | 灵活的指令与配置块 |
| 反向代理 | 原生支持 | 原生支持 |
| 静态文件服务 | 支持 | 支持 |
| 复杂代理规则 | 可以实现 | 生态成熟、使用广泛 |
| DNS 插件扩展 | 需要对应模块 | 通常通过外部工具或集成方案 |
| 常见使用场景 | 个人站点、家庭服务器、快速部署 HTTPS | 传统 Web 基础设施、复杂代理和已有运维体系 |
如果目标是快速部署几个个人站点,且希望减少证书维护工作,Caddy 往往更省心。如果现有基础设施已经围绕 Nginx 建立了成熟的配置管理、监控和运维流程,也没有必要仅仅为了自动 HTTPS 而迁移。
十、总结
Caddy 的核心价值在于将 Web 服务、反向代理和 HTTPS 自动化整合到一个相对简单的配置体系中。
对于个人开发者来说,可以用它为 Nuxt 博客配置 HTTPS,为 NestJS API 统一入口,也可以在 Docker 环境中通过服务名代理不同容器。对于家庭服务器用户,则可以进一步结合云服务器、DNS 和 frp,让没有公网 IPv4 的内网服务通过域名访问。
实际部署时,可以从最简单的反向代理开始,再逐步增加多域名路由、静态文件服务、DNS-01 证书、访问控制和监控能力。
需要记住的是:Caddy 解决的是 Web 入口、代理和 HTTPS 等问题;DNS 负责域名解析,frp 负责内外网之间的转发通道,后端应用负责业务逻辑。明确各组件的职责,才能构建一套容易维护、可靠且安全的服务架构。
参考资料: