Feat: Add ACME certificate automation (Let's Encrypt + full DNS-01 support via acme.sh) #52

Open
opened 2026-05-27 08:43:42 +01:00 by Gandalf · 0 comments
Owner

Problem

Sofarr currently uses a bundled self-signed snakeoil certificate for its native HTTPS support (added in commit da0898f). While this provides out-of-the-box HTTPS without an external reverse proxy, self-signed certificates are:

  • Insecure for any production or semi-public deployment (browser warnings, no trusted chain, potential MITM concerns)
  • Unreliable (manual management burden, short validity periods, painful renewal process)
  • Problematic for webhook callbacks, API consumers, and modern clients that expect valid TLS

Users who want proper trusted certificates are forced into workarounds (manual Let's Encrypt, custom cert mounting, or adding nginx/Caddy/Traefik), which undermines Sofarr’s “simple Docker deployment, no external web server required” design goal.

Proposed Solution

Add built-in ACME certificate automation with automatic issuance and renewal.

Key Requirements:

  • Full support for Let's Encrypt (and other ACME CAs)
  • Primary focus: DNS-01 challenge (works behind firewalls, supports wildcard certs, no need to expose port 80)
    • First-class support for Cloudflare
    • Support for all DNS providers available in acme.sh (100+ providers: Route53, Google Cloud DNS, Azure DNS, Hetzner, OVH, Namecheap, Porkbun, etc.)
  • Optional HTTP-01 challenge fallback
  • Automatic renewal (before expiry)
  • Zero-downtime certificate reload in the Node.js HTTPS server
  • 100% configuration via environment variables (consistent with current .env + Docker workflow)
  • Graceful fallback to snakeoil when disabled (for air-gapped/internal-only use)

Suggested Environment Variables

# ACME / Let's Encrypt
SOFARR_ACME_ENABLED=true
SOFARR_ACME_EMAIL=admin@yourdomain.com          # Required by Let's Encrypt
SOFARR_ACME_DOMAIN=sofarr.example.com           # Falls back to hostname from SOFARR_BASE_URL if unset
SOFARR_ACME_DNS_PROVIDER=cloudflare             # or dns_cf, route53, gcloud, etc.
SOFARR_ACME_STAGING=false                       # Set to true for testing

# Cloudflare example (most common)
SOFARR_CLOUDFLARE_API_TOKEN=your_cloudflare_token

# Other providers use their standard acme.sh variable names
# (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.)

Implementation Recommendation (to keep it lightweight)

  • Bundle acme.sh inside the Docker image (pure shell, no heavy dependencies)
  • Add a small startup/renewal script that:
    1. Checks certificate validity/expiry
    2. Runs acme.sh --issue --dns $PROVIDER ... when needed
    3. Places fullchain.pem + privkey.pem in the expected certs/ location
  • On renewal, reload the HTTPS server gracefully (Node.js httpsServer.setSecureContext() or file watcher)
  • Store ACME account data and certs in a persistent volume (or /app/certs)
  • Document everything in README + .env.sample
  • Add a simple status endpoint or log line showing “Certificate valid until YYYY-MM-DD”

Why acme.sh?
It exactly matches the request (“essentially all the methods available in acme.sh”) and is the lightest, most battle-tested option with the broadest DNS provider coverage.

Benefits

  • True “one-command” trusted HTTPS for public deployments
  • Automatic renewal forever — no more expired certificate surprises
  • Works perfectly with Cloudflare (and any other DNS provider)
  • Preserves Sofarr’s simple Docker-only deployment model
  • Significantly improves security posture and user experience (no more scary browser warnings)
### Problem Sofarr currently uses a bundled self-signed **snakeoil** certificate for its native HTTPS support (added in commit `da0898f`). While this provides out-of-the-box HTTPS without an external reverse proxy, self-signed certificates are: - **Insecure** for any production or semi-public deployment (browser warnings, no trusted chain, potential MITM concerns) - **Unreliable** (manual management burden, short validity periods, painful renewal process) - **Problematic** for webhook callbacks, API consumers, and modern clients that expect valid TLS Users who want proper trusted certificates are forced into workarounds (manual Let's Encrypt, custom cert mounting, or adding nginx/Caddy/Traefik), which undermines Sofarr’s “simple Docker deployment, no external web server required” design goal. ### Proposed Solution Add built-in **ACME certificate automation** with automatic issuance and renewal. **Key Requirements:** - Full support for **Let's Encrypt** (and other ACME CAs) - **Primary focus: DNS-01 challenge** (works behind firewalls, supports wildcard certs, no need to expose port 80) - First-class support for **Cloudflare** - Support for **all DNS providers available in acme.sh** (100+ providers: Route53, Google Cloud DNS, Azure DNS, Hetzner, OVH, Namecheap, Porkbun, etc.) - Optional HTTP-01 challenge fallback - Automatic renewal (before expiry) - Zero-downtime certificate reload in the Node.js HTTPS server - 100% configuration via environment variables (consistent with current `.env` + Docker workflow) - Graceful fallback to snakeoil when disabled (for air-gapped/internal-only use) **Suggested Environment Variables** ```env # ACME / Let's Encrypt SOFARR_ACME_ENABLED=true SOFARR_ACME_EMAIL=admin@yourdomain.com # Required by Let's Encrypt SOFARR_ACME_DOMAIN=sofarr.example.com # Falls back to hostname from SOFARR_BASE_URL if unset SOFARR_ACME_DNS_PROVIDER=cloudflare # or dns_cf, route53, gcloud, etc. SOFARR_ACME_STAGING=false # Set to true for testing # Cloudflare example (most common) SOFARR_CLOUDFLARE_API_TOKEN=your_cloudflare_token # Other providers use their standard acme.sh variable names # (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.) ``` **Implementation Recommendation (to keep it lightweight)** - Bundle **acme.sh** inside the Docker image (pure shell, no heavy dependencies) - Add a small startup/renewal script that: 1. Checks certificate validity/expiry 2. Runs `acme.sh --issue --dns $PROVIDER ...` when needed 3. Places `fullchain.pem` + `privkey.pem` in the expected `certs/` location - On renewal, reload the HTTPS server gracefully (Node.js `httpsServer.setSecureContext()` or file watcher) - Store ACME account data and certs in a persistent volume (or `/app/certs`) - Document everything in README + `.env.sample` - Add a simple status endpoint or log line showing “Certificate valid until YYYY-MM-DD” **Why acme.sh?** It exactly matches the request (“essentially all the methods available in acme.sh”) and is the lightest, most battle-tested option with the broadest DNS provider coverage. ### Benefits - True “one-command” trusted HTTPS for public deployments - Automatic renewal forever — no more expired certificate surprises - Works perfectly with Cloudflare (and any other DNS provider) - Preserves Sofarr’s simple Docker-only deployment model - Significantly improves security posture and user experience (no more scary browser warnings)
Gandalf added the Kind/Feature
Priority
Medium
3
labels 2026-05-27 08:43:42 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Gandalf/sofarr#52