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(NPM)证书模块的完整实战指南,围绕HTTP 证书(HTTP-01 挑战)、DNS 证书(DNS-01 挑战)与自定义证书上传三种证书获取方式展开,覆盖前置条件、界面操作、Let's Encrypt 签发原理、自动续期机制与常见排障。读完本文,你将能根据自身域名与网络条件正确选择证书方案,并理解 NPM 在签发与续期过程中对 Nginx 配置、DNS 记录和文件系统的底层操作。本文内容以仓库内帮助文档 frontend/src/locale/src/HelpDoc/en/Certificates.md(及对应乌克兰语版本)为主体,结合后端源码与前端表单实现进行纵深讲解。
一、NPM 中证书体系概览
在 Nginx Proxy Manager 中,证书是"域名 + 私钥 + 证书链"的集合,被 Proxy Hosts、Redirection Hosts、Dead Hosts 与 Streams 引用。证书创建入口位于前端 "Certificates" 页面,对应 API 为POST /api/nginx/certificates,实现位于 backend/internal/certificate.js 的create方法。
从数据模型看(backend/models/certificate.js),每条证书记录都带有provider字段,用于区分证书来源:
| provider 值 | 来源 | 自动续期 | 通配符域名 |
|---|---|---|---|
letsencrypt(HTTP 挑战) | Let's Encrypt 通过 HTTP 验证 | 支持 | 不支持 |
letsencrypt(DNS 挑战,meta.dns_challenge = true) | Let's Encrypt 通过 DNS 验证 | 支持 | 支持 |
other | 用户上传的自有 CA 证书 | 不支持 | 取决于证书本身 |
其中前两类均由 NPM 调用certbot命令签发,后端封装了完整的请求、续期与吊销流程(见 backend/internal/certificate.js);第三类则完全由用户提供证书材料,NPM 负责校验并落盘。
二、HTTP 证书:通过 80 端口完成域名验证
2.1 原理与前置条件
HTTP 证书使用 Let's Encrypt 的HTTP-01 挑战(webroot 模式)。Let's Encrypt 服务器会尝试通过HTTP(而非 HTTPS)访问你域名下的挑战路径/.well-known/acme-challenge/xxx,只有返回内容正确,才会签发证书。
在 NPM 中,这一方法有两个硬性前置条件:
- 必须已为该域名创建了 Proxy Host,且该 Proxy Host 通过 HTTP 可访问;
- 该 Proxy Host 的流量必须指向当前这台 Nginx 实例,因为挑战文件由本机 Nginx 对外提供。
签发成功后,你可以修改该 Proxy Host 使用这张证书开启 HTTPS;但为了让证书后续能够自动续期,该 Proxy Host 必须继续保持 HTTP 可访问——这是本帮助文档明确强调的约束(见 frontend/src/locale/src/HelpDoc/en/Certificates.md)。
该方法不支持通配符域名(如*.example.com)。
2.2 操作步骤
- 进入Certificates页面,点击Add SSL Certificate → Let's Encrypt;
- 选择HTTP Certificate方式;
- 在 "Domain Names" 中输入域名(可填多个),并选择密钥类型(Key Type):RSA 或 ECDSA,默认 ECDSA(见 frontend/src/modals/HTTPCertificateModal.tsx);
- 点击Save,NPM 将调用后端完成签发。
2.3 签发背后的完整流程(源码级)
后端create方法对 HTTP 挑战执行了严格的六步流程(backend/internal/certificate.js):
- 通过
internalHost.getHostsWithDomains找出所有使用了这些域名的 Proxy Host / Redirection Host / Dead Host; - 通过
disableInUseHosts临时移除这些主机的 Nginx 配置; - 调用
internalNginx.generateLetsEncryptRequestConfig生成一份临时的 Let's Encrypt 请求配置(模板见 backend/templates/letsencrypt-request.conf); - 重新加载 Nginx 后调用
requestLetsEncryptSsl请求证书; - 删除临时配置并再次 reload;
- 通过
enableInUseHosts恢复之前被禁用的主机配置。
之所以要"先禁用再恢复",是因为签发期间域名需要由挑战专用配置响应/.well-known/acme-challenge/路径。该路径由 docker/rootfs/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf 定义,将请求映射到/data/letsencrypt-acme-challenge目录,并显式关闭auth_basic与 IP ACL,以放行 Let's Encrypt 的验证请求。
真正的签发由 certbot 完成,requestLetsEncryptSsl(backend/internal/certificate.js)拼装的核心参数为:
certbot certonly \ -n \ --config /etc/letsencrypt.ini \ --work-dir /tmp/letsencrypt-lib \ --logs-dir /data/logs \ --cert-name npm-<certificateId> \ --agree-tos \ --authenticator webroot \ -m <用户邮箱> \ --preferred-challenges http \ --domains <域名1,域名2,...> \ [--key-type rsa|ecdsa]签发完成后,NPM 会从/etc/letsencrypt/live/npm-<id>/fullchain.pem读取证书到期时间并回写数据库。
2.4 签发前的 HTTP 可达性测试
前端表单提供了 "Test" 按钮(frontend/src/modals/HTTPCertificateModal.tsx),调用POST /api/nginx/certificates/test-http。后端testHttpsChallenge(backend/internal/certificate.js)会先在/data/letsencrypt-acme-challenge/.well-known/acme-challenge/写入一个test-challenge测试文件,然后逐个域名请求http://<域名>/.well-known/acme-challenge/test-challenge,最后清理测试文件。返回结果对应前端展示的状态:
ok:服务器返回 200 且内容正确,可以签发;no-host:域名无法解析;404:主机存在但未返回挑战文件;wrong-data:返回了错误内容;failed/other:<code>:其他错误。
建议在正式签发前先运行该测试,避免因 DNS 未生效或 Proxy Host 未指向本机而导致签发失败。
三、DNS 证书:通过 DNS 记录完成域名验证
3.1 原理与优势
DNS 证书使用DNS-01 挑战:NPM 借助DNS Provider 插件在你的域名 DNS 中自动创建一条临时的 TXT 记录,Let's Encrypt 查询该记录确认你对域名的控制权后签发证书。
与 HTTP 方式相比,DNS 方式有两点关键差异:
- 不需要预先创建 Proxy Host,也不要求任何主机通过 HTTP 可访问;
- 支持通配符域名(如
*.example.com),这是签发泛域名证书的唯一内置途径。
3.2 DNS Provider 插件体系
NPM 内置了庞大的 DNS 插件清单,定义于 backend/certbot/dns-plugins.json,包含 Cloudflare、DigitalOcean、Cloudflare、Google、Route 53、OVH、Vultr、Hetzner、阿里云(Aliyun)、腾讯云(Tencent Cloud)、DNSPod、DuckDNS、Namecheap、GoDaddy、CloudXNS 等上百家提供商。每条记录包含插件名称、certbot 包名、版本约束与凭据模板。
例如 Cloudflare 的凭据模板为:
# Cloudflare API token dns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567阿里云的模板为:
dns_aliyun_access_key = 12345678 dns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef前端通过GET /api/nginx/certificates/dns-providers(backend/routes/nginx/certificates.js)拉取提供商列表,选择后自动带入对应的凭据模板,供用户填写(frontend/src/components/Form/DNSProviderFields.tsx)。
3.3 操作步骤
- 进入Certificates → Add SSL Certificate → Let's Encrypt,选择DNS Certificate;
- 填写域名(勾选通配符时输入
*.example.com); - 选择 DNS Provider;
- 在凭据编辑器中填写该提供商的 API 凭据(界面会提示凭据以明文保存在数据库中,需谨慎对待,见 frontend/src/components/Form/DNSProviderFields.tsx);
- 可选设置Propagation Seconds(DNS 记录传播等待时间,0~7200 秒),用于等待 TXT 记录在全球生效后再让 Let's Encrypt 校验;
- 点击 Save。
3.4 签发背后的完整流程(源码级)
DNS 挑战走requestLetsEncryptSslWithDnsChallenge(backend/internal/certificate.js),关键步骤:
- 调用
installPlugin按需安装对应的 certbot DNS 插件; - 将用户填写的凭据以0600 权限写入
/etc/letsencrypt/credentials/credentials-<id>,避免敏感信息被其他用户读取; - 执行 certbot:
certbot certonly \ -n \ --config /etc/letsencrypt.ini \ --cert-name npm-<certificateId> \ --agree-tos \ -m <用户邮箱> \ --preferred-challenges dns \ --domains <域名1,域名2,...> \ --authenticator <dns插件全名> \ --<dns插件全名>-credentials /etc/letsencrypt/credentials/credentials-<id> \ [--<dns插件全名>-propagation-seconds <秒数>]对于 Route 53 这类特殊插件,不传--credentials参数,而是通过环境变量AWS_CONFIG_FILE指向凭据文件(backend/internal/certificate.js);DuckDNS 则会追加--dns-duckdns-no-txt-restore参数。
值得注意的是,由于 DNS 挑战不需要临时 Nginx 配置,create流程中会跳过"生成/删除 LE 请求配置"两步,仅做 reload 与主机启用/禁用处理(backend/internal/certificate.js)。这也从实现层面印证了文档所述:"申请 DNS 证书前无需创建 Proxy Host"。
四、自定义证书:上传自有 CA 签发的证书
4.1 适用场景
如果你已经拥有由自己的 CA(证书颁发机构)签发的 SSL 证书,例如企业内网 CA、商业证书或已在其他平台申请的证书,可以使用Custom Certificate方式直接上传,无需经过 Let's Encrypt。后端将此类证书的provider记为other。
4.2 需要准备的三份文件
在 frontend/src/modals/CustomCertificateModal.tsx 中可以看到,上传表单包含三个文件输入框与一个名称输入框:
| 字段 | 说明 | 是否必填 |
|---|---|---|
| Name | 证书显示名称(1~255 字符) | 必填 |
| Certificate Key | 私钥文件(PEM) | 必填 |
| Certificate | 证书文件(PEM,含公钥与主体信息) | 必填 |
| Intermediate Certificate | 中间证书链文件(PEM) | 选填 |
4.3 校验—创建—上传三段式流程
前端的提交逻辑(frontend/src/modals/CustomCertificateModal.tsx)清晰展示了三段式流程:
- 校验:
POST /api/nginx/certificates/validate,对私钥与证书做合法性检查; - 创建:
POST /api/nginx/certificates,创建provider: "other"的证书空记录; - 上传:
POST /api/nginx/certificates/<id>/upload,将文件写入后端存储。
后端校验逻辑在internalCertificate.validate(backend/internal/certificate.js):
- 私钥通过
openssl pkey -in <file> -check -noout验证,若包含密码保护或格式非法会直接报错(checkPrivateKey,backend/internal/certificate.js); - 证书与中间证书通过
openssl x509解析出 CN(subject)、颁发者(issuer)与有效期(notBefore/notAfter),并检测是否已过期(getCertificateInfoFromFile,backend/internal/certificate.js)。
上传成功后,后端会将证书链与私钥写入/data/custom_ssl/npm-<id>/fullchain.pem与privkey.pem(writeCustomCert,backend/internal/certificate.js),并把证书的 CN 与到期时间回写数据库。若提供了中间证书,会自动追加到fullchain.pem中。
自定义证书不会自动续期,到期后需要你手动重新上传替换。
五、Let's Encrypt 证书的自动续期机制
5.1 续期定时器
NPM 后端启动时会初始化一个每小时执行一次的续期定时器(initTimer,backend/internal/certificate.js),并立即触发一次检查。检查逻辑(processExpiringHosts,backend/internal/certificate.js)会查询所有provider = "letsencrypt"且距离到期不足 30 天的证书,逐个执行续期。
5.2 串行续期避免冲突
源码注释明确说明:续期必须串行执行,否则会触发 certbot 的 "Another instance of Certbot is already running" 错误(backend/internal/certificate.js)。因此所有待续期证书通过 Promise 链依次处理,单个失败仅记录日志,不会中断其余证书的续期。
5.3 手动续期与吊销
- 手动续期:Certificates 页面提供 Renew 按钮,调用
POST /api/nginx/certificates/<id>/renew(backend/routes/nginx/certificates.js)。后端按证书是否启用 DNS 挑战自动选择续期方式,并在成功后更新expires_on与审计日志。注意:只有letsencrypt类型的证书允许续期(backend/internal/certificate.js)。 - 删除吊销:删除 Let's Encrypt 证书时,后端会执行
certbot revoke --delete-after-revoke主动吊销(backend/internal/certificate.js)。 - 下载备份:Let's Encrypt 证书可从页面打包下载(
GET /api/nginx/certificates/<id>/download),后端会将/etc/letsencrypt/live/npm-<id>/下的.pem文件压缩为 zip(backend/internal/certificate.js)。
六、权限、邮箱与其他配置要点
6.1 操作权限
证书的新增、修改、删除与列表查看均有独立权限点(certificates:create、certificates:update、certificates:delete、certificates:get、certificates:list),前端也通过CERTIFICATES/MANAGE权限控制按钮显隐(frontend/src/pages/Certificates/Table.tsx)。非管理员用户需要被授予证书管理权限,见权限校验定义 backend/lib/access/certificates-create.json。
6.2 用户邮箱是硬性要求
签发 Let's Encrypt 证书时,certbot 需要注册邮箱。后端create流程会校验当前用户是否设置了有效邮箱,否则抛出 "A valid email address must be set on your user account to use Let's Encrypt" 错误(backend/internal/certificate.js)。因此首次使用 Let's Encrypt 前,请先在用户资料中填写邮箱。
6.3 环境级配置
backend/internal/certificate.js 中的getAdditionalCertbotArgs展示了两个可配置项:
- 自定义 ACME 服务器:通过
LE_SERVER环境变量传入--server <url>; - Let's Encrypt 预演环境:配置
LE_STAGING后追加--staging,用于在正式签发前测试流程(预演证书不受速率限制)。
七、常见问题与排障清单
| 现象 | 可能原因与排查方向 |
|---|---|
HTTP 证书签发失败,测试返回no-host | 域名 DNS 未解析到本机,或 Proxy Host 未指向本 Nginx 实例 |
HTTP 证书签发失败,返回404 | Proxy Host 存在但未走挑战配置路径,检查 Nginx 是否已 reload |
| 提示需要邮箱 | 当前用户账户未设置邮箱,见 6.2 节 |
| DNS 证书签发超时 | TXT 记录传播慢,调大 Propagation Seconds(0~7200 秒) |
| 通配符域名无法通过 HTTP 方式签发 | 通配符只支持 DNS 方式,请改用 DNS Certificate |
| 自定义证书上传报"私钥无效" | 私钥含密码保护或格式非法,需使用无密码的 PEM 私钥 |
| 证书到期未自动续期 | 确认 Proxy Host 仍保持 HTTP 可访问(HTTP 方式);续期定时器每小时运行一次,可先手动点击 Renew |
结语
Nginx Proxy Manager 将 Let's Encrypt 的 HTTP-01 与 DNS-01 两种挑战方式、上百家 DNS 提供商的插件体系以及自有证书上传统一封装在 Certificates 页面之下,后端通过 backend/internal/certificate.js 与 certbot 协作完成签发、续期、吊销与文件落盘,前端通过 frontend/src/modals/HTTPCertificateModal.tsx、frontend/src/modals/DNSCertificateModal.tsx、frontend/src/modals/CustomCertificateModal.tsx 提供操作界面。选择证书方式的核心原则是:能通过 HTTP 访问本机且不需要通配符 → 用 HTTP 方式;需要通配符或主机不在公网 80 端口可达 → 用 DNS 方式;已有自有 CA 证书 → 直接上传自定义证书。结合本文提供的源码级流程与排障清单,你可以更准确地完成证书配置并快速定位问题。
【免费下载链接】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),仅供参考