{"id":13540102,"url":"https://github.com/dokku/dokku-letsencrypt","last_synced_at":"2026-10-19T16:44:22.397Z","repository":{"id":39006811,"uuid":"47838113","full_name":"dokku/dokku-letsencrypt","owner":"dokku","description":"Automatic Let's Encrypt TLS Certificate installation for dokku","archived":false,"fork":false,"pushed_at":"2026-09-18T11:52:57.000Z","size":431,"stargazers_count":1118,"open_issues_count":2,"forks_count":96,"subscribers_count":11,"default_branch":"master","last_synced_at":"2026-09-27T03:29:39.219Z","etag":null,"topics":["dokku","dokku-letsencrypt","letsencrypt","nginx"],"latest_commit_sha":null,"homepage":"https://blog.semicolonsoftware.de/securing-dokku-with-lets-encrypt-tls-certificates/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dokku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"github":"dokku","open_collective":"dokku","patreon":"dokku"}},"created_at":"2015-12-11T16:37:07.000Z","updated_at":"2026-09-01T20:44:12.000Z","dependencies_parsed_at":"2026-09-27T00:24:26.933Z","dependency_job_id":null,"html_url":"https://github.com/dokku/dokku-letsencrypt","commit_stats":{"total_commits":275,"total_committers":40,"mean_commits":6.875,"dds":0.6654545454545455,"last_synced_commit":"1ddac1c83dc57a19d448bdf59e97086029da28ab"},"previous_names":[],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/dokku/dokku-letsencrypt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokku%2Fdokku-letsencrypt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokku%2Fdokku-letsencrypt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokku%2Fdokku-letsencrypt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokku%2Fdokku-letsencrypt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dokku","download_url":"https://codeload.github.com/dokku/dokku-letsencrypt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokku%2Fdokku-letsencrypt/sbom","scorecard":{"id":112461,"data":{"date":"2025-08-11","repo":{"name":"github.com/dokku/dokku-letsencrypt","commit":"658fef4930aa04d0667dbdafbb9464b5f8cef564"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":2,"reason":"Found 4/14 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/bump-version.yaml:1","Warn: no topLevel permission defined: .github/workflows/tagged-release.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/bump-version.yaml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/dokku/dokku-letsencrypt/bump-version.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/bump-version.yaml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/dokku/dokku-letsencrypt/bump-version.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tagged-release.yaml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/dokku/dokku-letsencrypt/tagged-release.yaml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating goacme/lego:v4.22.2 to goacme/lego:v4.22.2@sha256:b45109c67b3295bbd6b8fe7b1b6884c33b5189ff5f74509a72f7c8a113195300","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/dokku/.github/SECURITY.md:1","Info: Found linked content: github.com/dokku/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/dokku/.github/SECURITY.md:1","Info: Found text in security policy: github.com/dokku/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-15T12:37:32.963Z","repository_id":39006811,"created_at":"2025-08-15T12:37:32.963Z","updated_at":"2025-08-15T12:37:32.963Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38362277,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-19T02:00:06.414Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dokku","dokku-letsencrypt","letsencrypt","nginx"],"created_at":"2024-08-01T09:01:40.429Z","updated_at":"2026-10-19T16:44:22.390Z","avatar_url":"https://github.com/dokku.png","language":"Shell","funding_links":["https://github.com/sponsors/dokku","https://opencollective.com/dokku","https://patreon.com/dokku"],"categories":["Shell","others","\u003ca id=\"86d5daccb4ed597e85a0ec9c87f3c66f\"\u003e\u003c/a\u003eTLS\u0026\u0026SSL\u0026\u0026HTTPS"],"sub_categories":["\u003ca id=\"776c034543a65be69c061d1aafce3127\"\u003e\u003c/a\u003e新添加的"],"readme":"# dokku-letsencrypt\n\nOfficial dokku plugin that retrieves and installs free TLS certificates from [Let's Encrypt](https://letsencrypt.org). The app stays online throughout ACME validation, and certificates are renewed automatically once a cron job is enabled.\n\n\u003e Running this plugin counts as accepting the Let's Encrypt Subscriber Agreement on your behalf. The plugin passes `--accept-tos` to `lego` so that unattended cron-based renewal does not block on a prompt.\n\n## Installation\n\n```shell\nsudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git\nsudo dokku letsencrypt:cron-job --add\n```\n\nThe first command installs the plugin and pulls the `dokku/letsencrypt` Docker image used to run the [lego](https://github.com/go-acme/lego) ACME client. The second command schedules daily auto-renewals, so existing certificates renew themselves before they expire.\n\nTo upgrade later, run `sudo dokku plugin:update letsencrypt`. The update step also migrates legacy `DOKKU_LETSENCRYPT_*` environment variables (`_EMAIL`, `_GRACEPERIOD`, `_ARGS`, `_SERVER`) and the older `lego-docker-args` property to their current property names, so no manual reconfiguration is needed.\n\nYou can influence what the install and update commands do via the following host-level environment variables:\n\n| Variable | Default | Description |\n|---|---|---|\n| `LETSENCRYPT_IMAGE` | `dokku/letsencrypt` | Docker image used for the lego container. Override this if you maintain a fork or a mirror. |\n| `LETSENCRYPT_IMAGE_VERSION` | image tag from this repo's `Dockerfile` | Tag of the lego image to pull and run. |\n| `LETSENCRYPT_DISABLE_PULL` | (unset) | Set to `true` to skip `docker pull` during install and update. Useful on air-gapped hosts where the image is already loaded. |\n\n## Usage\n\nHelp for any command can be displayed by running `dokku letsencrypt:help`. Plugin help output together with this README is used to generate the public documentation; the per-command subsections below cover each command in depth.\n\n### Commands\n\n```\nletsencrypt:active \u003capp\u003e                                       Verify if letsencrypt is active for an app\nletsencrypt:auto-renew [\u003capp\u003e]                                 Auto-renew app if renewal is necessary\nletsencrypt:cleanup \u003capp\u003e                                      Remove stale certificate directories for app\nletsencrypt:cron-job [--add|--remove]                          Add, remove, or display the status of the auto-renewal cron job.\nletsencrypt:disable \u003capp\u003e                                      Disable letsencrypt for an app\nletsencrypt:enable \u003capp\u003e [--force]                             Enable or renew letsencrypt for an app (skipped when a valid certificate already exists unless --force is set)\nletsencrypt:help                                               Display letsencrypt help\nletsencrypt:list                                               List letsencrypt-secured apps with certificate expiry times\nletsencrypt:report [\u003capp\u003e|--global] [\u003cflag\u003e] [--format json]   Display a letsencrypt report for one or more apps\nletsencrypt:revoke \u003capp\u003e                                       Revoke letsencrypt certificate for app\nletsencrypt:set \u003capp\u003e \u003cproperty\u003e (\u003cvalue\u003e)                     Set or clear a letsencrypt property for an app\n```\n\n### Basic usage\n\nThe app needs to already be deployed and reachable on the public internet over HTTP before a certificate can be issued. Let's Encrypt fetches a challenge file from your app's domain to prove you control it, so an app that has only been created (no successful deploy yet) cannot be enabled.\n\nSet an email address, then enable the plugin for the app:\n\n```shell\ndokku letsencrypt:set myapp email your@email.tld\ndokku letsencrypt:enable myapp\n```\n\nA successful run looks like this:\n\n```\n=====\u003e Let's Encrypt myapp...\n-----\u003e Enabling letsencrypt proxy for myapp...\n-----\u003e Getting letsencrypt certificate for myapp via HTTP-01\n        - Domain 'myapp.mydomain.com'\n\n[ removed various log messages for brevity ]\n\n-----\u003e Certificate retrieved successfully.\n-----\u003e Installing let's encrypt certificates\n-----\u003e Configuring SSL for myapp.mydomain.com...(using /var/lib/dokku/plugins/available/nginx-vhosts/templates/nginx.ssl.conf.template)\n-----\u003e Disabling letsencrypt proxy for myapp...\n       Done\n```\n\nOnce the certificate is installed, the regular `dokku certs:*` commands can read, replace, or remove it. Setting an email once at the `--global` scope spares you from repeating it for every app:\n\n```shell\ndokku letsencrypt:set --global email your@email.tld\n```\n\nWhen you add or change an app's domains afterwards, the plugin logs a warning reminding you to run `letsencrypt:enable` again so the certificate covers the new SAN list.\n\n### Verify if letsencrypt is active for an app\n\n```shell\n# usage\ndokku letsencrypt:active \u003capp\u003e\n```\n\nPrints `true` if the certificate currently installed on the app was issued by this plugin, `false` otherwise. Useful in scripts that need to decide whether to call `letsencrypt:enable`.\n\n```shell\ndokku letsencrypt:active myapp\n```\n\n### Enable letsencrypt for an app\n\n```shell\n# usage\ndokku letsencrypt:enable \u003capp\u003e [--force]\ndokku letsencrypt:enable --all [--force]\n```\n\nflags:\n\n- `--force` / `-f`: request a new certificate even if the existing one is still valid. See [Idempotent enable](#idempotent-enable) for what triggers a real ACME call by default.\n- `--all`: run the enable flow against every app on the host. Useful after a bulk configuration change such as updating the global `email` or `server`.\n\nIssues a certificate via the configured challenge type (HTTP-01 by default, DNS-01 when `dns-provider` is set), installs it on the app, and reloads nginx. The command is safe to call on every deploy; it only contacts the ACME server when something has actually changed.\n\nEnable a single app:\n\n```shell\ndokku letsencrypt:enable myapp\n```\n\nForce a fresh certificate request for a single app:\n\n```shell\ndokku letsencrypt:enable myapp --force\n```\n\nEnable every app on the host:\n\n```shell\ndokku letsencrypt:enable --all\n```\n\n### Auto-renew certificates if necessary\n\n```shell\n# usage\ndokku letsencrypt:auto-renew [\u003capp\u003e]\n```\n\nRenews any certificate whose remaining lifetime has dropped below the configured `graceperiod`. With no app argument the plugin scans every app on the host, sorted by ascending time-to-renewal so the most urgent renewals run first (this matters when an ACME rate limit is in play). With an app argument it only renews that one app.\n\nThe auto-renewal cron job invokes this command, but you can also run it manually:\n\n```shell\ndokku letsencrypt:auto-renew\ndokku letsencrypt:auto-renew myapp\n```\n\n### Manage the auto-renewal cron job\n\n```shell\n# usage\ndokku letsencrypt:cron-job [--add|--remove]\n```\n\nflags:\n\n- `--add`: install the auto-renew cron entry.\n- `--remove`: remove the auto-renew cron entry.\n- no flag: print whether the cron job is currently installed.\n\nRenewal can only happen if something runs `letsencrypt:auto-renew` on a schedule. This command writes (or removes) that cron entry on your behalf. When the dokku `cron` plugin is installed, the entry is `24 6 * * *` (daily at 06:24) and its output is appended to `/var/log/dokku/letsencrypt.log`. Otherwise the plugin falls back to writing `@daily` directly to the dokku user's crontab.\n\n```shell\ndokku letsencrypt:cron-job --add\ndokku letsencrypt:cron-job\ndokku letsencrypt:cron-job --remove\n```\n\n### Disable letsencrypt for an app\n\n```shell\n# usage\ndokku letsencrypt:disable \u003capp\u003e\n```\n\nRemoves the certificate and the plugin's per-app state from `$DOKKU_ROOT/\u003capp\u003e/letsencrypt` and `$DOKKU_ROOT/\u003capp\u003e/tls`, then triggers nginx to drop the HTTPS configuration. The app stays running on plain HTTP. Run this when you want to take an app off Let's Encrypt without revoking the certificate at the ACME server.\n\n```shell\ndokku letsencrypt:disable myapp\n```\n\n### Remove stale certificate directories\n\n```shell\n# usage\ndokku letsencrypt:cleanup \u003capp\u003e\n```\n\nEvery certificate request lands in a hash-keyed directory under `$DOKKU_ROOT/\u003capp\u003e/letsencrypt/certs/`, and `current` symlinks to the active one. Old hash directories accumulate when the lego config changes (new domain, different `lego-args`, etc.). `letsencrypt:cleanup` removes everything except the currently-active hash directory.\n\n```shell\ndokku letsencrypt:cleanup myapp\n```\n\n### List letsencrypt-secured apps\n\n```shell\n# usage\ndokku letsencrypt:list\n```\n\nPrints a table of every app with a Let's Encrypt certificate installed: expiry timestamp, time remaining on the certificate, and time remaining before auto-renewal kicks in. The list is sorted by expiry date so the most urgent entries appear first.\n\n```shell\ndokku letsencrypt:list\n```\n\n### Display a letsencrypt report\n\n```shell\n# usage\ndokku letsencrypt:report [\u003capp\u003e|--global] [\u003cflag\u003e] [--format json]\n```\n\nflags:\n\n- `--global`: report only global properties.\n- `--format json`: emit a JSON object instead of the default stdout layout. Cannot be combined with an info flag.\n- `--letsencrypt-\u003cproperty\u003e` (such as `--letsencrypt-email` or `--letsencrypt-computed-server`): print the value for that single property.\n\nExposes the plugin's configuration and state for tooling and diagnostics. With no arguments it prints a report for every app; with an app name it scopes to that one app; with `--global` it shows only the global values. See [Reports](#reports) for the redaction behavior in the top-level `dokku report` command.\n\n```shell\ndokku letsencrypt:report\ndokku letsencrypt:report myapp\ndokku letsencrypt:report --global\ndokku letsencrypt:report myapp --format json\ndokku letsencrypt:report myapp --letsencrypt-email\n```\n\n### Revoke letsencrypt certificate for app\n\n```shell\n# usage\ndokku letsencrypt:revoke \u003capp\u003e\n```\n\nTells the ACME server to revoke the certificate currently installed on the app. The local files remain in place so the next `letsencrypt:enable` can reuse them or replace them. Use this when a private key has leaked or you no longer want the certificate to be considered valid by browsers.\n\n```shell\ndokku letsencrypt:revoke myapp\n```\n\n### Set or clear a letsencrypt property\n\n```shell\n# usage\ndokku letsencrypt:set \u003capp\u003e|--global \u003cproperty\u003e [\u003cvalue\u003e]\n```\n\nSets the property to the given value, or clears it when no value is provided. The first argument can be an app name (sets the property for that app) or `--global` (sets the default for every app). When the plugin needs a property's value it first checks the app, then falls back to the global setting.\n\nValid properties are `dns-provider`, `email`, `graceperiod`, `server`, `lego-args`, `lego-docker-options`, and any `dns-provider-*` key (used for DNS-01 credentials, see [DNS-01 challenge](#dns-01-challenge)).\n\nSet a per-app value:\n\n```shell\ndokku letsencrypt:set myapp email myapp-admin@example.com\n```\n\nSet a global default:\n\n```shell\ndokku letsencrypt:set --global email admin@example.com\n```\n\nClear an app-level value (falls back to the global value, if any):\n\n```shell\ndokku letsencrypt:set myapp email\n```\n\n## Configuration\n\nAll non-secret configuration is stored via `letsencrypt:set`. App-level values take precedence over global values, so it is common to set safe defaults at `--global` and only override per app when needed.\n\n| Property | Default | Description |\n|---|---|---|\n| `dns-provider` | (none) | Name of a [valid lego DNS provider](https://go-acme.github.io/lego/dns/). Setting this switches the app from HTTP-01 to DNS-01, which is what enables wildcard certificates. |\n| `email` | (none) | **Required.** Address used to register the ACME account. Let's Encrypt sends expiry reminders here. |\n| `graceperiod` | `2592000` (30 days) | Seconds remaining on a certificate before auto-renew tries to replace it. The Let's Encrypt certificate lifetime is 90 days, so 30 days of grace is the common default. |\n| `lego-args` | (none) | Extra arguments appended to the `lego` CLI invocation. See the [lego CLI docs](https://go-acme.github.io/lego/usage/cli/). |\n| `lego-docker-options` | (none) | Extra flags appended to `docker run` when starting the lego container. Use this for volume mounts, env files, or custom networks. This is distinct from `lego-args`, which goes to the `lego` binary itself. |\n| `server` | `default` | ACME directory URL. `default` resolves to `https://acme-v02.api.letsencrypt.org/directory`. `staging` resolves to `https://acme-staging-v02.api.letsencrypt.org/directory`. Any other value is used as a literal URL. |\n\nSet a single property:\n\n```shell\ndokku letsencrypt:set myapp email myapp-admin@example.com\ndokku letsencrypt:set --global graceperiod 1209600\n```\n\n`dns-provider-*` properties (DNS-01 credentials) live in the same property store and follow the same precedence rule. They are covered in [DNS-01 challenge](#dns-01-challenge).\n\n## Automatic renewal\n\nLet's Encrypt certificates expire after 90 days, so any production app needs unattended renewal. The plugin's renewal mechanism is a cron job that runs `letsencrypt:auto-renew`, which iterates over every Let's Encrypt-secured app and re-issues certificates whose remaining lifetime has dropped below `graceperiod` (default: 30 days).\n\nInstall the cron job once during initial setup:\n\n```shell\ndokku letsencrypt:cron-job --add\n```\n\nWhen the dokku `cron` plugin is installed, the entry is `24 6 * * *` (daily at 06:24) and its output is appended to `/var/log/dokku/letsencrypt.log`. Otherwise the plugin falls back to writing `@daily` to the dokku user's crontab. Running `letsencrypt:cron-job` without a flag reports whether the entry is installed.\n\n## Idempotent enable\n\n`letsencrypt:enable \u003capp\u003e` is safe to call on every deploy. It only contacts the ACME server when one of the following is true:\n\n- the app does not currently have a Let's Encrypt certificate installed,\n- the app's domains, email, server, `lego-args`, `lego-docker-options`, or `dns-provider` have changed since the certificate was issued,\n- the certificate is within its renewal grace period (see `graceperiod`).\n\nIn every other case the command exits successfully without touching nginx, the lego container, or the ACME server. This is what makes the command safe to wire into CI-driven review-app deploys without burning through Let's Encrypt's rate limits.\n\nTo force a new certificate request even when the existing certificate is still valid, pass `--force`:\n\n```shell\ndokku letsencrypt:enable myapp --force\n```\n\nThis is useful when copying certificates between servers (the new host has no ACME-account record of the existing cert) or when manually rotating a certificate.\n\n## Reports\n\n`letsencrypt:report` exposes per-app and global plugin properties for tooling and diagnostics. Without arguments it prints a human-readable report for every app, with an app name it reports on that app, and with `--global` it limits output to global properties. Because global properties are app-independent, `--global` works on a host that has no apps deployed yet, and an argument-less invocation on such a host warns instead of failing.\n\n```shell\ndokku letsencrypt:report\ndokku letsencrypt:report myapp\ndokku letsencrypt:report --global\n```\n\nPass `--format json` to emit a JSON object instead of the default stdout layout. The flag works with app, global, and \"all apps\" invocations:\n\n```shell\ndokku letsencrypt:report myapp --format json\ndokku letsencrypt:report --global --format json\n```\n\nSpecifying a single property flag (such as `--letsencrypt-email`) prints just that value. Combining `--format json` with a single property flag is rejected.\n\n```shell\ndokku letsencrypt:report myapp --letsencrypt-email\n```\n\nAny `dns-provider-*` properties set globally or for the app appear in the report alongside the fixed fields. For each set property, the report emits the scopes that actually have a value: a `--letsencrypt-dns-provider-\u003cKEY\u003e` row when set on the app, a `--letsencrypt-global-dns-provider-\u003cKEY\u003e` row when set globally, and a `--letsencrypt-computed-dns-provider-\u003cKEY\u003e` row that resolves to the app value (falling back to the global value).\n\nThe top-level `dokku report \u003capp\u003e` command aggregates output from every plugin and is commonly used in support contexts. When the letsencrypt section is rendered through that aggregate command, every `dns-provider-*` credential value is redacted to `****` to avoid leaking API keys or token paths into shared output. The provider name and every other property remain unredacted. `dokku letsencrypt:report` is unaffected and continues to show raw values, so operators can verify what they have configured.\n\n## Challenge types\n\nACME (the Let's Encrypt protocol) supports two challenge types, and this plugin can use either of them:\n\n- **HTTP-01** is the default. Let's Encrypt fetches `http://\u003cyour-domain\u003e/.well-known/acme-challenge/\u003ctoken\u003e` and expects to see a specific response. This requires your app to be reachable on port 80 from the public internet, but needs no special DNS access.\n- **DNS-01** proves control of a domain by writing a TXT record at `_acme-challenge.\u003cyour-domain\u003e`. The plugin invokes a [lego DNS provider](https://go-acme.github.io/lego/dns/) (Cloudflare, Route53, Namecheap, etc.) to create and remove that record. DNS-01 is the only way to obtain wildcard certificates (`*.example.com`) and is useful when port 80 is firewalled.\n\nPick HTTP-01 unless you need wildcard certificates or your app cannot accept inbound connections on port 80. To use DNS-01, see [DNS-01 challenge](#dns-01-challenge).\n\n## DNS-01 challenge\n\n\u003e DNS-01 support is sponsored by [Orca Scan Ltd](https://orcascan.com/).\n\nTo enable DNS-01, set `dns-provider` to a supported lego provider, then set the per-provider environment variables that lego reads. Provider-specific env vars are stored as letsencrypt properties prefixed with `dns-provider-`, scoped either globally or per app. Before you run `letsencrypt:enable`, make sure every domain you plan to cover (including any wildcard records) actually resolves to your server.\n\n```shell\ndokku letsencrypt:set --global dns-provider namecheap\ndokku letsencrypt:set --global dns-provider-NAMECHEAP_API_USER user\ndokku letsencrypt:set --global dns-provider-NAMECHEAP_API_KEY key\n```\n\nIf a DNS provider supports `_FILE`-suffixed environment variables for reading secrets from files, mount the secret file into the lego container with `lego-docker-options` and set the corresponding `dns-provider-*_FILE` property to the in-container path:\n\n```shell\ndokku letsencrypt:set --global lego-docker-options \"-v /etc/dokku-letsencrypt/cloudflare-token:/secrets/cf-token:ro\"\ndokku letsencrypt:set --global dns-provider-CLOUDFLARE_DNS_API_TOKEN_FILE /secrets/cf-token\n```\n\n### Using the exec DNS provider\n\nThe lego [`exec` DNS provider](https://go-acme.github.io/lego/dns/exec/) shells out to a user-supplied script for creating and removing TXT records. The script must be reachable from inside the lego container at the path stored in `EXEC_PATH`. Use `lego-docker-options` to mount it from the host:\n\n```shell\nsudo install -m 0755 /path/on/host/dns.sh /var/lib/dokku/data/letsencrypt/--global/exec-dns.sh\n\ndokku letsencrypt:set --global dns-provider exec\ndokku letsencrypt:set --global lego-docker-options \"-v /var/lib/dokku/data/letsencrypt/--global/exec-dns.sh:/scripts/dns.sh:ro\"\ndokku letsencrypt:set --global dns-provider-EXEC_PATH /scripts/dns.sh\n```\n\nConsult the lego documentation for your specific DNS provider for the full set of credentials it needs.\n\n### Disabling DNS-01 for a single app\n\nWhen a global `dns-provider` is set but a particular app's domain is not managed by that provider, set the app's `dns-provider` to `none` to force HTTP-01 for that app only:\n\n```shell\ndokku letsencrypt:set myapp dns-provider none\n```\n\nSetting the property to an empty string (`dokku letsencrypt:set myapp dns-provider \"\"`) deletes the app-level value and falls back to the global setting; `none` is the explicit opt-out. The `none` sentinel is also accepted at `--global` scope, where it behaves identically to leaving the property unset.\n\n## Generating a cert for multiple domains\n\nYour [default dokku app](https://dokku.com/docs/networking/proxies/nginx/?h=default+site#default-site) is also served at the root domain. If your app `00-default` runs at `00-default.mydomain.com`, it is reachable at `mydomain.com` too. Once you enable letsencrypt for the app, the bare-root URL stops working unless that domain is on the certificate. Add it to the app's domains first:\n\n```shell\ndokku domains:add 00-default mydomain.com\ndokku letsencrypt:enable 00-default\n```\n\nThe same pattern applies whenever you add or change domains on a Let's Encrypt-secured app: domain changes invalidate the certificate's SAN list, so re-run `letsencrypt:enable`. The plugin emits a warning prompting you to do this whenever `dokku domains:add` or `dokku domains:set` touches the app.\n\n## Default-vhost (`_`) domain\n\nDokku 0.30.4 added support for the literal `_` domain as an nginx default catch-all vhost. Let's Encrypt rejects `_` as an invalid identifier, so the plugin silently drops it from the certificate's SAN list and requests a certificate for the remaining domains. An app whose only domain is `_` cannot be enabled and falls into the existing \"no domains detected\" error path.\n\n## Dockerfile and image-based deploys\n\nWhen securing Dockerfile or image-based deploys, the plugin needs port 80 reachable for the HTTP-01 challenge and port 443 mapped to your app's container port for HTTPS traffic. For Dockerfile deploys, dokku proxies whichever ports the image declares with `EXPOSE` on the same port numbers on the host. Dokku also adds a matching `https:443:\u003ccontainer-port\u003e` mapping for every `http:80:\u003ccontainer-port\u003e` whenever a certificate is installed, so the standard case requires no manual `dokku ports:*` calls.\n\nTo keep the HTTP-01 challenge working, `letsencrypt:enable` looks for an `http:80:*` entry in the app's proxy port map. If none exists, the plugin injects one using the container port from the first existing `http:*:*` (or `https:*:*`) mapping, including detected mappings from `EXPOSE`. The new mapping is persisted so subsequent renewals do not need to re-add it. Once the certificate is installed, dokku's `post-certs-update` trigger fills in the matching `https:443` mapping, completing the proxy setup. If the app has no `http` or `https` mapping at all, `letsencrypt:enable` exits with an error pointing at `dokku ports:add` rather than issuing a request that the ACME server would reject. DNS-01 deployments skip the port-80 check entirely because they do not use port 80 for validation.\n\nA full workflow for a Dockerfile or image-based app listening on port 5555:\n\n1. Create the app and push it to dokku.\n2. On the dokku host, run `dokku letsencrypt:enable myapp` to retrieve and install the certificate.\n\nAfter this, `dokku ports:report myapp` should show:\n\n```\n=====\u003e myapp ports information\n       Ports map:                     http:80:5555 https:443:5555\n       Ports map detected:            https:5555:5555\n```\n\nReplace `5555` with whatever container port your app actually listens on.\n\n## HTTP to HTTPS redirect\n\nDokku's default nginx template automatically redirects HTTP requests to HTTPS once a certificate is installed. To change this behavior, [customize the nginx template](https://dokku.com/docs/networking/proxies/nginx/).\n\n## Rate limits\n\nLet's Encrypt enforces [public rate limits](https://letsencrypt.org/docs/rate-limits/). The most relevant one for dokku is the per-week cap on certificates per registered domain: because dokku gives every new app a default subdomain like `\u003capp\u003e.\u003cdokku-domain\u003e`, running this plugin on many apps that all share `\u003cdokku-domain\u003e` will hit that cap quickly.\n\nThe workaround is to set a real domain per app and remove the default subdomain before enabling. For example, if your dokku domain is `dokku.example.com` and you want to secure your `foo` app at `foo.com`:\n\n```shell\ndokku domains:add foo foo.com\ndokku domains:remove foo foo.dokku.example.com\ndokku letsencrypt:enable foo\n```\n\nWhile iterating on configuration, point the plugin at Let's Encrypt's staging environment, which has much higher rate limits and issues untrusted certificates:\n\n```shell\ndokku letsencrypt:set myapp server staging\ndokku letsencrypt:enable myapp\n```\n\nOnce you are ready for a production certificate, clear the override and re-enable:\n\n```shell\ndokku letsencrypt:set myapp server\ndokku letsencrypt:enable myapp --force\n```\n\nWhen the ACME server returns a rate-limit response, `letsencrypt:enable` exits non-zero and prints a dedicated warning pointing at the Let's Encrypt rate-limits documentation and suggesting the staging server. The full lego output is still printed above the warning, so the specific limit that was hit (\"too many certificates already issued\", \"too many failed authorizations\", etc.) remains visible.\n\n## Shared ACME account\n\nLet's Encrypt also limits new accounts per IP to 10 per 3 hours, which would be easy to trip on a busy dokku host. To stay clear of this, the plugin stores a single ACME account in `${DOKKU_LIB_ROOT}/data/letsencrypt/--global/accounts` and mounts it into every lego invocation. Apps sharing the same `email` and `server` reuse one account regardless of how many apps are enabled, matching Let's Encrypt's [recommendation](https://letsencrypt.org/docs/integration-guide/#one-account-or-many) for hosting providers. Apps configured with a distinct `email` or `server` get their own entry under the shared directory, keyed by `(server, email)`.\n\nWhen upgrading from a previous version of this plugin, the first `letsencrypt:enable` after the upgrade registers exactly one new account in the shared directory. Existing per-app account material under `$DOKKU_ROOT/\u003capp\u003e/letsencrypt/certs/\u003chash\u003e/accounts/` is left in place so that `letsencrypt:revoke` for certificates issued before the upgrade can still find the original account.\n\n## App cloning and renaming\n\nThe plugin clears the destination app's `letsencrypt` directory whenever an app is cloned or renamed. Certificates are bound to specific domains and specific config hashes, so reusing the source app's directory would leave the new app pointing at the wrong cert. After cloning or renaming, run `letsencrypt:enable` on the new app to issue a fresh certificate.\n\n```shell\ndokku apps:clone myapp myapp-staging\ndokku letsencrypt:enable myapp-staging\n```\n\n## Cloudflare\n\nIf your domain is proxied through Cloudflare, set Cloudflare's DNS records to \"Proxied\" mode and configure SSL/TLS to \"Full\" mode before enabling this plugin. Cloudflare's \"Flexible\" mode terminates HTTPS at Cloudflare's edge and connects to your origin over plain HTTP, which means Let's Encrypt's renewal request is served back as HTTP rather than reaching your origin's HTTPS listener. Symptoms range from validation failures to Cloudflare reporting the origin as down.\n\nIf you are committed to \"Flexible\" SSL/TLS mode, do not use this plugin. For background, see:\n\n- \u003chttps://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/\u003e\n- \u003chttps://community.cloudflare.com/t/lets-encrypt-ssl-cannot-renew-with-cloudflare/257666\u003e\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdokku%2Fdokku-letsencrypt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdokku%2Fdokku-letsencrypt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdokku%2Fdokku-letsencrypt/lists"}