Projects/Site-Wide HTTPS and Automatic Certificate Renewal
Network

Site-Wide HTTPS and Automatic Certificate Renewal

NPM + certbot automatically issue and renew wildcard certificates, unifying all self-hosted services under HTTPS.

What It Is

This is the unified certificate and HTTPS layer for my Homelab: NPM (Nginx Proxy Manager) serves as the reverse proxy entry point, certbot handles issuance and renewal, and all public domains of my self-hosted services share the same Let's Encrypt certificate.

Why I Did It

During a health check, I found that the renewal processes for both existing certificates had failed: one used manual + manually adding TXT records, and certbot renew exited with code 1 directly; the other used standalone, but ports 80/443 are all occupied by the NPM container, so it was bound to fail. In other words, once 12-31 passes, HTTPS across the site would start reporting certificate errors. The domain was purchased from e8hk, and I do not have a Cloudflare dashboard API Token, so the unattended DNS-01 path required for a wildcard certificate is not viable.

How I Solved It

I switched to HTTP-01 file validation + webroot: I added the /.well-known/acme-challenge/ challenge directory to NPM's port 80 server blocks (7 places in total). The certificate covers only the 6 domains that currently actually have public A records. The certificate name remains xiejun-server.top, and not a single line of NPM configuration was changed. The new certificate is valid until 2027-01-11, certbot renew --dry-run passed, and certbot.timer runs automatically every day.

Current Status

Running. The only cost is losing the wildcard, which only affects internal names that are not resolved on the public internet; these services are accessed by path anyway, so there is no practical impact. To add subdomains later, after adding the A records in DNS, run one command to expand the certificate.

Features

  • 13 个主页入口 + 6 个公网域名走真实公网链路做全量体检,逐项核对状态码、TLS 与 HSTS
  • 把失效的 manual/standalone 续期方式改为 HTTP-01 webroot 文件验证,续期重新可无人值守
  • 在 NPM 的 80 端口 server 块内加 ACME 挑战目录,证书名不变、反代配置零改动
  • certbot.timer 每天自动续期,并用 --dry-run 演练验证整条链路
  • 顺带定位并修掉 .223 走 8.8.8.8 导致的偶发十秒级卡顿,改用本机 unbound 分流
  • 留了完整的备份与回退路径:nginx 配置、certbot renewal 配置、resolv.conf 快照

Stack: Let's Encrypt · Nginx Proxy Manager · certbot · HTTP-01 / webroot · systemd timer · unbound · Docker

Prerequisites

  • The domain has a public A record added and pointing to the ingress IP (HTTP-01 validation can only issue certificates for domains with A records; wildcards cannot be done).
  • Port 80 is occupied by NPM, so standalone cannot be used; webroot must be used.

Steps

1. Create the challenge directory and mount it into the NPM container

mkdir -p /data/acme-webroot/.well-known/acme-challenge

Confirm that this path is mounted to the same location inside the NPM container and that it is readable inside the container.

2. Add the challenge location to NPM's port 80 server block

Every site that needs issuance (including shared configurations such as 1.conf, 9-shared-hosts.conf, etc.) must add:

location ^~ /.well-known/acme-challenge/ {
    root /data/acme-webroot;
    default_type text/plain;
}

After making the changes, reload NPM, then first use curl http://<domain>/.well-known/acme-challenge/test to confirm that the file can be read.

3. Issue the certificate

Reuse the old certificate name so that not a single reference in NPM needs to be changed:

certbot certonly --webroot -w /data/acme-webroot \
  --cert-name xiejun-server.top \
  -d xiejun-server.top -d memory.xiejun-server.top -d photo.xiejun-server.top \
  -d dsh.xiejun-server.top -d ledger.xiejun-server.top -d video.xiejun-server.top

Include only the domains that actually have public A records; extra domains will cause validation to fail.

4. Dry run and automatic renewal

certbot renew --dry-run
systemctl enable --now certbot.timer
systemctl list-timers | grep certbot

Do not stop until all dry-run tests pass; don't just see "certificate issued successfully" and call it done.

5. Adding a new subdomain for expansion

After adding the A record in DNS, run on the ingress machine:

bash /root/add-subdomain-cert.sh newsite.xiejun-server.top

6. Rollback

The nginx configuration backups are in /root/cert-http01-bak-*, and the certbot renewal configuration backups are in /root/renewal-xiejun-server.top.conf.bak-*. Copy them back and reload to restore the original state.