Self-hosted service
dokku/dokku-letsencrypt avatar
dokku/dokku-letsencrypt

dokku-letsencrypt: automatic TLS certificates for Dokku apps

Automatic Let's Encrypt TLS Certificate installation for dokku

1,118 stars96 forksShellMIT

At a glance

What is it?
dokku-letsencrypt is the official Dokku plugin that fetches free certificates from Let's Encrypt through the lego ACME client and renews them from a cron job. It uses HTTP-01 by default, switches to DNS-01 on request, and its command set covers enable, revoke, report and cleanup.
Who is it for?
dokku-letsencrypt suits a Dokku host that already serves apps over public HTTP and wants certificates issued and renewed without a separate certbot setup on the machine, and the operator accepts the Subscriber Agreement on the host's behalf. It does not fit an app that is not yet deployed or not reachable from the internet, since the HTTP-01 challenge needs a live domain, and it is not a general ACME client for other servers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Dokku plugin that keeps nginx in front of the ACME client

dokku-letsencrypt is the official plugin for Dokku that retrieves and installs free TLS certificates from Let's Encrypt. The primary language of the repository is Shell, and the top level tree is the shape of a Dokku plugin rather than an application: `commands`, `command-functions`, `subcommands/`, `help-functions`, `internal-functions`, `config`, `cron-entries`, `cron-job`, `templates/`, plus the lifecycle hooks `post-app-clone-setup`, `post-app-rename-setup`, `post-delete` and `post-domains-update`, and `install`, `update` and `uninstall`.

The actual certificate client is not written here. The Dockerfile is a single line over an existing image:

dockerfile
FROM goacme/lego:v4.35.2

So the plugin's job is orchestration: it drives lego inside a container, wires the resulting certificate into the app's nginx virtual host, and schedules the renewal. The repository also carries a `plugin.toml` and a `report` file, matching Dokku's plugin conventions.

One decision belongs to the operator, not the tool. The README states that running the plugin counts as accepting the Let's Encrypt Subscriber Agreement on your behalf, because the plugin passes `--accept-tos` to lego so that unattended cron-based renewal does not block on an interactive prompt. That is a deliberate trade: automation in exchange for a term you accept by installing it.

Installing the plugin and scheduling the renewal

Two commands set up everything, and both are run on the Dokku host:

shell
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
sudo dokku letsencrypt:cron-job --add

The first installs the plugin and pulls the `dokku/letsencrypt` Docker image used to run the lego ACME client. The second schedules daily auto-renewals, so certificates renew before they expire.

Upgrading later is a single command:

shell
sudo dokku plugin:update letsencrypt

The update step also migrates the 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 after an upgrade from an older release.

Three host-level environment variables let you change how the install and update behave. `LETSENCRYPT_IMAGE` sets the Docker image used for the lego container and defaults to `dokku/letsencrypt`, which is what you would override if you maintain a fork or a mirror. `LETSENCRYPT_IMAGE_VERSION` pins the tag of the lego image to pull and run, defaulting to the image tag from the repository's Dockerfile. `LETSENCRYPT_DISABLE_PULL`, when set to `true`, skips `docker pull` during install and update, which is useful on air-gapped hosts where the image is already loaded.

The command set and what each one does

Every command is namespaced under `letsencrypt:`, and the README lists them with one-line descriptions:

code
letsencrypt:active <app>                                       Verify if letsencrypt is active for an app
letsencrypt:auto-renew [<app>]                                 Auto-renew app if renewal is necessary
letsencrypt:cleanup <app>                                      Remove stale certificate directories for app
letsencrypt:cron-job [--add|--remove]                          Add, remove, or display the status of the auto-renewal cron job.
letsencrypt:disable <app>                                      Disable letsencrypt for an app
letsencrypt:enable <app> [--force]                             Enable or renew letsencrypt for an app (skipped when a valid certificate already exists unless --force is set)
letsencrypt:help                                               Display letsencrypt help
letsencrypt:list                                               List letsencrypt-secured apps with certificate expiry times
letsencrypt:report [<app>|--global] [<flag>] [--format json]   Display a letsencrypt report for one or more apps
letsencrypt:revoke <app>                                       Revoke letsencrypt certificate for app
letsencrypt:set <app> <property> (<value>)                     Set or clear a letsencrypt property for an app

The set covers more than issuing. `letsencrypt:list` shows secured apps with their certificate expiry times, which is the command to reach for in a routine check. `letsencrypt:report` produces a report for one or more apps or globally, and it can emit JSON, so the output can be consumed by a script rather than read by a human. `letsencrypt:active` prints `true` when the certificate currently installed on an app was issued by this plugin and `false` otherwise, which is the safe check before deciding to call `enable`. `letsencrypt:cleanup` removes stale certificate directories for an app, and `letsencrypt:revoke` revokes a certificate outright.

`letsencrypt:help` displays the plugin help, and the README notes that the plugin help output together with the README is used to generate the public documentation.

Enabling a certificate and the HTTP-01 constraint

There is a precondition that catches people out. The app needs to be already deployed and reachable on the public internet over HTTP before a certificate can be issued, because Let's Encrypt fetches a challenge file from your app's domain to prove you control it. An app that has only been created, with no successful deploy, cannot be enabled.

The sequence is short. Set an email address, then enable the plugin for the app:

shell
dokku letsencrypt:set myapp email [email protected]
dokku letsencrypt:enable myapp

A successful run prints the domain it obtained the certificate for, the nginx template it used to configure SSL, and confirmation that the certificate was retrieved and installed.

The `enable` command is safe to call on every deploy. It only contacts the ACME server when something has actually changed, and it is skipped when a valid certificate already exists unless `--force` is passed. Two flags extend it: `--force` requests a new certificate even when the current one is still valid, and `--all` runs the enable flow against every app on the host, which is useful after a bulk configuration change such as updating the global `email` or `server` property.

Setting the email once at the `--global` scope saves repeating it per app:

shell
dokku letsencrypt:set --global email [email protected]

One more behaviour to know: when 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.

Renewal, rate limits and the challenge type

Renewal is handled by `letsencrypt:auto-renew`, which renews any certificate whose remaining lifetime has dropped below the configured `graceperiod`. Called with no app argument, it scans every app on the host, and the README explains that it sorts apps by ascending time-to-renewal so the most urgent renewals run first, which matters when an ACME rate limit is in play.

The challenge type is chosen by configuration. `letsencrypt:enable` issues a certificate via the configured challenge type, which is HTTP-01 by default and DNS-01 when `dns-provider` is set. That distinction decides whether the plugin is usable at all in your setup. HTTP-01 needs the domain to serve the challenge file over the public internet, which the deployment precondition already assumes. DNS-01 requires the plugin to be able to update a DNS record for the domain, which needs a DNS provider integration rather than a public web root.

The cron job invokes the same `letsencrypt:auto-renew` command, and the README is explicit that renewal can only happen if something runs it on a schedule. That is why `letsencrypt:cron-job --add` is part of the install step rather than an optional extra, and why `--remove` exists to remove the entry and a bare invocation prints whether it is installed.

Testing the plugin against a real ACME stack

The repository carries a substantial test harness, and the Makefile shows how it works. The test target drives a Docker Compose stack rather than mocking the ACME exchange, with the compose file at `tests/docker-compose.yml` and a host directory under `tmp/letest-host`.

The build steps are separate targets. `build-lego` builds a local test image from `tests/lego`, `build-stack` creates the host directory and brings the compose stack up, `wait-stack` waits for it, and `install-plugin` runs the setup script inside the Dokku container. `make setup` chains those four.

The unit tests use bats, with the flags `--timing --print-output-on-failure`, and the Makefile lets you narrow the scope in two ways: `UNIT_TESTS` can point at a single file relative to the tests directory, and `UNIT_TESTS_FILTER` is passed to bats as a `--filter` to select one test by name. A separate lint target runs shellcheck over every plugin bash file, and the list of those files is enumerated in the Makefile, including the subcommand scripts and the test helpers. There are native variants of the setup, test and clean targets for running without containers.

Release cadence and the licence

The plugin is released steadily: 0.25.0 on 2026-05-13, 0.25.1 on 2026-07-07, and 0.25.2 on 2026-08-28. The repository is not archived and the last push was on 2026-09-28, so bug fixes and changes to the lego image tag continue to land on the master branch.

The licence is MIT, which is the permissive choice for a plugin that runs as root on a production host.

The upgrade path deserves one more look, because the version numbers alone do not tell the whole story. `sudo dokku plugin:update letsencrypt` both updates the code and migrates the old environment variable names to the current property names, and `LETSENCRYPT_IMAGE_VERSION` lets you pin the lego container tag independently of the plugin version. The lego image the Dockerfile is based on is pinned at v4.35.2, so the ACME client version in use is visible in the repository rather than hidden in a floating tag.

Editorial conclusion

dokku-letsencrypt suits a Dokku host that already serves apps over public HTTP and wants certificates issued and renewed without a separate certbot setup on the machine, and the operator accepts the Subscriber Agreement on the host's behalf. It does not fit an app that is not yet deployed or not reachable from the internet, since the HTTP-01 challenge needs a live domain, and it is not a general ACME client for other servers. Before enabling it, run `dokku letsencrypt:list` to see the current expiry state, decide between HTTP-01 and DNS-01 based on whether the domain can serve a challenge file, and set the global `email` once with `dokku letsencrypt:set --global email`.

Frequently asked questions

What does dokku-letsencrypt do?

It is the official Dokku plugin that retrieves and installs free TLS certificates from Let's Encrypt, using the lego ACME client, and renews them automatically once a cron job is enabled. The plugin passes --accept-tos to lego so unattended renewal does not block on a prompt.

How do I install dokku-letsencrypt on a Dokku host?

Run `sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git` to install the plugin and pull the lego Docker image, then `sudo dokku letsencrypt:cron-job --add` to schedule daily auto-renewals. Later upgrades use `sudo dokku plugin:update letsencrypt`, which also migrates legacy environment variables.

Why can I not enable a certificate for my app?

The app must already be deployed and reachable on the public internet over HTTP, because Let's Encrypt fetches a challenge file from the domain to prove control. A newly created app with no successful deploy cannot be enabled.

How do I check which Dokku apps have certificates and when they expire?

Use `dokku letsencrypt:list` to list letsencrypt-secured apps with their certificate expiry times, and `dokku letsencrypt:report` for a report that can also be emitted as JSON for scripting.

How does automatic renewal pick which certificate to renew first?

letsencrypt:auto-renew renews any certificate below the configured graceperiod, and with no app argument it scans every app on the host sorted by ascending time-to-renewal, so the most urgent renewal runs first when an ACME rate limit is in play.

Official sources

  1. dokku/dokku-letsencrypt on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dokku-dokku-letsencrypt.svg)](https://hysenlabs.com/projects/dokku-dokku-letsencrypt)

Was this useful?