Open-source project
diafygi/acme-tiny avatar
diafygi/acme-tiny

acme-tiny: a 200-line ACME client you are meant to read before trusting it

A tiny script to issue and renew TLS certs from Let's Encrypt

4,769 stars575 forksPythonMIT

At a glance

What is it?
acme-tiny issues and renews Let's Encrypt certificates from a single Python file that holds no dependencies beyond python and openssl. It is aimed at administrators who want to audit the code that touches their account key, and it deliberately leaves registration, HTTP serving and cron scheduling to you.
Who is it for?
acme-tiny suits administrators who already manage nginx or Apache, can host a challenge directory over plain HTTP on port 80, and want to read every line that handles their Let's Encrypt account key. It is the wrong choice if you want a client that registers the account, edits your web server config and installs a renewal timer for you, or if your only challenge option is DNS-01.
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 159 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

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

Editorial analysis

What acme-tiny does that certbot does not do for you

The README describes acme-tiny as a tiny, auditable script you put on your server to issue and renew Let's Encrypt certificates. The design constraint behind that sentence is trust: the script runs on your machine and reads your Let's Encrypt account private key, so the author kept it under 200 lines and states the only prerequisites are python and openssl. There is no plugin system and no dependency list in pyproject.toml; the dependencies array is empty.

That shapes who it is for. acme-tiny handles the ACME conversation and nothing around it. You create the account key, you create the certificate signing request, you make your web server answer HTTP requests for the challenge path, and you schedule the renewal. The README is explicit about the audience: if you do not already understand what a registered public key and a signed request mean, it says the script likely is not for you and points at the official Let's Encrypt client instead. Treat that as the honest boundary rather than a marketing line.

The ACME exchange behind a single command

The whole program is one file, acme_tiny.py, plus a tests directory and a pyproject.toml. The README's Step 4 shows the shape of a run: the script is given an account key, a CSR and a directory, and it writes the signed certificate chain to stdout. Everything else happens inside that invocation.

The mechanism follows the HTTP-01 challenge. acme-tiny asks Let's Encrypt for a challenge for each name in the CSR, writes the token file into the directory passed as --acme-dir, and tells the CA it is ready. Let's Encrypt then fetches that file over plain HTTP from port 80 on your domain. The README notes that a redirect to HTTPS is acceptable, but the initial request is HTTP. Once validation succeeds, acme-tiny finalizes the order and prints the chain. Since acme-tiny 4.0.0 and the ACME v2 release, the intermediate certificate is included in that download, so older shell scripts and Makefiles that concatenated an intermediate by hand need to drop that step.

The data flow is deliberately one-directional: private key in, certificate out, challenge files written to a directory you already serve. Nothing is stored in a state directory, and there is no account database to keep in sync.

Installing acme-tiny and issuing a first certificate

The project is on PyPI as acme-tiny and declares a console script entry point named acme-tiny, so a normal pip install gives you both the module and the command. The README's own examples call the script directly as python acme_tiny.py, which works if you have the file on disk.

bash
pip install acme-tiny

You need an account key before anything else. The README's first step is a 4096-bit RSA key written to account.key. If you already have a key from the original Let's Encrypt client, it is stored in JWK format and must be converted to PEM first; the README links a conversion script by JonLundy for that case.

bash
openssl genrsa 4096 > account.key

Next, a domain key and a CSR. For a single name the README uses the CN field. For a name plus its www host, it switches to a subjectAltName extension, and it gives a second variant of that command for openssl older than 1.1.1 that passes -reqexts SAN with a config file.

bash
openssl genrsa 4096 > domain.key
openssl req -new -sha256 -key domain.key -subj "/" -addext "subjectAltName = DNS:yoursite.com, DNS:www.yoursite.com" > domain.csr

Create the challenge directory and point your web server at it under the /.well-known/acme-challenge/ path. The README's nginx example uses an alias to /var/www/challenges/ with try_files $uri =404.

nginx
location /.well-known/acme-challenge/ {
    alias /var/www/challenges/;
    try_files $uri =404;
}

Now run the script. It needs permission to write into the challenge directory and to read the account key and CSR. The signed chain lands in signed_chain.crt, and you point your TLS server block at that file and at domain.key.

bash
python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /var/www/challenges/ > ./signed_chain.crt

Renewal is a shell script and a crontab line. The README's example writes to a temporary file, moves it into place only on success, and reloads nginx; the cron entry runs it on the first of each month and appends stderr to a log.

sh
#!/usr/bin/sh
python /path/to/acme_tiny.py --account-key /path/to/account.key --csr /path/to/domain.csr --acme-dir /var/www/challenges/ > /path/to/signed_chain.crt.tmp || exit
mv /path/to/signed_chain.crt.tmp /path/to/signed_chain.crt
service nginx reload

Where acme-tiny breaks, and the cases it cannot serve

The challenge directory is the fragile part. Let's Encrypt fetches the token over HTTP on port 80, so any firewall rule, CDN in front of the origin, or server block that does not route /.well-known/acme-challenge/ to the directory you passed will fail validation. The README documents the nginx location block but does not document rollback or recovery from a failed order, and it does not describe what the script prints on error beyond the fact that the renewal script exits on a non-zero status.

The renewal example has a second weakness worth naming. It writes the new chain to a temporary file and moves it into place, which avoids truncating a working certificate on failure, but the cron line only appends stderr to /var/log/acme_tiny.log. Nothing in the README reads that log or alerts on it. A certificate that quietly stops renewing is invisible until it expires.

There is also no DNS-01 support in anything the README describes, so wildcard certificates are out of reach. If you need a wildcard, or if port 80 on your host is not reachable from the public internet, this is the wrong tool regardless of how small the script is.

acme-tiny against the official Let's Encrypt client

The README points readers who do not follow the account-key explanation at the official Let's Encrypt client, and the comparison is about division of labour rather than protocol support. Both speak ACME and both can obtain the same certificates. The difference is what sits around the protocol.

The official client registers the account, discovers or edits web server configuration, and installs a renewal timer as part of its own workflow. acme-tiny does none of that. It assumes the account key exists, assumes the CSR exists, assumes the challenge path is already served, and assumes something else runs it on a schedule. In exchange you get a file small enough to read in one sitting, with no dependencies declared in pyproject.toml, which is the entire reason the project exists. If you would rather have the automation than the audit surface, the official client is the better fit and the README says so.

Maintenance, licence and what a version bump costs you

The repository is not archived, and the last push was on 2026-04-27. pyproject.toml declares version 5.0.3, requires-python ">=3.7,>=2.7", and lists classifiers from Python 2.7 through 3.13, with Development Status 5 - Production/Stable and Intended Audience System Administrators. The project is distributed under the MIT licence, with license-files pointing at the LICENSE file in the repository root; that permits reuse and modification, but it is not legal advice and you should read the text yourself.

Upgrade cost is low by construction. There is one module, no runtime dependencies, and a tests directory in the repository. The upgrade risk is not in the code but in the invocation: the README warns that scripts written for acme-tiny before 4.0 still concatenate the intermediate certificate, and doing that against a current version would duplicate it in the chain. If you carry a shell script or Makefile from that era, check whether it still appends an intermediate before you bump the version.

Editorial conclusion

acme-tiny suits administrators who already manage nginx or Apache, can host a challenge directory over plain HTTP on port 80, and want to read every line that handles their Let's Encrypt account key. It is the wrong choice if you want a client that registers the account, edits your web server config and installs a renewal timer for you, or if your only challenge option is DNS-01. Before adopting it, run the script once by hand and check the output chain, confirm the challenge directory is reachable at /.well-known/acme-challenge/ over HTTP, and decide how you will detect a failed renewal, because the README's cron example redirects stderr to a log file and nothing else.

Frequently asked questions

What does ACME stand for in SSL?

The README does not expand the acronym; it refers to the ACME protocol as the thing Let's Encrypt uses and notes the ACME v2 release that arrived with acme-tiny 4.0.0. The script's job is to speak that protocol on your behalf.

Does Let's Encrypt still work with acme-tiny?

The README is written around Let's Encrypt throughout, from the account key step to the 90-day certificate lifetime it cites for renewals, and it notes that since the ACME v2 release the intermediate certificate is included in the issued download. Nothing in the repository suggests the integration has been dropped.

What is an ACME certificate?

The README does not define the term. What it shows is that acme-tiny submits a CSR to Let's Encrypt, proves control of the domains by hosting challenge files, and prints a signed certificate chain to stdout.

Official sources

  1. diafygi/acme-tiny on GitHub
  2. Issues
  3. License: MIT
  4. README
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/diafygi-acme-tiny.svg)](https://hysenlabs.com/projects/diafygi-acme-tiny)