README
¶
Standalone ACME Client
This ACME-client is based on the awesome go-acme/lego library. ❤️
This client enables you to supply a simple configuration-file that will request certificates and save them to your filesystem - similar to how dehydrated does.
It also checks if existing certificates need to be re-created - if:
- Its lifetime is below the configured
renewal_days - A file is missing (cert/key/bundle)
- It has an invalid format (corrupted)
- Its public/private keypair do not match
- Configured domains do not match the ones in the certificate-SAN
Usage
Install
- Get the binary
-
Docker image: oxlorg/acme-client
-
Download pre-compiled binary from the Releases
-
Or build it yourself:
-
Build:
mkdir $REPO/build cd $REPO go mod tidy # download dependencies GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o "../build/acme" ./cmd/main.go
- Transfer the binary to your server
- Prepare for challenges:
- To use the
http-01challenge - configure a web-root directory and a web-server that serves its content via plain HTTP - To use the
dns-01challenge - create an account at a supported provider
Config
It expects a YAML config-file in this format:
---
path_web: '/var/www/acme' # web-root-directory - has to contain '<path>/.well-known/acme-challenge/' and be writable for the service-user
path_certs: '/var/local/acme'
file_mode_cert: 0644 # default: 0640
file_mode_key: 0640 # default: 0600
file_group: 'ssl-cert' # default: primary group of service-user
create_bundle: true # optionally create certificate bundles (public: cert+ca, private: cert+ca+pk)
email: 'test@waf.alpenmesh.com'
retries: 1 # retries per configured certificate if a validation error occurred
cooldown_sec: 2 # seconds to wait between requests/retries
renewal_days: 14 # default: 14; when cert-lifetime falls below - it gets renewed
max_domains: 50 # default: 50; providers like letsencrypt have a hard-limit (100) and if you have many domains its more efficient for processing & when appending new ones (batches)
hook_cmd: 'sudo systemctl reload haproxy.service' # hook command to be ran after all certificates were processed AND something changed
groups:
- name: "App #1"
id: 1
services:
- id: 1
challenge_type: "http-01"
provider: "letsencrypt_staging" # or use URL: https://acme-staging-v02.api.letsencrypt.org/directory
domains:
- 'aaa.waf.alpenmesh.com'
- 'bbb.waf.alpenmesh.com'
- 'ccc.waf.alpenmesh.com'
- name: "App #2"
id: 2
services:
- id: 1
challenge_type: "dns-01"
provider: "cloudflare"
provider_config: # env-vars to pass the go-acme/lego execution
CLOUDFLARE_API_KEY: '...'
domains:
- '*.alpenmesh.net'
For DNS-Provider config see: go-acme/lego documentation
WARNING: A change to the Group-ID or Service-ID will trigger a renewal for those certificates.
See also: Examples
Run
./acme --help
> OXL ACME-Client | Version: 1.1.0 | License: MIT | Repo: https://git.OXL.at/acme-client | © 2026 OXL IT Services
> Usage of build/acme:
> -c string
> Path to config file (default "acme.yml")
> -check
> Try-run mode without actually processing
> -show-providers
> Only show supported DNS-providers & HTTP-Provider aliases and exit
> -validate
> Only validate the config-file
# move to permanent directory after upload
mv ./acme /usr/local/bin/acme
# to check the supported DNS-providers & HTTP-Provider aliases
/usr/local/bin/acme -show-providers
# run
/usr/local/bin/acme -c /etc/acme/acme.yml
# to connect over proxy
export HTTPS_PROXY=http://test-proxy.waf.alpenmesh.com:3128
/usr/local/bin/acme -c /etc/acme/acme.yml
Result
File format: <Group-ID>_<Service-ID>-<Cert-/Batch-ID>
root@srv:/var/local/acme# tree
├── account # account cache
│ ├── account_09ff80dda58a752729e0506d726ba47590ff1413129666b581a2eee1fa01449b.key
│ └── account_3bfae30343be0ae9c6709cc568ac155d2c3cb562fdf487f6e858b8cd0006cd27.key
├── bundle_certs # if 'create_bundle: true' | public-key bundles
│ ├── 1_1_1.crt
│ └── 2_1_1.crt
├── bundle_private # if 'create_bundle: true' | bundles including private-key
│ ├── 1_1_1.pem
│ └── 2_1_1.pem
├── certs # public-keys
│ ├── 1_1_1.crt
│ └── 2_1_1.crt
└── private # private-keys
├── 1_1_1.key
└── 2_1_1.key
root@srv:/var/local/acme# ls -l
drwx------ 2 acme acme 4096 Jan 5 23:32 account
drwxr-xr-x 2 acme ssl-cert 4096 Jan 5 23:34 bundle_certs
drwxr-x--- 2 acme ssl-cert 4096 Jan 5 23:34 bundle_private
drwxr-xr-x 2 acme ssl-cert 4096 Jan 5 23:34 certs
drwxr-x--- 2 acme ssl-cert 4096 Jan 5 23:34 private
root@srv:/var/local/acme# ls -l */*
-rw------- 1 acme acme 227 Jan 5 23:32 account/account_09ff80dda58a752729e0506d726ba47590ff1413129666b581a2eee1fa01449b.key
-rw------- 1 acme acme 227 Jan 5 23:32 account/account_3bfae30343be0ae9c6709cc568ac155d2c3cb562fdf487f6e858b8cd0006cd27.key
-rw-r--r-- 1 acme ssl-cert 3831 Jan 5 23:33 bundle_certs/1_1_1.crt
-rw-r--r-- 1 acme ssl-cert 3831 Jan 5 23:33 bundle_certs/2_1_1.crt
-rw-r----- 1 acme ssl-cert 5510 Jan 5 23:33 bundle_private/1_1_1.pem
-rw-r----- 1 acme ssl-cert 5506 Jan 5 23:33 bundle_private/2_1_1.pem
-rw-r--r-- 1 acme ssl-cert 1935 Jan 5 23:33 certs/1_1_1.crt
-rw-r--r-- 1 acme ssl-cert 1935 Jan 5 23:33 certs/2_1_1.crt
-rw-r----- 1 acme ssl-cert 1679 Jan 5 23:33 private/1_1_1.key
-rw-r----- 1 acme ssl-cert 1675 Jan 5 23:33 private/2_1_1.key
Output / Logs
Note: This output is not from the exact config-example above.
First run:
OXL ACME-Client | Version: 1.0.2 | License: MIT | Repo: https://git.OXL.at/acme-client | © 2026 OXL IT Services
2026/01/05 23:04:30 [INFO] [App: 1 'App #1' | Cert: 1] processing...
2026/01/05 23:04:30 [INFO] [App: 1 'App #1' | Cert: 1] updating: certificate file missing
2026/01/05 23:04:31 [INFO] [App: 1 'App #1' | Cert: 1] obtaining certificate...
2026/01/05 23:04:31 [INFO] Generating new account key for test@waf.alpenmesh.com
2026/01/05 23:04:32 [INFO] acme: Trying to resolve account by key
2026/01/05 23:04:32 [INFO] Registering new ACME account for test@waf.alpenmesh.com
2026/01/05 23:04:32 [INFO] acme: Registering account for test@waf.alpenmesh.com
2026/01/05 23:04:32 [INFO] [aaa.waf.alpenmesh.com, bbb.waf.alpenmesh.com, ccc.waf.alpenmesh.com] acme: Obtaining bundled SAN certificate
2026/01/05 23:04:33 [INFO] [aaa.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042904583
2026/01/05 23:04:33 [INFO] [bbb.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042904593
2026/01/05 23:04:33 [INFO] [ccc.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042904603
2026/01/05 23:04:33 [INFO] [aaa.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:04:33 [INFO] [aaa.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:04:33 [INFO] [bbb.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:04:33 [INFO] [bbb.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:04:33 [INFO] [ccc.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:04:33 [INFO] [ccc.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:04:33 [INFO] [aaa.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:04:48 [INFO] [aaa.waf.alpenmesh.com] The server validated our request
2026/01/05 23:04:48 [INFO] [bbb.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:04:51 [INFO] [bbb.waf.alpenmesh.com] The server validated our request
2026/01/05 23:04:51 [INFO] [ccc.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:04:56 [INFO] [ccc.waf.alpenmesh.com] The server validated our request
2026/01/05 23:04:56 [INFO] [aaa.waf.alpenmesh.com, bbb.waf.alpenmesh.com, ccc.waf.alpenmesh.com] acme: Validations succeeded; requesting certificates
2026/01/05 23:04:57 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2026/01/05 23:05:00 [INFO] [aaa.waf.alpenmesh.com] Server responded with a certificate.
2026/01/05 23:05:00 [INFO] [App: 1 'App #1' | Cert: 2] processing...
2026/01/05 23:05:00 [WARN] [App: 1 'App #1' | Cert: 2] has 1 duplicate domains configured
2026/01/05 23:05:00 [INFO] [App: 1 'App #1' | Cert: 2] updating: certificate file missing
2026/01/05 23:05:01 [INFO] [App: 1 'App #1' | Cert: 2] obtaining certificate...
2026/01/05 23:05:01 [INFO] acme: Trying to resolve account by key
2026/01/05 23:05:01 [INFO] [ddd.waf.alpenmesh.com, eee.waf.alpenmesh.com] acme: Obtaining bundled SAN certificate
2026/01/05 23:05:02 [INFO] [ddd.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042910063
2026/01/05 23:05:02 [INFO] [eee.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042910073
2026/01/05 23:05:02 [INFO] [ddd.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:02 [INFO] [ddd.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:02 [INFO] [eee.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:02 [INFO] [eee.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:02 [INFO] [ddd.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:09 [INFO] [ddd.waf.alpenmesh.com] The server validated our request
2026/01/05 23:05:09 [INFO] [eee.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:14 [INFO] Skipping deactivating of valid auth: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042910063
2026/01/05 23:05:14 [INFO] Deactivating auth: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042910073
2026/01/05 23:05:14 [WARN] [App: 1 'App #1' | Cert: 2] attempt 1 failed: "error: one or more domains had a problem:
[eee.waf.alpenmesh.com] invalid authorization: acme: error: 400 :: urn:ietf:params:acme:error:dns :: While processing CAA for eee.waf.alpenmesh.com: DNS problem: SERVFAIL looking up CAA for com - the domain's nameservers may be malfunctioning
"
2026/01/05 23:05:15 [INFO] [App: 1 'App #1' | Cert: 2] obtaining certificate...
2026/01/05 23:05:15 [INFO] acme: Trying to resolve account by key
2026/01/05 23:05:16 [INFO] [ddd.waf.alpenmesh.com, eee.waf.alpenmesh.com] acme: Obtaining bundled SAN certificate
2026/01/05 23:05:16 [INFO] [ddd.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042910063
2026/01/05 23:05:16 [INFO] [eee.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042912623
2026/01/05 23:05:16 [INFO] [ddd.waf.alpenmesh.com] acme: authorization already valid; skipping challenge
2026/01/05 23:05:16 [INFO] [eee.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:16 [INFO] [eee.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:16 [INFO] [eee.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:23 [INFO] [eee.waf.alpenmesh.com] The server validated our request
2026/01/05 23:05:23 [INFO] [ddd.waf.alpenmesh.com, eee.waf.alpenmesh.com] acme: Validations succeeded; requesting certificates
2026/01/05 23:05:24 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2026/01/05 23:05:24 [INFO] [ddd.waf.alpenmesh.com] Server responded with a certificate.
2026/01/05 23:05:24 [INFO] [App: 2 'App #2' | Cert: 1] processing...
2026/01/05 23:05:24 [INFO] [App: 2 'App #2' | Cert: 1] updating: certificate file missing
2026/01/05 23:05:25 [INFO] [App: 2 'App #2' | Cert: 1] obtaining certificate...
2026/01/05 23:05:26 [INFO] acme: Trying to resolve account by key
2026/01/05 23:05:26 [INFO] [fff.waf.alpenmesh.com, ggg.waf.alpenmesh.com, hhh.waf.alpenmesh.com] acme: Obtaining bundled SAN certificate
2026/01/05 23:05:27 [INFO] [fff.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042914213
2026/01/05 23:05:27 [INFO] [ggg.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042914223
2026/01/05 23:05:27 [INFO] [hhh.waf.alpenmesh.com] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/255623553/21042914233
2026/01/05 23:05:27 [INFO] [fff.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:27 [INFO] [fff.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:27 [INFO] [ggg.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:27 [INFO] [ggg.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:27 [INFO] [hhh.waf.alpenmesh.com] acme: Could not find solver for: tls-alpn-01
2026/01/05 23:05:27 [INFO] [hhh.waf.alpenmesh.com] acme: use http-01 solver
2026/01/05 23:05:27 [INFO] [fff.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:32 [INFO] [fff.waf.alpenmesh.com] The server validated our request
2026/01/05 23:05:32 [INFO] [ggg.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:40 [INFO] [ggg.waf.alpenmesh.com] The server validated our request
2026/01/05 23:05:40 [INFO] [hhh.waf.alpenmesh.com] acme: Trying to solve HTTP-01
2026/01/05 23:05:53 [INFO] [hhh.waf.alpenmesh.com] The server validated our request
2026/01/05 23:05:53 [INFO] [fff.waf.alpenmesh.com, ggg.waf.alpenmesh.com, hhh.waf.alpenmesh.com] acme: Validations succeeded; requesting certificates
2026/01/05 23:05:53 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2026/01/05 23:05:54 [INFO] [fff.waf.alpenmesh.com] Server responded with a certificate.
2026/01/05 23:05:54 [INFO] Executing hook: "echo "DONE""
DONE
Second run:
OXL ACME-Client | Version: 1.0.0 | License: MIT | Repo: https://git.OXL.at/acme-client | © 2026 OXL IT Services
2026/01/05 23:12:41 [INFO] [App: 1 'App #1' | Cert: 1] processing...
2026/01/05 23:12:41 [INFO] [App: 1 'App #1' | Cert: 1] skipping: cert is valid
2026/01/05 23:12:41 [INFO] [App: 1 'App #1' | Cert: 2] processing...
2026/01/05 23:12:41 [WARN] [App: 1 'App #1' | Cert: 2] has 1 duplicate domains configured
2026/01/05 23:12:41 [INFO] [App: 1 'App #1' | Cert: 2] skipping: cert is valid
2026/01/05 23:12:41 [INFO] [App: 2 'App #2' | Cert: 1] processing...
2026/01/05 23:12:41 [INFO] [App: 2 'App #2' | Cert: 1] skipping: cert is valid
Click to show internal directories.
Click to hide internal directories.