From cb6db02afc693ff1be69361cf8d2dcbc78f2eeec Mon Sep 17 00:00:00 2001 From: aleskxyz <39186039+aleskxyz@users.noreply.github.com> Date: Tue, 17 Mar 2026 04:45:49 +0100 Subject: [PATCH 1/2] Add proxy support with proxychain Signed-off-by: aleskxyz <39186039+aleskxyz@users.noreply.github.com> --- README.md | 7 ++++- src/Dockerfile | 9 +++++-- src/entrypoint.sh | 67 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e2d88c8..2766a3e 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The following environment variables can be used to customize the Certbot contain | `CERTBOT_DOMAINS` | Comma-separated list of domains for which to obtain the certificate (example: `example.com,www.example.com`) | - | | `CERTBOT_CERT_NAME` | Explicit certificate name to update/modify ([See official docs →](https://eff-certbot.readthedocs.io/en/stable/using.html#changing-a-certificate-s-domains)) | - | | `CERTBOT_EXPAND` | **DEPRECATED**: Expand existing certificate to add domains (use CERTBOT_CERT_NAME instead, [see official docs →](https://eff-certbot.readthedocs.io/en/stable/using.html#re-creating-and-updating-existing-certificates)) | `false` | -| `CERTBOT_EMAIL` | Email address for Let's Encrypt notifications | - | +| `CERTBOT_EMAIL` | **Optional.** Email address for Let's Encrypt notifications. If omitted, the container will register with Let's Encrypt using `--register-unsafely-without-email`. | - | | `CERTBOT_KEY_TYPE` | Type of private key to generate | `ecdsa` | | `CERTBOT_SERVER` | The ACME server URL | `https://acme-v02.api.letsencrypt.org/directory` | | `CLOUDFLARE_API_TOKEN` | Cloudflare API token for DNS authentication (see below how to create one) | - | @@ -70,6 +70,11 @@ The following environment variables can be used to customize the Certbot contain | `PGID` | The group ID to run certbot as | `0` | | `RENEWAL_INTERVAL` | Interval between certificate renewal checks. Set to `0` to disable renewals and only run once. | 43200 seconds (12 hours) | | `REPLACE_SYMLINKS` | Replaces symlinks with direct copies of the files they reference (required for Windows) | `false` | +| `PROXY_TYPE` | Proxy type for routing Certbot traffic. Supported values: `none`, `socks5`, `socks4`, `http`, `https`. When `none`, no proxy is used. | `none` | +| `PROXY_HOST` | Proxy hostname or IP address, required when `PROXY_TYPE` is not `none`. | - | +| `PROXY_PORT` | Proxy port, required when `PROXY_TYPE` is not `none`. | - | +| `PROXY_USERNAME` | Optional username for authenticated proxies. | - | +| `PROXY_PASSWORD` | Optional password for authenticated proxies. | - | ### Creating a Cloudflare API Token diff --git a/src/Dockerfile b/src/Dockerfile index 2a2479e..7e6462c 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -20,12 +20,17 @@ ENV CERTBOT_DOMAINS="" \ PUID=0 \ PGID=0 \ RENEWAL_INTERVAL=43200 \ - REPLACE_SYMLINKS=false + REPLACE_SYMLINKS=false \ + PROXY_TYPE="none" \ + PROXY_HOST="" \ + PROXY_PORT="" \ + PROXY_USERNAME="" \ + PROXY_PASSWORD="" COPY --chmod=700 entrypoint.sh /entrypoint.sh RUN apk update && \ - apk add --no-cache shadow su-exec && \ + apk add --no-cache shadow su-exec proxychains-ng && \ addgroup -g "${CERTBOT_GID}" "${CERTBOT_GROUP}" && \ adduser -u "${CERTBOT_UID}" -G "${CERTBOT_GROUP}" -D -H "${CERTBOT_USER}" && \ mkdir -p /var/log/letsencrypt && \ diff --git a/src/entrypoint.sh b/src/entrypoint.sh index 6f77ee9..c2cbd13 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -88,6 +88,54 @@ is_default_privileges() { [ "${PUID:-$default_uid}" = "$default_uid" ] && [ "${PGID:-$default_gid}" = "$default_gid" ] } +configure_proxychains() { + # Build /etc/proxychains.conf from environment variables when a proxy is configured + proxy_type="${PROXY_TYPE:-none}" + + if [ "$proxy_type" = "none" ]; then + debug_print "Proxychains proxy not configured (PROXY_TYPE=none)." + return 0 + fi + + case "$proxy_type" in + socks5|socks4|http|https) + ;; + *) + echo "Error: Invalid PROXY_TYPE '$proxy_type'. Supported values: socks5, socks4, http, https, none" + exit 1 + ;; + esac + + if [ -z "$PROXY_HOST" ] || [ -z "$PROXY_PORT" ]; then + echo "Error: PROXY_HOST and PROXY_PORT must be set when PROXY_TYPE is not 'none'" + exit 1 + fi + + # Use the default path that proxychains4 inside this image reads by default + conf_dir="/etc/proxychains" + conf_path="${conf_dir}/proxychains.conf" + + mkdir -p "$conf_dir" + + debug_print "Generating $conf_path for proxy $proxy_type $PROXY_HOST:$PROXY_PORT" + + { + echo "strict_chain" + echo "proxy_dns" + echo + echo "[ProxyList]" + if [ -n "$PROXY_USERNAME" ] && [ -n "$PROXY_PASSWORD" ]; then + echo "${proxy_type} ${PROXY_HOST} ${PROXY_PORT} ${PROXY_USERNAME} ${PROXY_PASSWORD}" + else + echo "${proxy_type} ${PROXY_HOST} ${PROXY_PORT}" + fi + } > "$conf_path" + + # Ensure the certbot user can read the config + chmod 644 "$conf_path" + chown "${default_unprivileged_user}:${default_unprivileged_group}" "$conf_path" 2>/dev/null || true +} + run_certbot() { # Ensure the log directory is set to 700 chmod 700 /var/log/letsencrypt @@ -99,6 +147,12 @@ run_certbot() { certbot_cmd="su-exec ${default_unprivileged_user} certbot" fi + # Optionally wrap certbot with proxychains to route traffic via a proxy + if [ "${PROXY_TYPE:-none}" != "none" ]; then + debug_print "Wrapping certbot with proxychains4 based on proxy settings" + certbot_cmd="proxychains4 ${certbot_cmd}" + fi + debug_print "Running certbot with command: $certbot_cmd" # Add -v flag if DEBUG is enabled @@ -115,13 +169,20 @@ run_certbot() { fi # Run certbot command + email_args="" + if [ -n "$CERTBOT_EMAIL" ]; then + email_args="--email $CERTBOT_EMAIL" + else + email_args="--register-unsafely-without-email" + fi + $certbot_cmd $debug_flag certonly \ --dns-cloudflare \ --dns-cloudflare-credentials "$CLOUDFLARE_CREDENTIALS_FILE" \ --dns-cloudflare-propagation-seconds "$CLOUDFLARE_PROPAGATION_SECONDS" \ -d "$CERTBOT_DOMAINS" \ --key-type "$CERTBOT_KEY_TYPE" \ - --email "$CERTBOT_EMAIL" \ + $email_args \ --server "$CERTBOT_SERVER" \ --agree-tos \ --non-interactive \ @@ -140,7 +201,7 @@ run_certbot() { validate_environment_variables() { # Validate required environment variables - for var in CLOUDFLARE_API_TOKEN CERTBOT_DOMAINS CERTBOT_EMAIL CERTBOT_KEY_TYPE CERTBOT_SERVER CLOUDFLARE_CREDENTIALS_FILE CLOUDFLARE_PROPAGATION_SECONDS; do + for var in CLOUDFLARE_API_TOKEN CERTBOT_DOMAINS CERTBOT_KEY_TYPE CERTBOT_SERVER CLOUDFLARE_CREDENTIALS_FILE CLOUDFLARE_PROPAGATION_SECONDS; do if [ -z "$(eval echo \$$var)" ]; then echo "Error: $var environment variable is not set" exit 1 @@ -161,6 +222,8 @@ fi validate_environment_variables +configure_proxychains + if ! is_default_privileges; then configure_uid_and_gid fi From ca679d4760a9aadac73c348609c37c498086cd91 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 17 Jul 2026 10:31:47 -0500 Subject: [PATCH 2/2] Rework proxy support to standard env vars; make CERTBOT_EMAIL optional Replace the proxychains-based proxy implementation from #31 with the standard HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment variables, which certbot (requests) and the Cloudflare SDK (httpx) honor natively. The image now installs pysocks and socksio so SOCKS proxies work in both HTTP stacks, and the README documents the new usage. This avoids writing proxy credentials to disk and removes the LD_PRELOAD layer. CERTBOT_EMAIL is now optional: when unset, certbot registers with --register-unsafely-without-email (Let's Encrypt no longer sends expiration emails as of June 2025). The fallback is shown in the startup banner, and email flags use the entrypoint's existing positional-parameter pattern. --- README.md | 27 ++++++++++++++----- src/Dockerfile | 13 +++++---- src/entrypoint.sh | 68 +++++------------------------------------------ 3 files changed, 33 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index b8a9d9f..89dfe42 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The following environment variables can be used to customize the Certbot contain | `CERTBOT_DOMAINS` | Comma-separated list of domains for which to obtain the certificate (example: `example.com,www.example.com`) | - | | `CERTBOT_CERT_NAME` | Explicit certificate name to update/modify ([See official docs →](https://eff-certbot.readthedocs.io/en/stable/using.html#changing-a-certificate-s-domains)) | - | | `CERTBOT_EXPAND` | **DEPRECATED**: Expand existing certificate to add domains (use CERTBOT_CERT_NAME instead, [see official docs →](https://eff-certbot.readthedocs.io/en/stable/using.html#re-creating-and-updating-existing-certificates)) | `false` | -| `CERTBOT_EMAIL` | **Optional.** Email address for Let's Encrypt notifications. If omitted, the container will register with Let's Encrypt using `--register-unsafely-without-email`. | - | +| `CERTBOT_EMAIL` | **Optional.** Email address for your Let's Encrypt account. If omitted, the container registers with `--register-unsafely-without-email`. This is safe for most setups since [Let's Encrypt no longer sends expiration notification emails](https://letsencrypt.org/2025/06/26/expiration-notification-service-has-ended/). | - | | `CERTBOT_KEY_TYPE` | Type of private key to generate | `ecdsa` | | `CERTBOT_SERVER` | The ACME server URL | `https://acme-v02.api.letsencrypt.org/directory` | | `CLOUDFLARE_API_TOKEN` | Cloudflare API token for DNS authentication (see below how to create one) | - | @@ -70,11 +70,26 @@ The following environment variables can be used to customize the Certbot contain | `PGID` | The group ID to run certbot as | `0` | | `RENEWAL_INTERVAL` | Interval between certificate renewal checks. Set to `0` to disable renewals and only run once. | 43200 seconds (12 hours) | | `REPLACE_SYMLINKS` | Replaces symlinks with direct copies of the files they reference (required for Windows) | `false` | -| `PROXY_TYPE` | Proxy type for routing Certbot traffic. Supported values: `none`, `socks5`, `socks4`, `http`, `https`. When `none`, no proxy is used. | `none` | -| `PROXY_HOST` | Proxy hostname or IP address, required when `PROXY_TYPE` is not `none`. | - | -| `PROXY_PORT` | Proxy port, required when `PROXY_TYPE` is not `none`. | - | -| `PROXY_USERNAME` | Optional username for authenticated proxies. | - | -| `PROXY_PASSWORD` | Optional password for authenticated proxies. | - | + +### Using a proxy + +Certbot is built on Python's [`requests` library, which natively supports the standard proxy environment variables](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (`HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`). Both the ACME (Let's Encrypt) traffic and the Cloudflare API calls will respect these variables — no extra configuration required: + +```yaml +environment: + # HTTP proxy + HTTPS_PROXY: "http://proxy.example.com:3128" +``` + +SOCKS proxies are also supported (the image ships with [PySocks](https://pypi.org/project/PySocks/)), including authentication: + +```yaml +environment: + # SOCKS5 proxy with authentication. + # Use the "socks5h" scheme to have the proxy resolve DNS (prevents DNS leaks); + # use "socks5" if you want DNS resolved locally instead. + HTTPS_PROXY: "socks5h://username:password@proxy.example.com:1080" +``` ### Creating a Cloudflare API Token diff --git a/src/Dockerfile b/src/Dockerfile index 7e6462c..45b1798 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -20,17 +20,16 @@ ENV CERTBOT_DOMAINS="" \ PUID=0 \ PGID=0 \ RENEWAL_INTERVAL=43200 \ - REPLACE_SYMLINKS=false \ - PROXY_TYPE="none" \ - PROXY_HOST="" \ - PROXY_PORT="" \ - PROXY_USERNAME="" \ - PROXY_PASSWORD="" + REPLACE_SYMLINKS=false COPY --chmod=700 entrypoint.sh /entrypoint.sh RUN apk update && \ - apk add --no-cache shadow su-exec proxychains-ng && \ + apk add --no-cache shadow su-exec && \ + # Enable SOCKS proxy support via the standard HTTP_PROXY/HTTPS_PROXY environment + # variables (see "Using a proxy" in the README): pysocks for certbot/ACME (requests), + # socksio for the Cloudflare API client (httpx) + pip install --no-cache-dir pysocks socksio && \ addgroup -g "${CERTBOT_GID}" "${CERTBOT_GROUP}" && \ adduser -u "${CERTBOT_UID}" -G "${CERTBOT_GROUP}" -D -H "${CERTBOT_USER}" && \ mkdir -p /var/log/letsencrypt && \ diff --git a/src/entrypoint.sh b/src/entrypoint.sh index c2cbd13..a49879d 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -88,54 +88,6 @@ is_default_privileges() { [ "${PUID:-$default_uid}" = "$default_uid" ] && [ "${PGID:-$default_gid}" = "$default_gid" ] } -configure_proxychains() { - # Build /etc/proxychains.conf from environment variables when a proxy is configured - proxy_type="${PROXY_TYPE:-none}" - - if [ "$proxy_type" = "none" ]; then - debug_print "Proxychains proxy not configured (PROXY_TYPE=none)." - return 0 - fi - - case "$proxy_type" in - socks5|socks4|http|https) - ;; - *) - echo "Error: Invalid PROXY_TYPE '$proxy_type'. Supported values: socks5, socks4, http, https, none" - exit 1 - ;; - esac - - if [ -z "$PROXY_HOST" ] || [ -z "$PROXY_PORT" ]; then - echo "Error: PROXY_HOST and PROXY_PORT must be set when PROXY_TYPE is not 'none'" - exit 1 - fi - - # Use the default path that proxychains4 inside this image reads by default - conf_dir="/etc/proxychains" - conf_path="${conf_dir}/proxychains.conf" - - mkdir -p "$conf_dir" - - debug_print "Generating $conf_path for proxy $proxy_type $PROXY_HOST:$PROXY_PORT" - - { - echo "strict_chain" - echo "proxy_dns" - echo - echo "[ProxyList]" - if [ -n "$PROXY_USERNAME" ] && [ -n "$PROXY_PASSWORD" ]; then - echo "${proxy_type} ${PROXY_HOST} ${PROXY_PORT} ${PROXY_USERNAME} ${PROXY_PASSWORD}" - else - echo "${proxy_type} ${PROXY_HOST} ${PROXY_PORT}" - fi - } > "$conf_path" - - # Ensure the certbot user can read the config - chmod 644 "$conf_path" - chown "${default_unprivileged_user}:${default_unprivileged_group}" "$conf_path" 2>/dev/null || true -} - run_certbot() { # Ensure the log directory is set to 700 chmod 700 /var/log/letsencrypt @@ -147,12 +99,6 @@ run_certbot() { certbot_cmd="su-exec ${default_unprivileged_user} certbot" fi - # Optionally wrap certbot with proxychains to route traffic via a proxy - if [ "${PROXY_TYPE:-none}" != "none" ]; then - debug_print "Wrapping certbot with proxychains4 based on proxy settings" - certbot_cmd="proxychains4 ${certbot_cmd}" - fi - debug_print "Running certbot with command: $certbot_cmd" # Add -v flag if DEBUG is enabled @@ -168,21 +114,21 @@ run_certbot() { set -- "$@" --expand fi - # Run certbot command - email_args="" + # Register with an email if provided, otherwise register without one + # (Let's Encrypt no longer sends expiration emails as of June 2025) if [ -n "$CERTBOT_EMAIL" ]; then - email_args="--email $CERTBOT_EMAIL" + set -- "$@" --email "$CERTBOT_EMAIL" else - email_args="--register-unsafely-without-email" + set -- "$@" --register-unsafely-without-email fi + # Run certbot command $certbot_cmd $debug_flag certonly \ --dns-cloudflare \ --dns-cloudflare-credentials "$CLOUDFLARE_CREDENTIALS_FILE" \ --dns-cloudflare-propagation-seconds "$CLOUDFLARE_PROPAGATION_SECONDS" \ -d "$CERTBOT_DOMAINS" \ --key-type "$CERTBOT_KEY_TYPE" \ - $email_args \ --server "$CERTBOT_SERVER" \ --agree-tos \ --non-interactive \ @@ -222,8 +168,6 @@ fi validate_environment_variables -configure_proxychains - if ! is_default_privileges; then configure_uid_and_gid fi @@ -245,7 +189,7 @@ EOF echo "🚀 Let's Get Encrypted! 🚀" echo "🌐 Domain(s): $CERTBOT_DOMAINS" -echo "📧 Email: $CERTBOT_EMAIL" +echo "📧 Email: ${CERTBOT_EMAIL:-(none — registering without an email address)}" echo "🌐 Certbot Server: $CERTBOT_SERVER" echo "🔑 Key Type: $CERTBOT_KEY_TYPE" echo "⏰ Renewal Interval: $RENEWAL_INTERVAL seconds"