nginx-proxy-manager 证书管理实战指南:HTTP 验证、DNS 验证与自定义证书的签发与维护
【免费下载链接】nginx-proxy-managerDocker container for managing Nginx proxy hosts with a simple, powerful interface项目地址: https://gitcode.com/GitHub_Trending/ng/nginx-proxy-manager
导读
本文基于 nginx-proxy-manager 内置的"证书帮助"文档(frontend/src/locale/src/HelpDoc/en/Certificates.md,葡萄牙语版本见 frontend/src/locale/src/HelpDoc/pt/Certificates.md),系统讲解在该项目中签发与管理 SSL 证书的三种方式:HTTP 验证(HTTP-01)证书、DNS 验证(DNS-01)证书与自定义证书上传。读完本文,你将掌握每种方式的前置条件、适用场景(尤其是 wildcard 通配符域名的支持差异),并理解证书从签发、自动续期到撤销的底层实现机制,从而在 nginx-proxy-manager 中为代理主机、重定向主机、404 主机与 TCP/UDP 流式转发正确配置 TLS。
证书是整个反向代理体系中安全性的基石:nginx-proxy-manager 的证书对象被代理主机(Proxy Host)、重定向主机(Redirection Host)、404 主机(Dead Host)和流式转发(Stream)共同引用(见 backend/models/certificate.js 中的关系映射)。因此理解证书的三种来源方式,是使用该项目的必修课。
一、证书的三种来源总览
在 nginx-proxy-manager 管理界面中打开Certificates(证书)页面(下图),点击 "Add SSL Certificate" 即可看到三种入口:Let's Encrypt(HTTP 验证)、Let's Encrypt(DNS 验证)和 Custom(自定义证书)。
nginx-proxy-manager 证书管理页面
三者的核心区别可以总结为下表:
| 特性 | HTTP 验证证书 | DNS 验证证书 | 自定义证书 |
|---|---|---|---|
| 验证通道 | HTTP-01(80 端口) | DNS-01(TXT 记录) | 无验证,直接上传 |
| 是否需要先建 Proxy Host | 需要(指向本机且可经 HTTP 访问) | 不需要 | 不需要 |
| 是否支持通配符域名(wildcard) | 不支持 | 支持 | 取决于证书本身 |
| 证书提供方 | Let's Encrypt | Let's Encrypt(经 DNS Provider 插件) | 任意 CA |
| 自动续期 | 支持 | 支持 | 不支持(需手动更新) |
从源码看,三种来源最终都写入同一张certificate表(backend/models/certificate.js),通过provider字段区分:Let's Encrypt 签发的证书provider为letsencrypt,自定义证书为other(见 backend/internal/certificate.js)。
二、HTTP 验证证书(HTTP-01 Challenge)
2.1 工作原理
HTTP 验证证书意味着 Let's Encrypt 服务器会通过 HTTP(而非 HTTPS)访问你的域名,在/.well-known/acme-challenge/路径下验证一个临时令牌文件,验证成功后签发证书。nginx-proxy-manager 为这一流程准备了专用的临时站点配置模板 backend/templates/letsencrypt-request.conf,该模板监听 80 端口并仅开放 ACME 挑战路径:
server { listen 80; listen [::]:80; server_name {{ domain_names | join: " " }}; access_log /data/logs/letsencrypt-requests_access.log standard; error_log /data/logs/letsencrypt-requests_error.log warn; include conf.d/include/letsencrypt-acme-challenge.conf; location / { return 404; } }2.2 前置条件
根据原文档要求,使用 HTTP 验证方式必须满足:
- 为你的域名创建一个Proxy Host;
- 该 Proxy Host 必须可以通过 HTTP 访问,并且流量指向这台 nginx-proxy-manager 安装;
- 域名的 DNS 记录必须已解析到本机,且 80 端口对外可达。
签发完成后,你可以修改这个 Proxy Host,让它同时使用该证书提供 HTTPS 连接。但必须保留 HTTP 访问配置——因为证书的续期仍然要通过 HTTP 挑战完成。这是原文档反复强调的关键约束:如果把 HTTP 关掉,续期将会失败。
2.3 不支持的场景
此方式不支持 wildcard(通配符)域名,因为 HTTP-01 挑战要求对每个具体域名逐一发起 HTTP 请求,无法为*.example.com这种通配符一次性完成验证。需要通配符证书请改用下一节的 DNS 验证方式。
2.4 签发流程的源码实现
在 backend/internal/certificate.js 的create()方法中,Let's Encrypt 证书的签发遵循一条明确的六步流程:
- 找出使用了这些域名的现有主机(
internalHost.getHostsWithDomains); - 临时禁用这些主机的 nginx 配置(
disableInUseHosts),避免端口冲突; - 生成 Let's Encrypt 请求配置(
generateLetsEncryptRequestConfig,即上文模板); - 调用 certbot 签发证书(
requestLetsEncryptSsl); - 移除临时配置(
deleteLetsEncryptRequestConfig); - 恢复之前禁用的主机配置并重载 nginx。
对应的 certbot 命令(backend/internal/certificate.js)大致如下:
certbot certonly -n \ --config /etc/letsencrypt.ini \ --work-dir /tmp/letsencrypt-lib \ --logs-dir /data/logs \ --cert-name npm-<certificate_id> \ --agree-tos -m <你的邮箱> \ --authenticator webroot \ --preferred-challenges http \ --domains example.com,www.example.com其中--cert-name npm-<certificate_id>将证书与数据库中的证书 ID 一一对应,证书文件最终存放在/etc/letsencrypt/live/npm-<certificate_id>/(见 backend/internal/certificate.js)。另外注意:发起 Let's Encrypt 请求前,用户账号必须配置了有效的邮箱地址,否则会抛出 "A valid email address must be set on your user account to use Let's Encrypt" 错误(backend/internal/certificate.js)。
2.5 签发前的可访问性测试
在正式签发前,界面还会提供 HTTP 挑战测试功能(对应 APIPOST /api/nginx/certificates/test-http,见 backend/routes/nginx/certificates.js)。其实现(backend/internal/certificate.js)会在本机 ACME 挑战目录写入一个测试文件,然后通过第三方 HTTP 探测工具访问http://<domain>/.well-known/acme-challenge/test-challenge,根据返回结果给出ok、404、no-host、wrong-data等诊断结论,帮助你定位 DNS 解析、防火墙或反代配置问题。
三、DNS 验证证书(DNS-01 Challenge)
3.1 工作原理
DNS 验证证书要求使用一个DNS Provider(DNS 供应商)插件。该插件会通过你的 DNS 服务商 API 在域名下自动创建临时的 TXT 记录(_acme-challenge),Let's Encrypt 查询该记录以确认你对域名的所有权,验证成功后签发证书。
相比 HTTP 方式,DNS 验证有两大优势(原文档明确说明):
- 不需要预先创建 Proxy Host;
- 不需要 Proxy Host 配置 HTTP 访问——因为验证完全发生在 DNS 层面,与 80/443 端口是否可达无关。
3.2 通配符域名支持
此方式支持 wildcard 域名。这是签发*.example.com通配符证书的唯一内置途径,尤其适合内部服务多、不想为每个子域名单独签证书的场景。
3.3 DNS Provider 插件机制
nginx-proxy-manager 内置了数量庞大的 DNS 插件清单,定义在 backend/certbot/dns-plugins.json,覆盖 Cloudflare、DigitalOcean、GoDaddy、DNSPod、Aliyun(阿里云)、Tencent Cloud(腾讯云)、Route 53(Amazon)、OVH、Hetzner、Vultr、Linode、Azure、Google 等主流厂商。每个插件条目包含四要素:
{ "cloudflare": { "credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", "dependencies": "acme=={{certbot-version}}", "full_plugin_name": "dns-cloudflare", "name": "Cloudflare", "package_name": "certbot-dns-cloudflare", "version": "=={{certbot-version}}" } }其中credentials是界面提示用户填写的凭证模板,full_plugin_name是 certbot 的认证器名称。后端通过GET /api/nginx/certificates/dns-providers把这份清单暴露给前端(backend/routes/nginx/certificates.js),前端在 frontend/src/components/Form/DNSProviderFields.tsx 中渲染为下拉选项。
当你选择某个 DNS 提供商后,后端会按需安装对应的 certbot 插件:installPlugin()使用 pip 在 certbot 虚拟环境中安装package_name + version指定的包(见 backend/lib/certbot.js),安装命令形如:
. /opt/certbot/bin/activate && pip install --no-cache-dir 'acme==<CERTBOT_VERSION>' 'certbot-dns-cloudflare==<CERTBOT_VERSION>' && deactivate凭证会以0600权限写入/etc/letsencrypt/credentials/credentials-<certificate_id>,避免其他用户读取(backend/internal/certificate.js)。其中 Route 53 比较特殊,它不是通过命令行参数传凭证,而是通过环境变量AWS_CONFIG_FILE指向凭证文件(backend/internal/certificate.js)。
3.4 签发流程与可调参数
DNS 验证的签发流程(backend/internal/certificate.js)同样会临时禁用占用这些域名的现有主机,但不需要生成临时的 nginx 配置(源码注释 "With DNS challenge no config is needed, so skip 3 and 5")。核心 certbot 命令为:
certbot certonly -n \ --config /etc/letsencrypt.ini \ --work-dir /tmp/letsencrypt-lib \ --logs-dir /data/logs \ --cert-name npm-<certificate_id> \ --agree-tos -m <你的邮箱> \ --preferred-challenges dns \ --domains '*.example.com,example.com' \ --authenticator dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/credentials/credentials-<certificate_id>除凭证外,你还可以配置以下参数(对应 backend/schema/components/certificate-object.json 中meta的字段定义):
| meta 字段 | 含义 | 默认值 |
|---|---|---|
dns_challenge | 是否启用 DNS 验证 | false |
dns_provider | DNS 提供商标识(对应 dns-plugins.json 的键) | 无 |
dns_provider_credentials | 供应商 API 凭证内容 | 无 |
propagation_seconds | 等待 TXT 记录全球生效的秒数 | 无(不传则用插件默认值) |
key_type | 私钥类型:rsa或ecdsa | rsa |
其中propagation_seconds会转换为 certbot 的--<plugin>-propagation-seconds参数(backend/internal/certificate.js),用于在 DNS 记录尚未全球生效时避免验证失败;key_type会转换为--key-type参数(backend/internal/certificate.js)。前端 frontend/src/modals/DNSCertificateModal.tsx 中 DNS 证书默认选择ecdsa密钥类型。
四、自定义证书(Custom Certificate)
4.1 使用场景
自定义证书用于上传你自己的 SSL 证书,通常由你的证书颁发机构(CA)签发,例如企业内部的私有 CA、付费商业证书,或由其他工具(如 acme.sh)签发的证书。该方式与 Let's Encrypt 无关,适合需要长期固定证书、或证书已由组织统一管理的场景。
4.2 可上传的文件
后端在 backend/internal/certificate.js 中定义了允许上传的三种文件(allowedSslFiles):
| 文件字段 | 说明 |
|---|---|
certificate | 证书本体(PEM 格式) |
certificate_key | 私钥 |
intermediate_certificate | 中间证书/证书链 |
上传时会先经过校验:私钥通过openssl pkey -check验证,证书通过openssl x509解析出 CN(Common Name)、签发者(issuer)与有效期(notBefore/notAfter),过期证书会被拒绝(backend/internal/certificate.js)。上传界面同时提供POST /api/nginx/certificates/validate预校验接口(backend/routes/nginx/certificates.js),在保存前即可发现证书与私钥不匹配等问题。
4.3 证书落盘与使用
自定义证书被写入/data/custom_ssl/npm-<certificate_id>/目录:如果提供了中间证书,会与证书本体拼接成fullchain.pem,私钥单独写入privkey.pem(backend/internal/certificate.js),供 nginx 引用。删除自定义证书时不会触发吊销操作;只有 Let's Encrypt 证书在删除时会调用revokeLetsEncryptSsl向 CA 吊销(backend/internal/certificate.js)。
五、证书生命周期:签发后的一切
5.1 自动续期
Let's Encrypt 证书的自动续期由后端内置的定时器驱动。在 backend/internal/certificate.js 中:
- 定时器间隔为1 小时(
intervalTimeout: 1000 * 60 * 60); - 续期阈值为到期前 30 天(
renewBeforeExpirationBy: [30, "days"]); - 每次触发时查询数据库中
provider = letsencrypt且expires_on早于阈值日期的证书,逐个串行执行续期——源码注释明确指出必须串行,否则会报 "Another instance of Certbot is already running" 错误(backend/internal/certificate.js)。
续期动作会写入审计日志(action: "renewed",见 backend/internal/certificate.js),并自动更新数据库中的expires_on。你也可以在界面上手动触发续期(POST /api/nginx/certificates/:id/renew,backend/routes/nginx/certificates.js)。
5.2 下载与导出
Let's Encrypt 证书支持打包下载:后端会把/etc/letsencrypt/live/npm-<id>/下所有.pem文件打包为 zip 返回(backend/internal/certificate.js),便于你在其他服务器或负载均衡器上复用同一份证书。自定义证书(provider = other)不支持此下载接口,且自定义证书仅允许在上传后以文件替换方式更新(POST /api/nginx/certificates/:id/upload,backend/routes/nginx/certificates.js)。
5.3 权限控制
证书操作受权限系统约束。创建证书要求用户具备certificates:create权限(backend/internal/certificate.js),对应的权限定义为"可管理证书",管理员角色默认拥有全部权限,普通用户可通过授权获得(见 backend/lib/access/certificates-create.json)。在证书列表与详情查询时,非管理员用户只能看到自己创建的证书(permission_visibility !== "all"时按owner_user_id过滤,见 backend/internal/certificate.js),这也意味着证书与创建它的用户绑定,多人协作时需注意归属问题。
六、选择建议与常见坑位
结合原文档与源码实现,给出如下实操建议:
- 普通子域名且 80 端口可达:优先选 HTTP 验证证书。注意签发后保留 Proxy Host 的 HTTP 访问,否则 30 天后自动续期必然失败。
- 需要通配符证书,或 80 端口不可达 / 使用 CDN 代理:选 DNS 验证证书。需要提前到 DNS 服务商后台创建 API Token,并按 backend/certbot/dns-plugins.json 中该供应商的
credentials模板填写凭证。若 TXT 记录生效较慢,可调大propagation_seconds。 - 企业内部 CA 或合规要求:选自定义证书,上传证书、私钥与中间证书三件套,注意私钥不能加密(带 passphrase 的私钥校验会超时失败)。
- 首次签发失败排查顺序:先使用证书页面的 HTTP 挑战测试确认域名可达;再确认账号邮箱已填写;最后查看
/data/logs/letsencrypt-requests_*.log与 certbot 日志(/data/logs)定位具体错误。 - 证书被多个主机引用时:签发过程中 nginx 会短暂重载、对应主机配置会被临时禁用再恢复(backend/internal/certificate.js),因此建议在业务低峰期执行首次签发。
通过以上三种证书方式的合理搭配,你可以在 nginx-proxy-manager 中构建一套完整的 TLS 证书管理方案:HTTP 验证覆盖常规站点、DNS 验证打通通配符与内网穿透场景、自定义证书满足企业合规要求,而自动续期与审计日志则保证了证书的全生命周期可维护、可追溯。
【免费下载链接】nginx-proxy-managerDocker container for managing Nginx proxy hosts with a simple, powerful interface项目地址: https://gitcode.com/GitHub_Trending/ng/nginx-proxy-manager
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考