From e857f808b0e3a5b1d432b6ffda0acce33b0d94e3 Mon Sep 17 00:00:00 2001 From: Sausageroll2077 <85130548+Sausageroll2077@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:05:41 +0200 Subject: [PATCH] Add CERT_PROFILE support and simplify renewal scheduling Add CERT_PROFILE env var for Let's Encrypt cert profiles. Run certbot twice daily via a cron entry with a random delay instead of the previous sed-based cron randomization. Update changelog. Co-Authored-By: Claude Opus 4.8 --- readme-vars.yml | 4 ++- root/etc/crontabs/root | 2 +- .../s6-rc.d/init-certbot-config/run | 25 ++++++++++++++++--- root/etc/s6-overlay/s6-rc.d/init-renew/run | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/readme-vars.yml b/readme-vars.yml index 38ada432..2f0abaf7 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -32,6 +32,7 @@ opt_param_usage_include_env: true opt_param_env_vars: - {env_var: "SUBDOMAINS", env_value: "www,", desc: "Subdomains you'd like the cert to cover (comma separated, no spaces) ie. `www,ftp,cloud`. For a wildcard cert, set this *exactly* to `wildcard` (wildcard cert is available via `dns` validation only)"} - {env_var: "CERTPROVIDER", env_value: "", desc: "Optionally define the cert provider. Set to `zerossl` for ZeroSSL certs (requires existing [ZeroSSL account](https://app.zerossl.com/signup) and the e-mail address entered in `EMAIL` env var). Otherwise defaults to Let's Encrypt."} + - {env_var: "CERT_PROFILE", env_value: "", desc: "Optionally define a cert profile to use for cert generation. This is useful if you want to use a custom cert profile instead of the default one. Currently only supported for Let's Encrypt. See https://letsencrypt.org/docs/profiles/ "} - {env_var: "DNSPLUGIN", env_value: "cloudflare", desc: "Required if `VALIDATION` is set to `dns`. Options are `acmedns`, `aliyun`, `azure`, `bunny`, `cloudflare`, `cpanel`, `desec`, `digitalocean`, `directadmin`, `dnsimple`, `dnsmadeeasy`, `dnspod`, `do`, `domeneshop`, `dreamhost`, `duckdns`, `dynu`, `freedns`, `gandi`, `gehirn`, `glesys`, `godaddy`, `google`, `he`, `hetzner`, `hetzner-cloud`, `infomaniak`, `inwx`, `ionos`, `linode`, `loopia`, `luadns`, `mijn-host`, `namecheap`, `netcup`, `njalla`, `nsone`, `ovh`, `porkbun`, `rfc2136`, `route53`, `sakuracloud`, `standalone`, `transip`, and `vultr`. Also need to enter the credentials into the corresponding ini (or json for some plugins) file under `/config/dns-conf`."} - {env_var: "PROPAGATION", env_value: "", desc: "Optionally override (in seconds) the default propagation time for the dns plugins."} - {env_var: "EMAIL", env_value: "", desc: "Optional e-mail address used for cert expiration notifications (Required for ZeroSSL)."} @@ -65,7 +66,7 @@ app_setup_block: | 2. Certs that cover sub-subdomains of your main subdomain (ie. `*.yoursubdomain.duckdns.org`, set the `SUBDOMAINS` variable to `wildcard`) * `--cap-add=NET_ADMIN` is required for fail2ban to modify iptables * After setup, navigate to `https://example.com` to access the default homepage (http access through port 80 is disabled by default, you can enable it by editing the default site config at `/config/nginx/site-confs/default.conf`). - * Certs are checked nightly and if expiration is within 30 days, renewal is attempted. If your cert is about to expire in less than 30 days, check the logs under `/config/log/letsencrypt` to see why the renewals have been failing. It is recommended to input your e-mail in docker parameters so you receive expiration notices from Let's Encrypt in those circumstances. + * Certs are checked twice daily using ACME Renewal Information (ARI) to determine the optimal renewal window. If your cert is about to expire, check the logs under `/config/log/letsencrypt` to see why the renewals have been failing. ### Certbot Plugins @@ -219,6 +220,7 @@ init_diagram: | "swag:latest" <- Base Images # changelog changelogs: + - {date: "10.07.26:", desc: "Add support for Let's Encrypt cert profiles. Run certbot twice daily with a random delay." } - {date: "19.06.26:", desc: "Add support for mijn.host dns validation."} - {date: "01.06.26:", desc: "Remove obsolete old cert check logic."} - {date: "23.01.26:", desc: "Reorder init to fix proxy conf version checks."} diff --git a/root/etc/crontabs/root b/root/etc/crontabs/root index c848b3f5..49c2d318 100644 --- a/root/etc/crontabs/root +++ b/root/etc/crontabs/root @@ -5,4 +5,4 @@ 0 3 * * 6 run-parts /etc/periodic/weekly 0 5 1 * * run-parts /etc/periodic/monthly -8 2 * * * /app/le-renew.sh >> /config/log/letsencrypt/renewal.log 2>&1 +0 */12 * * * sleep $((60 + $RANDOM % 230)); /app/le-renew.sh >> /config/log/letsencrypt/renewal.log 2>&1 diff --git a/root/etc/s6-overlay/s6-rc.d/init-certbot-config/run b/root/etc/s6-overlay/s6-rc.d/init-certbot-config/run index e018d62e..f0cf5826 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-certbot-config/run +++ b/root/etc/s6-overlay/s6-rc.d/init-certbot-config/run @@ -12,12 +12,13 @@ EXTRA_DOMAINS=${EXTRA_DOMAINS}\\n\ ONLY_SUBDOMAINS=${ONLY_SUBDOMAINS}\\n\ VALIDATION=${VALIDATION}\\n\ CERTPROVIDER=${CERTPROVIDER}\\n\ +CERT_PROFILE=${CERT_PROFILE}\\n\ DNSPLUGIN=${DNSPLUGIN}\\n\ EMAIL=${EMAIL}\\n\ STAGING=${STAGING}\\n" # Sanitize variables -SANED_VARS=(DNSPLUGIN EMAIL EXTRA_DOMAINS ONLY_SUBDOMAINS STAGING SUBDOMAINS URL VALIDATION CERTPROVIDER) +SANED_VARS=(DNSPLUGIN EMAIL EXTRA_DOMAINS ONLY_SUBDOMAINS STAGING SUBDOMAINS URL VALIDATION CERTPROVIDER CERT_PROFILE) for i in "${SANED_VARS[@]}"; do export echo "${i}"="${!i//\"/}" export echo "${i}"="$(echo "${!i}" | tr '[:upper:]' '[:lower:]')" @@ -80,7 +81,7 @@ if [[ -f "/config/donoteditthisfile.conf" ]]; then mv /config/donoteditthisfile.conf /config/.donoteditthisfile.conf fi if [[ ! -f "/config/.donoteditthisfile.conf" ]]; then - echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf + echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\" ORIGCERT_PROFILE=\"${CERT_PROFILE}\"" >/config/.donoteditthisfile.conf echo "Created .donoteditthisfile.conf" fi @@ -186,7 +187,8 @@ if [[ ! "${URL}" = "${ORIGURL}" ]] || [[ ! "${DNSPLUGIN}" = "${ORIGDNSPLUGIN}" ]] || [[ ! "${PROPAGATION}" = "${ORIGPROPAGATION}" ]] || [[ ! "${STAGING}" = "${ORIGSTAGING}" ]] || - [[ ! "${CERTPROVIDER}" = "${ORIGCERTPROVIDER}" ]]; then + [[ ! "${CERTPROVIDER}" = "${ORIGCERTPROVIDER}" ]] || + [[ ! "${CERT_PROFILE}" = "${ORIGCERT_PROFILE}" ]]; then echo "Different validation parameters entered than what was used before. Revoking and deleting existing certificate, and an updated one will be created" if [[ "${ORIGCERTPROVIDER}" = "zerossl" ]]; then REV_ACMESERVER=("https://acme.zerossl.com/v2/DV90") @@ -204,7 +206,7 @@ if [[ ! "${URL}" = "${ORIGURL}" ]] || fi # saving new variables -echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\"" >/config/.donoteditthisfile.conf +echo -e "ORIGURL=\"${URL}\" ORIGSUBDOMAINS=\"${SUBDOMAINS}\" ORIGONLY_SUBDOMAINS=\"${ONLY_SUBDOMAINS}\" ORIGEXTRA_DOMAINS=\"${EXTRA_DOMAINS}\" ORIGVALIDATION=\"${VALIDATION}\" ORIGDNSPLUGIN=\"${DNSPLUGIN}\" ORIGPROPAGATION=\"${PROPAGATION}\" ORIGSTAGING=\"${STAGING}\" ORIGCERTPROVIDER=\"${CERTPROVIDER}\" ORIGEMAIL=\"${EMAIL}\" ORIGCERT_PROFILE=\"${CERT_PROFILE}\"" >/config/.donoteditthisfile.conf # if zerossl is selected or staging is set to true, use the relevant server if [[ "${CERTPROVIDER}" = "zerossl" ]] && [[ "${STAGING}" = "true" ]]; then @@ -227,6 +229,21 @@ fi set_ini_value "server" "${ACMESERVER}" /config/etc/letsencrypt/cli.ini +# set certificate profile (e.g. "shortlived" for 6-day certs, "classic" for 90-day) +# Profiles are a Let's Encrypt ACME feature; ZeroSSL ignores it. +if [[ -n "${CERT_PROFILE}" ]]; then + if [[ "${CERTPROVIDER}" = "zerossl" ]]; then + echo "ZeroSSL does not support ACME profiles, ignoring CERT_PROFILE variable" + sed -i "/^preferred-profile\b/d" /config/etc/letsencrypt/cli.ini + else + echo "Requesting certificate with profile: ${CERT_PROFILE}" + set_ini_value "preferred-profile" "${CERT_PROFILE}" /config/etc/letsencrypt/cli.ini + fi +else + # remove if previously set so going back to default works + sed -i "/^preferred-profile\b/d" /config/etc/letsencrypt/cli.ini +fi + # figuring out domain only vs domain & subdomains vs subdomains only DOMAINS_ARRAY=() if [[ -z "${SUBDOMAINS}" ]] || [[ "${ONLY_SUBDOMAINS}" != true ]]; then diff --git a/root/etc/s6-overlay/s6-rc.d/init-renew/run b/root/etc/s6-overlay/s6-rc.d/init-renew/run index b402a0b0..5645a5b7 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-renew/run +++ b/root/etc/s6-overlay/s6-rc.d/init-renew/run @@ -3,7 +3,7 @@ # Check if the cert is expired or expires within a day, if so, renew if openssl x509 -in /config/keys/letsencrypt/fullchain.pem -noout -checkend 86400 >/dev/null; then - echo "The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am)." + echo "The cert does not expire within the next day. Letting the cron script handle the renewal attempts." else echo "The cert is either expired or it expires within the next day. Attempting to renew. This could take up to 10 minutes." /app/le-renew.sh