traefik-certificate-exporter
Overview
This tool can be used to extract acme certificates (ex: lets encrupt) from traefik json files. The tool is design to watch for changes to a folder for any files that match a filespec (defaults to *.json; a file name or glob, without a directory part) and when changes are detected it will process the file and extract any certificates that are in it to the specified output path
Supported stores: acme.json from any RFC 8555 CA (Let's Encrypt, ZeroSSL, Google Trust Services, step-ca, ...) written by Traefik v1.7, v2 or v3; the shape is detected from the file's keys, not the CA URL, and each resolver is exported independently.
Installation
Python Script/Tool
Installation can be done via the python package installer tool pip
pip install traefik-certificate-exporter
Usage
usage: traefik-certificate-exporter [-h] [-c PATH] [-d PATH] [-w] [-fs GLOB] [-o PATH] [--traefik-resolver-id RESOLVER] [--flat] [--restart-container] [--dry-run] [-r] [--include-resolvername-in-outputpath]
[--pkcs12-passphrase PASSPHRASE] [--no-validate-keys] [--no-dotenv] [--post-export-command COMMAND] [-ll LEVEL] [-id [DOMAIN ...] | -xd [DOMAIN ...]]
Extract traefik letsencrypt certificates.
options:
-h, --help show this help message and exit
-c PATH, --config-file PATH
the configuration file to load (default: config.yaml)
-d PATH, --data-path PATH
the path that contains the acme json files
-w, --watch-for-changes
If specified, monitor and watch for changes to acme files
-fs GLOB, --file-spec GLOB
file-name glob of the ACME store inside the data path (no directory part); default `*.json`
-o PATH, --output-path PATH
The folder to exports the certificates in to
--traefik-resolver-id RESOLVER
Traefik certificate-resolver-id.
--flat If specified, all certificates into a single folder
--restart-container If specified, any container that are labeled with 'com.github.ravensorb.traefik-certificate-exporter.domain-restart=<DOMAIN>' will be restarted, only on change: when an exported
certificate file for one of its domains is newer than the container's start. Multiple domains can be separated by ','
--dry-run Don't write files and do not restart docker containers.
-r, --run-at-start Runs Export immediately on start (used with watch-for-changes).
--include-resolvername-in-outputpath
Added the resolvername in the path used to export the certificates (ignored if flat is specified).
--pkcs12-passphrase PASSPHRASE
Passphrase used to encrypt the exported PKCS12 (.pfx) file with AES-256/SHA-256 (readable by Windows 10 1709+/Server 2019+, OpenSSL 3, current macOS). The .pfx always includes the
intermediates. Empty or unset: unencrypted.
--no-validate-keys If specified, skip RSA private-key validation when a .pfx is built (~0.5 s per RSA-4096 key). Only when the ACME store is written by your own Traefik: the keys are then trusted, not
checked. The key/certificate match is always checked.
--no-dotenv If specified, do not read a .env file from the current directory (env: TRAEFIK_CERTIFICATE_EXPORTER_SETTINGS_LOADDOTENV=false). The container image sets this.
--post-export-command COMMAND
Shell-like command line to run after an export pass, only on change: when at least one domain's exported files changed (no shell involved -- parsed with shlex, run via subprocess with a
fixed 30s timeout). The changed domain names are exposed to it as a comma-separated TRAEFIK_CERTIFICATE_EXPORTER_EXPORTED_DOMAINS environment variable. Treated as a secret: log lines
name only its program. On failure only the last line of its stderr is logged, with its arguments masked; output it prints in any other form is not redacted. Skipped entirely when --dry-
run is set.
-ll LEVEL, --log-level LEVEL
Set the logging level, one of: DEBUG, INFO, WARNING, ERROR, CRITICAL. When not given, TRAEFIK_CERTIFICATE_EXPORTER_LOGGING_LEVEL is used (case-insensitive); otherwise the default is
INFO.
-id [DOMAIN ...], --include-domains [DOMAIN ...]
If specified, only certificates that match domains in this list will be extracted
-xd [DOMAIN ...], --exclude-domains [DOMAIN ...]
If specified. certificates that match domains in this list will be ignored
Settings come from four layers, and for every setting the order is
CLI > env > config file > defaults: a CLI flag beats a TRAEFIK_CERTIFICATE_EXPORTER_*
environment variable, which beats the config file, which beats the packaged default. In the
image this makes the environment the run-mode switch: the image sets
TRAEFIK_CERTIFICATE_EXPORTER_SETTINGS_WATCHFORCHANGES=true, so watchforchanges: false in
/config/config.yaml alone does not select run-once (docker/README.md).
Argument parsing is strict: an unrecognised flag (or a stray positional argument) exits
immediately with code 2, naming the closest valid flag when there is one
(Did you mean '--data-path'?) and pointing at --help. This happens before logging is
configured, so nothing is written to the log file. If the container crash-loops
at start-up with no log output, check your command:/args: override for a typo and read
the container's stderr.
.env file (standalone CLI)
The CLI reads a .env file from the current working directory only. It does not look
for one next to the config file or in the installed package's folder. A variable from
.env acts as an environment variable, so the precedence above is unchanged (a variable
already set in the real environment wins over the file).
To turn .env loading off, pass --no-dotenv or set
TRAEFIK_CERTIFICATE_EXPORTER_SETTINGS_LOADDOTENV=false. The switch is read only from the
environment or the command line: a loaddotenv key in config.yaml or in a .env file is
ignored with an ERROR. The container image sets it to false and reads no .env
(docker/README.md).
Run as root (for example under sudo), a .env file may set only any-source settings,
and ${VAR} in a value is not expanded
(ADR-0049).
Examples
Watch the letsencrypt folder for any changes to files matching acme-*.json and export any certs managed by the resolver called "resolver-http"
Script
Run it once and then exit
traefik-certificate-exporter \
-d /mnt/traefik-data/letsencrypt \
-o /mnt/certs \
-fs "acme-*.json" \
-r
Run it and watch for changes to the files
traefik-certificate-exporter \
-d /mnt/traefik-data/letsencrypt \
-o /mnt/certs \
-fs "acme-*.json"
-w
Post-Export Hook
--post-export-command (or settings.postexportcommand / TRAEFIK_CERTIFICATE_EXPORTER_SETTINGS_POSTEXPORTCOMMAND)
runs a shell-like command line after an export pass, only on change: when at least
one domain's exported files changed in that pass -- useful for copying/chowning/moving an
exported certificate into another service's expected location. A pass that changed
nothing (including an exporter restart over an unchanged store) runs no hook and logs
No certificate changed; no hook or notification once at INFO
(ADR-0045). It is unset
by default (purely additive; no behavior change if you don't set it).
- The command is parsed with Python's
shlexand executed directly (never through a shell), so it is not vulnerable to shell injection, but it also doesn't support shell features like pipes, globs, or$VARexpansion. - The changed domain names (only those) are exposed to it as a comma-separated
TRAEFIK_CERTIFICATE_EXPORTER_EXPORTED_DOMAINSenvironment variable; it is never empty, because the hook does not run when nothing changed. - The command is treated as a secret, because it often carries a credential (an
Authorizationheader, a webhook token): it is redacted in the DEBUG settings dumps, and the hook's log lines name only the program (argv[0]), never its arguments. When the hook fails, only the last line of its stderr is logged, with its arguments masked and control characters stripped; it is not redacted beyond that, so keep credentials out of what it prints in any other form. - The export pass's
TRAEFIK_CERTIFICATE_EXPORTER_CORRELATION_IDis exposed too, so a hook that logs elsewhere can be joined to the exporter's own log lines for that pass. - A fixed 30 second timeout applies; a command that doesn't return in time is killed and the timeout is logged as an error.
- Skipped entirely when
--dry-runis set, consistent with dry-run suppressing file writes and container restarts. - A non-zero exit or timeout is logged as an error but never crashes the watch loop.
traefik-certificate-exporter \
-d /mnt/traefik-data/letsencrypt \
-o /mnt/certs \
-w \
--post-export-command "/usr/local/bin/notify-service.sh"
Logging and tracing an export pass
Each export pass (the start-up sweep, and each debounced pass the watcher runs) is tagged
with a correlation_id — a UUID4 shared by every log line that pass produces, from the
detected file change through the export, the post-export hook and the notification. Grep
one value to see the whole pass.
The rotating log file is JSON, so correlation_id is a field there already. Console output
is coloured human-readable text by default, with the id in brackets after the level; set
TRAEFIK_CERTIFICATE_EXPORTER_LOGGING_CONSOLEJSON=true to emit the console stream as JSON
instead (useful when docker logs is being shipped to a log aggregator). A - means the
line was emitted outside any export pass.
The log level is -ll/--log-level; when that is not given,
TRAEFIK_CERTIFICATE_EXPORTER_LOGGING_LEVEL (case-insensitive: DEBUG, INFO, WARNING,
ERROR, CRITICAL) is used, and otherwise INFO. The container's command line is fixed,
so the environment variable is how to change verbosity there
(-e TRAEFIK_CERTIFICATE_EXPORTER_LOGGING_LEVEL=DEBUG). An invalid value falls back to
INFO with one WARNING, and one INFO line at start-up names the effective level and its
source (cli, env or default).
See docs/operational.md.
Releases and images
- Versions come from git tags. A release is a
vX.Y.Ztag onmain; the package and the image carry that version. Every push tomainbetween releases publishes a dev build (X.Y.Z.devNfor the package,X.Y.Z-dev.Nanddevfor the image). - Python package: PyPI (releases), TestPyPI (dev builds) and the Gitea package index.
- Image: GitHub Container Registry, Docker Hub and the Gitea container registry,
built for
amd64andarm64.latestandX.Y.Z/X.Yfollow releases (andXfrom 1.0.0 on; no bare0tag is written);devfollowsmain. - A destination is enabled only when its
PUBLISH_*token secret and everyPUBLISH_*variable it needs are set, so a fork or a mirror publishes only where it has been configured to. A partly configured destination is skipped with one warning naming what is missing; an unconfigured one is silent. The Gitea package index and the Gitea container registry sharePUBLISH_GITEA_TOKENbut can be enabled separately. - Where the publish secrets live. One forge's run publishes a whole release. On GitHub
the
PUBLISH_*secrets live in thedevorreleaseenvironment, never at repository level (ADR-0039). On Gitea they are repository-level secrets, present only when Gitea is the publishing forge, with the mitigations of CI spine AD-13: protectedmainandv*tags, project-scoped tokens, rotation after any untrusted push. The forge settings to apply are the checklist in docs/ci/pipeline-walkthrough.md.
How the pipeline works, and how to set it up: docs/ci/pipeline-walkthrough.md.
Upgrading from an earlier release (what changes in each version and what to do about it): docs/upgrading.md.
Credits
This tool is HEAVLY influenced by the excellent work of DanielHuisman and Marc Brückner
Export notifications
Set appriseurls in your config file to be notified when a certificate changes — email,
chat, or a generic webhook, via Apprise:
settings:
appriseurls:
- "tgram://<bot-token>/<chat-id>"
- "mailto://user:pass@example.com"
There is no command-line flag for this setting, matching domains — the other list-valued
setting — and from the environment it takes indexed keys
(..._APPRISEURLS_0, ..._APPRISEURLS_1) rather than a comma-separated string, because a
comma is legal inside an Apprise URL.
A notification means a certificate changed: it is sent only on change, when at least
one domain's exported files changed in the pass, and it lists only those domains. Each built
file is compared with the one on disk and an unchanged file is not rewritten, so its
modification time stays stable
(ADR-0043). A pass that changed
nothing — an exporter restart over an unchanged store, or a pass that only recovers a lost
container restart — sends nothing
(ADR-0045). Delivery is
fire-and-forget — a failure is logged and stops nothing. For something that must succeed,
use postexportcommand, which blocks and reports its exit code.
See docker/README.md for the container setup.
Metadata
Release files for traefik-certificate-exporter 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| traefik_certificate_exporter-0.3.1.tar.gz | 83.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| traefik_certificate_exporter-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 166.8 kB
Release files / traefik_certificate_exporter-0.3.1.tar.gz
| Download URL | traefik_certificate_exporter-0.3.1.tar.gz |
|---|---|
| Size | 83.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d671a806322f13fb1efb5a97eb4d28d284419305368951429a312cd68f88d6f
|
|
BLAKE2b-256 checksum How to use checksums |
73c12c22d6c4fab3399118f0c15b073cf1da2b7823c493ea96b5a079a180696d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|
Release files / traefik_certificate_exporter-0.3.1-py3-none-any.whl
| Download URL | traefik_certificate_exporter-0.3.1-py3-none-any.whl |
|---|---|
| Size | 83.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
918d8762f3821dc203799e4d80c60e376bf581a402d7ca5e4f0d5fbb09c0c210
|
|
BLAKE2b-256 checksum How to use checksums |
08d23edfa7868dc87f83801f2e43b16abbf3c1a1ecad1ebe7fc5908593f590f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|