PyACME CLI is a small Python command line tool for issuing and renewing Let's Encrypt certificates with DNS-01 validation.
It can create wildcard certificates, manage DNS challenge records through supported providers, store certificate metadata locally, list existing certificates, and renew certificates from cron.
- Issue Let's Encrypt certificates with DNS-01 challenges.
- Support wildcard domains such as
*.example.com. - Support multiple names on one certificate.
- Automatically create TXT records with Cloudflare, ArvanCloud, or AcmeDNS.
- Support manual DNS TXT records when no provider integration is used.
- Store certificates, private keys, account keys, and metadata under
~/.pyacme. - Renew certificates automatically and run a custom command after renewal.
- Show certificate status in a terminal table.
| Provider | Value | Access token required | Notes |
|---|---|---|---|
| Cloudflare | cloudflare |
Yes | Uses the Cloudflare DNS API. |
| ArvanCloud | arvancloud |
Yes | Uses the ArvanCloud DNS API. |
| AcmeDNS | acmedns |
No | First run asks you to create a CNAME record. |
| Manual DNS | dns |
No | Prints the TXT record and waits for you to add it. |
- Python 3.12 or newer
- A domain you control
- DNS access for
_acme-challengerecords - Network access to Let's Encrypt and your DNS provider API
pip install pyacmecliYou can run the CLI with:
python -m pyacmecli --helpInitialize the local PyACME directory:
python -m pyacmecli initIssue a certificate with Cloudflare:
python -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider cloudflare \
--email admin@example.com \
--access-token 'cloudflare-api-token' \
--renew-command 'docker restart nginx'List saved certificates:
python -m pyacmecli listRenew certificates that expire within the next 30 days:
python -m pyacmecli cronForce renewal for all saved certificates:
python -m pyacmecli cron --force-renewalCreates the local data directory:
python -m pyacmecli initPyACME stores data in:
~/.pyacme
Requests a new certificate.
python -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider cloudflare \
--email admin@example.com \
--access-token 'provider-token' \
--renew-command 'systemctl reload nginx'Options:
| Option | Required | Description |
|---|---|---|
--domain |
Yes | Domain name. Can be used multiple times. |
--provider |
Yes | One of cloudflare, arvancloud, acmedns, or dns. |
--email |
Yes | Email used for the Let's Encrypt account. |
--access-token |
Cloudflare/ArvanCloud only | DNS provider API token. |
--renew-command |
Yes | Shell command to run after successful renewal. |
Shows certificates saved under ~/.pyacme:
python -m pyacmecli listThe table includes the domain, certificate path, expiry date, status, renew command, and last renewal time.
Renews certificates that expire within 30 days:
python -m pyacmecli cronRun it from cron once per day:
0 2 * * * /path/to/venv/bin/python -m pyacmecli cronTo renew every certificate regardless of expiry date:
python -m pyacmecli cron --force-renewalpython -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider cloudflare \
--email admin@example.com \
--access-token 'cloudflare-api-token' \
--renew-command 'systemctl reload nginx'The token needs permission to read zones and edit DNS records for the target zone.
python -m pyacmecli new \
--domain example.ir \
--domain '*.example.ir' \
--provider arvancloud \
--email admin@example.ir \
--access-token 'arvancloud-api-token' \
--renew-command 'docker restart nginx'python -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider acmedns \
--email admin@example.com \
--renew-command 'systemctl reload nginx'On the first run, PyACME registers with auth.acme-dns.io and prints a CNAME
record. Add that CNAME to your DNS zone, then press Enter to continue.
python -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider dns \
--email admin@example.com \
--renew-command 'systemctl reload nginx'PyACME prints the required _acme-challenge TXT record and waits until you add
it to DNS.
For each certificate, PyACME creates a directory under ~/.pyacme using the
first domain name from the request.
Example:
~/.pyacme/example.com/
account.key.pem
account_url.result
cert.pem
privkey.pem
certificate.json
Do not delete this directory if you want automated renewal to keep working.
Enable debug output with:
python -m pyacmecli --verbose new ...Or set a specific log level:
python -m pyacmecli --log-level DEBUG listmkdir -p /apps/pyacmecli
cd /apps/pyacmecli
python3.12 -m venv .venv
source .venv/bin/activate
pip install pyacmecli
python -m pyacmecli init
python -m pyacmecli new \
--domain example.com \
--domain '*.example.com' \
--provider cloudflare \
--email admin@example.com \
--access-token 'cloudflare-api-token' \
--renew-command 'docker restart nginx'
python -m pyacmecli list
python -m pyacmecli cronCron entry:
0 2 * * * /apps/pyacmecli/.venv/bin/python -m pyacmecli cronInstall dependencies with uv:
uv syncRun the CLI from the repository:
uv run python -m pyacmecli --helpBuild the package:
uv buildPublish the package:
uv publishRun linting:
uv run ruff check .This project is licensed under the Apache License 2.0. See LICENSE for details.