Open-source project
dehydrated-io/dehydrated avatar
dehydrated-io/dehydrated

dehydrated: an ACME client that is one bash script

ACME client implemented as a simple shell-script – just add water

6,246 stars724 forksShellMIT

At a glance

What is it?
dehydrated signs and renews TLS certificates against ACME servers such as Let's Encrypt and ZeroSSL using a single bash script and openssl. It suits operators who want certificate automation they can read, and it is a poor fit for anyone who wants a library or a managed service.
Who is it for?
Adopt dehydrated when you already run your own web server, want certificate automation you can read end to end, and are comfortable with shell, cron and file permissions. Do not adopt it if you want a daemon that watches certificates for you, a language library to call from application code, or a system where nobody is available to debug a failed challenge.
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 153 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 September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What dehydrated does that a web server plugin does not

Most people meet ACME through a web server plugin: you install a module, it hooks into the server's configuration, and certificates appear. That works until the certificate is not for the web server. dehydrated takes the other position. It is a standalone client that talks to any ACME v1 or ACME v2 server, and it writes the certificate files to disk. What happens next is your problem, which is exactly the point. The README lists the targets it is built for: signing a list of domains including wildcards, signing a custom CSR either standalone or automated through hooks, renewal when a certificate is about to expire or the domain set changes, and revocation. The audience is the operator who terminates TLS somewhere a plugin does not reach: a mail server, an SMTP gateway, an IMAP endpoint, a reverse proxy, or a set of internal services that all read the same PEM files. It is also for people who dislike opaque tooling. The whole client is a shell script in the repository root, next to docs/ and CHANGELOG, so you can read every request it makes.

How the script, openssl and the hooks fit together

dehydrated does no cryptography itself. The README states it uses the openssl utility for everything related to handling keys and certificates, so openssl must be installed. Everything else it needs is cURL, sed, grep, awk and mktemp, and the README notes those are pre-installed almost everywhere except cURL. That dependency list is the architecture in miniature: the script orchestrates HTTP calls to the ACME server, shells out to openssl for key generation and CSR handling, and writes results into a certificate directory.

Domain selection comes from domains.txt, one entry per certificate, and the README points new users at docs/domains_txt.md before anything else. Validation is where the hook system matters. With the http-01 challenge you place a token under a web-accessible WELLKNOWN path; the README tells you to set that path up first and then fill in domains.txt. With dns-01 you need a hook script that creates and removes TXT records, because the client cannot edit your DNS zone by itself. The --hook parameter takes the path to that script, and the hook is also the extension point for installing the finished certificate wherever it needs to go. If you skip the hook, dehydrated still issues the certificate and leaves it in the output directory. Nothing reloads your service for you.

Installing dehydrated and issuing a first certificate

The README does not give a package installation command, so treat the repository itself as the source: the dehydrated script sits at the top level, and the docs directory holds the guides the README recommends, including docs/domains_txt.md, docs/wellknown.md and docs/staging.md. The config example lives at docs/examples/config, and the README says to copy it to a location such as /etc/dehydrated/config and edit it. dehydrated searches for config in a fixed order and uses the first file it finds: /etc/dehydrated/config, then /usr/local/etc/dehydrated/config, then the current working directory of your shell, then the directory it was run from. Pick one deliberately, because a stray config in the working directory will silently take precedence over the system one.

Before touching production, point the client at the staging CA. The README is explicit that you should use the staging URL when experimenting so you do not hit Let's Encrypt rate limits, and docs/staging.md covers the details. Registration accepts the terms of service:

bash
./dehydrated --register --accept-terms

With the config in place and domains.txt filled in, a cron run signs anything missing, changed or close to expiry. The README describes --cron as signing or renewing non-existent, changed or expiring certificates:

bash
./dehydrated --cron

For a one-off certificate that should not come from domains.txt, --domain takes the names directly and --alias names the certificate directory, which the README says is only used when --domain is specified:

bash
./dehydrated --cron --domain example.com --alias example

Expect a certificate directory named after the primary domain, or after the alias, containing the key, certificate and chain files. Renewal decisions are driven by RENEW_DAYS in the config, and --force overrides them when you need a renewal now.

The failure modes you will actually hit

The first is the config search order. Because the current working directory is checked before the directory the script was run from, a config file left in a home directory or a deployment folder can override the system config without any warning. The symptom is a run that behaves as if your edits had no effect.

The second is the challenge path. If WELLKNOWN is not reachable from the public internet, or the web server does not serve the token, http-01 validation fails and the error surfaces as an ACME validation failure rather than a web server error. The README sends you to docs/wellknown.md for this and to docs/troubleshooting.md when something breaks.

The third is hook errors. dns-01 and automated CSR signing depend on your hook script doing the right thing at the right moment; a hook that fails to clean up a TXT record leaves stale state in your zone. The --keep-going flag exists because cron mode processes multiple certificates, and by default an error stops the run. That is a reasonable default and a real trade-off: with --keep-going you get the rest of the certificates renewed, but you also need to read the output to notice the one that failed.

The fourth is concurrency. dehydrated uses a lockfile, and --no-lock disables it, which the README itself flags as potentially dangerous. Two overlapping cron runs without a lock can collide. If you use --domain with several certificates, --lock-suffix gives each run its own lock name.

Finally, there are the timeouts. --order-timeout and --validation-timeout bound how long the client waits for order processing and domain validation before erroring out. On slow DNS propagation, a short timeout turns a working setup into a flapping one.

Where dehydrated is the wrong tool

If you want a long-running daemon that notices a certificate is close to expiry and renews it on its own schedule, dehydrated is not that. It is a command you run, typically from cron. The README's --cron command is described as signing or renewing non-existent, changed or expiring certificates, which means the scheduling decision belongs to you.

If your application is written in Go, Python or Rust and you want to obtain certificates in-process, calling a shell script is the wrong shape. The output is files on disk, and you would be parsing them or watching the directory. A library that speaks ACME natively fits that case better.

If nobody on the team is comfortable reading shell, debugging a failed hook, or checking file permissions on the account key, the simplicity of a single script stops being an advantage. dehydrated gives you no dashboard, no retry queue and no notification layer. It logs to stdout and exits. Everything else is yours to build, and the hook mechanism is the seam where that work goes.

How it compares to acme.sh and certbot

The closest alternative in spirit is acme.sh, which is also a shell client and also installs as a script. The difference is in what the project chooses to own. acme.sh bundles a large set of DNS provider integrations and an install step that puts the client on the system for you. dehydrated keeps the client small and pushes DNS and deployment work into the hook script you write, so the provider logic lives in your repository rather than in the client's. If your DNS provider is not one you want to script yourself, that is a real cost. If you want to see exactly what runs against your zone, it is a benefit.

certbot is the other reference point. It is a Python application with a plugin architecture, and its web server plugins edit nginx or Apache configuration directly. dehydrated never touches your web server config: it writes files and stops. That is why it works for mail servers and proxies, and it is also why certbot remains the easier choice when the certificate is for a standard nginx or Apache vhost and you want the installer to do the wiring. The README also notes that dehydrated supports ACME v1 alongside ACME v2, which matters only if you are pointed at an older CA endpoint.

Maintenance, licence and what upgrading costs

The repository is not archived, and the last push was on 2026-04-30. Release history is uneven: v0.7.0 in December 2020, v0.7.1 in October 2022, and v0.7.2 in May 2025. If you track versions, expect long gaps between tags and treat the master branch as the thing that moves. The CHANGELOG file at the repository root is where to look before upgrading, and because the client is a script, an upgrade is a file replacement rather than a package transaction, which means rollback is also a file replacement. The README does not document a rollback procedure, so keep the previous script until the new one has run through a renewal cycle.

The project is MIT licensed, which is permissive and places few conditions on redistribution or modification. The licence covers the client. It does not cover the certificates you obtain: those come from whichever CA you point --ca at, and their terms and rate limits apply. The README points at ZeroSSL as the maintaining organisation and mentions free and paid certificates, so if you use ZeroSSL as your CA, read their terms rather than assuming the MIT licence settles anything. That is a description of the licence text, not legal advice.

Operationally, the maintenance cost is not the script. It is the hook, the cron entry, the config file location and the account key. Those are the parts you own and the parts that break.

Editorial conclusion

Adopt dehydrated when you already run your own web server, want certificate automation you can read end to end, and are comfortable with shell, cron and file permissions. Do not adopt it if you want a daemon that watches certificates for you, a language library to call from application code, or a system where nobody is available to debug a failed challenge. Before trusting it with production domains, run the whole flow against the staging CA, confirm your WELLKNOWN path is reachable from the public internet, and decide where the config file will live, because dehydrated picks the first config it finds in /etc/dehydrated/config, /usr/local/etc/dehydrated/config, the current working directory, then the directory it was run from.

Frequently asked questions

What is dehydrated, the ACME client?

It is a client for signing certificates with an ACME server such as ZeroSSL or Let's Encrypt, implemented as a bash script that is also zsh-compatible. It supports ACME v1 and ACME v2, including wildcard certificates, and uses the openssl utility for key and certificate handling.

How do I install dehydrated?

The README does not give a package install command. The repository contains the dehydrated script at the top level and a docs directory with the getting started guides, and the README says to copy docs/examples/config to a location such as /etc/dehydrated/config and edit it.

What dependencies does dehydrated need?

openssl is required for everything related to keys and certificates, and cURL is needed for the ACME requests. sed, grep, awk and mktemp are also used, and the README notes those are found pre-installed on almost any system, with cURL being the exception.

Which challenge types does dehydrated support?

The --challenge parameter accepts http-01, dns-01, dns-persist-01 and tls-alpn-01. For http-01 you set up the WELLKNOWN path first, and the README recommends docs/wellknown.md and docs/domains_txt.md before you start.

How do I avoid hitting Let's Encrypt rate limits while testing?

The README states you should use the staging URL when experimenting with the script, and points to docs/staging.md for the details. Switching the CA back to production is a config change, not a code change.

Official sources

  1. dehydrated-io/dehydrated 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/dehydrated-io-dehydrated.svg)](https://hysenlabs.com/projects/dehydrated-io-dehydrated)