acme.sh
Certificate Management with ‘acme.sh’
I like to manage my certificates on my own. If you work with Wildcard Certs, acme.sh is a nice and flexible ACME Client, purely written in Shell.
It’s probably the easiest & smartest shell script to automatically issue & renew the free certificates.
Basic Handling
Get Version
acme.sh --version
run it
# acme.sh --version
https://github.com/acmesh-official/acme.sh
v3.0.6
Upgrade Self
are we up2date ?
acme.sh --upgrade
run it
# acme.sh --upgrade
[Mon May 1 11:35:55 CEST 2023] Already uptodate!
[Mon May 1 11:35:55 CEST 2023] Upgrade success!
Info
General Info about the Setup
acme.sh --info
run it
# acme.sh --info
LE_WORKING_DIR=/root/.acme.sh
LE_CONFIG_HOME=/root/.acme.sh
LOG_FILE='/root/.acme.sh/acme.sh.log'
#LOG_LEVEL=1
#AUTO_UPGRADE="1"
#NO_TIMESTAMP=1
USER_PATH='/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin:/ ...
UPGRADE_HASH='0d25xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
ACCOUNT_EMAIL='YOUR.EMAIL.NET'
DEFAULT_ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'
List all Certs
which Certs are Managed with acme.sh ?
acme.sh --list
run it
# acme.sh --list
Main_Domain KeyLength SAN_Domains CA Created Renew
selfhosting.ch "ec-256" *.selfhosting.ch LetsEncrypt.org 2023-05-01T06:25:40Z 2023-06-29T06:25:40Z
stoege.net "ec-256" *.stoege.net LetsEncrypt.org 2023-04-13T05:00:25Z 2023-06-11T05:00:25Z
... some more ...
Info about selfhosting.ch
acme.sh info selfhosting.ch
run it
# acme.sh info selfhosting.ch
[Mon May 1 11:44:39 CEST 2023] The domain 'selfhosting.ch' seems to have a ECC cert already, lets use ecc cert.
DOMAIN_CONF=/root/.acme.sh/selfhosting.ch_ecc/selfhosting.ch.conf
Le_Domain=selfhosting.ch
Le_Alt=*.selfhosting.ch
Le_Webroot=dns_nsd
Le_PreHook=
Le_PostHook=
Le_RenewHook=
Le_API=https://acme-v02.api.letsencrypt.org/directory
Le_Keylength=ec-256
Le_OrderFinalize=https://acme-v02.api.letsencrypt.org/acme/finalize/XXXXXXXXXXXXXXXXXXXXXX
Nsd_ZoneFile=/var/nsd/zones/master/selfhosting.ch
Nsd_Command=nsd-control reload selfhosting.ch
Le_DNSSleep=1
Le_LinkOrder=https://acme-v02.api.letsencrypt.org/acme/order/XXXXXXXXXXXXXXXXXXXXXX
Le_LinkCert=https://acme-v02.api.letsencrypt.org/acme/cert/0409XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Le_CertCreateTime=1682922340
Le_CertCreateTimeStr=2023-05-01T06:25:40Z
Le_NextRenewTimeStr=2023-06-29T06:25:40Z
Le_NextRenewTime=1688019940
Remove existing Cert
and remove the folder afterwards
d="selfhosting.ch"
acme.sh --remove -d ${d}
rm -rf /root/.acme.sh/${d}_ecc/
run it
# acme.sh --remove -d ${d}
[Mon May 1 11:56:10 CEST 2023] The domain 'selfhosting.ch' seems to have a ECC cert already, lets use ecc cert.
[Mon May 1 11:56:10 CEST 2023] selfhosting.ch is removed, the key and cert files are in /root/.acme.sh/selfhosting.ch_ecc
[Mon May 1 11:56:10 CEST 2023] You can remove them by yourself.
Get a Demo Cert
Let’s get a Demo Cert for selfhosting.ch, and *.selfhosting.ch as a wildcard domain. As we host our DNS on our own, and it does not have an API, we need to switch to an “manual mode” which is desribed here.
- https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode
- https://github.com/acmesh-official/acme.sh/issues/1029
This Certificate is valid for
- selfhosting.ch
- server.selfhosting.ch
- any-server.selfhosting.ch
but not for
- test.www.selfhosting.ch
d="selfhosting.ch"
acme.sh --issue -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
run it
# acme.sh --issue -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Mon May 1 12:05:10 CEST 2023] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:05:10 CEST 2023] Creating domain key
[Mon May 1 12:05:10 CEST 2023] The domain key is here: /root/.acme.sh/selfhosting.ch_ecc/selfhosting.ch.key
[Mon May 1 12:05:10 CEST 2023] Multi domain='DNS:selfhosting.ch,DNS:*.selfhosting.ch'
[Mon May 1 12:05:10 CEST 2023] Getting domain auth token for each domain
[Mon May 1 12:05:14 CEST 2023] Getting webroot for domain='selfhosting.ch'
[Mon May 1 12:05:14 CEST 2023] Getting webroot for domain='*.selfhosting.ch'
[Mon May 1 12:05:15 CEST 2023] selfhosting.ch is already verified, skip dns-01.
[Mon May 1 12:05:15 CEST 2023] *.selfhosting.ch is already verified, skip dns-01.
[Mon May 1 12:05:15 CEST 2023] Verify finished, start to sign.
[Mon May 1 12:05:15 CEST 2023] Lets finalize the order.
[Mon May 1 12:05:15 CEST 2023] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/XXXXXXXX'
[Mon May 1 12:05:16 CEST 2023] Downloading cert.
[Mon May 1 12:05:16 CEST 2023] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/XXXXXXXX'
[Mon May 1 12:05:17 CEST 2023] Cert success.
-----BEGIN CERTIFICATE-----
MIIE ...
... redacted ...
... /bVd
-----END CERTIFICATE-----
[Mon May 1 12:05:17 CEST 2023] Your cert is in: /root/.acme.sh/selfhosting.ch_ecc/selfhosting.ch.cer
[Mon May 1 12:05:17 CEST 2023] Your cert key is in: /root/.acme.sh/selfhosting.ch_ecc/selfhosting.ch.key
[Mon May 1 12:05:17 CEST 2023] The intermediate CA cert is in: /root/.acme.sh/selfhosting.ch_ecc/ca.cer
[Mon May 1 12:05:17 CEST 2023] And the full chain certs is there: /root/.acme.sh/selfhosting.ch_ecc/fullchain.cer
Renew selfhosting.ch
d="selfhosting.ch"
acme.sh --renew -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
we need to enforce it as it is newer than 30 Days. just add –force and try again
acme.sh --renew -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --force
run it
# acme.sh --renew -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --force
[Mon May 1 12:11:25 CEST 2023] The domain 'selfhosting.ch' seems to have a ECC cert already, lets use ecc cert.
[Mon May 1 12:11:25 CEST 2023] Renew: 'selfhosting.ch'
[Mon May 1 12:11:25 CEST 2023] Renew to Le_API=https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:11:26 CEST 2023] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:11:26 CEST 2023] Multi domain='DNS:selfhosting.ch,DNS:*.selfhosting.ch'
[Mon May 1 12:11:26 CEST 2023] Getting domain auth token for each domain
[Mon May 1 12:11:30 CEST 2023] Getting webroot for domain='selfhosting.ch'
[Mon May 1 12:11:30 CEST 2023] Getting webroot for domain='*.selfhosting.ch'
[Mon May 1 12:11:30 CEST 2023] selfhosting.ch is already verified, skip dns-01.
[Mon May 1 12:11:30 CEST 2023] *.selfhosting.ch is already verified, skip dns-01.
...
Wildcard Certificate “somedomain.ch”
another Domain needs manual update of the Zone File. don’t understand exactly why this happens to some domains, while it is not needed for another domain. must be, because i was already playing around with these domains a bit …
d="somedomain.ch"
acme.sh --issue -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
d="somedomain.ch"
acme.sh --issue -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Mon May 1 12:17:03 CEST 2023] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:17:03 CEST 2023] Multi domain='DNS:somedomain.ch,DNS:*.somedomain.ch'
[Mon May 1 12:17:03 CEST 2023] Getting domain auth token for each domain
[Mon May 1 12:17:07 CEST 2023] Getting webroot for domain='somedomain.ch'
[Mon May 1 12:17:07 CEST 2023] Getting webroot for domain='*.somedomain.ch'
[Mon May 1 12:17:07 CEST 2023] Add the following TXT record:
[Mon May 1 12:17:07 CEST 2023] Domain: '_acme-challenge.somedomain.ch'
[Mon May 1 12:17:07 CEST 2023] TXT value: 'ceLvXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
[Mon May 1 12:17:07 CEST 2023] Please be aware that you prepend _acme-challenge. before your domain
[Mon May 1 12:17:07 CEST 2023] so the resulting subdomain will be: _acme-challenge.somedomain.ch
[Mon May 1 12:17:07 CEST 2023] Please add the TXT records to the domains, and re-run with --renew.
[Mon May 1 12:17:07 CEST 2023] Please check log file for more details: /root/.acme.sh/acme.sh.log
add the txt Record to Zone and reload Zone. i’m dooing that with ansible, this depends on your infrastructure & mgmt tools
add TXT Record
# increase serial nr
echo "_acme-challenge IN TXT ceLvXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX >> somedomain.ch
nsd-control reload
Run again
acme.sh --renew -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
run it
# acme.sh --renew -d ${d} -d "*.${d}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Mon May 1 12:20:58 CEST 2023] The domain 'somedomain.ch' seems to have a ECC cert already, lets use ecc cert.
[Mon May 1 12:20:58 CEST 2023] Renew: 'somedomain.ch'
[Mon May 1 12:20:58 CEST 2023] Renew to Le_API=https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:20:59 CEST 2023] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Mon May 1 12:21:00 CEST 2023] Multi domain='DNS:somedomain.ch,DNS:*.somedomain.ch'
[Mon May 1 12:21:00 CEST 2023] Getting domain auth token for each domain
[Mon May 1 12:21:00 CEST 2023] somedomain.ch is already verified, skip dns-01.
[Mon May 1 12:21:00 CEST 2023] Verifying: *.somedomain.ch
[Mon May 1 12:21:02 CEST 2023] Pending, The CA is processing your order, please just wait. (1/30)
[Mon May 1 12:21:06 CEST 2023] Pending, The CA is processing your order, please just wait. (2/30)
[Mon May 1 12:21:09 CEST 2023] Pending, The CA is processing your order, please just wait. (3/30)
[Mon May 1 12:21:13 CEST 2023] Success
[Mon May 1 12:21:13 CEST 2023] Verify finished, start to sign.
[Mon May 1 12:21:13 CEST 2023] Lets finalize the order.
[Mon May 1 12:21:13 CEST 2023] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/XXXXXXXX'
[Mon May 1 12:21:15 CEST 2023] Downloading cert.
[Mon May 1 12:21:15 CEST 2023] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/XXXXXXXX'
[Mon May 1 12:21:16 CEST 2023] Cert success.
-----BEGIN CERTIFICATE-----
MIIE ...
... redacted ...
... /+00=
-----END CERTIFICATE-----
[Mon May 1 12:21:16 CEST 2023] Your cert is in: /root/.acme.sh/somedomain.ch_ecc/somedomain.ch.cer
[Mon May 1 12:21:16 CEST 2023] Your cert key is in: /root/.acme.sh/somedomain.ch_ecc/somedomain.ch.key
[Mon May 1 12:21:16 CEST 2023] The intermediate CA cert is in: /root/.acme.sh/somedomain.ch_ecc/ca.cer
[Mon May 1 12:21:16 CEST 2023] And the full chain certs is there: /root/.acme.sh/somedomain.ch_ecc/fullchain.cer
Cleanup
# remove last line with TXT from somedomain.ch
sed -i '/TXT/d' somedomain.ch
Final Word
and of course, we’re not gooing to renew the Certs by hand. I’ll write a Script which update the txt Record in the Zone File and reload the Zone automatically ;)
Patch for dns_nsd.sh
there is a script which handles cert for NSD The NLnet Labs Name Server Daemon (NSD). Unfortunately, the script does not increase the serial number and therefore failed. here is a little Patch for this:
Patch
cat << 'EOF' > /tmp/patch.txt
commit 172ba6544e3d30d324d55419e7edb133fbd937a3
Author: Daniel Stocker <mail@stoege.ch>
Date: Sun Jun 11 23:02:08 2023 +0200
fix increase serial
diff --git a/dns_nsd.sh b/dns_nsd.sh
index 0d29a48..777e9de 100644
--- a/dns_nsd.sh
+++ b/dns_nsd.sh
@@ -32,6 +32,13 @@ dns_nsd_add() {
echo "$fulldomain. $ttlvalue IN TXT \"$txtvalue\"" >>"$Nsd_ZoneFile"
_info "Added TXT record for $fulldomain"
+
+ # increase serial
+ export Nsd_OldSerial=$(cat "${Nsd_ZoneFile}" |awk '/serial/{ print $1}')
+ export Nsd_NewSerial=$(( Nsd_OldSerial + 1 ))
+ sed -i "s/$Nsd_OldSerial/$Nsd_NewSerial/" "$Nsd_ZoneFile"
+ _info "Increased Serial from ${Nsd_OldSerial} to ${Nsd_NewSerial} for $fulldomain"
+
_debug "Running $Nsd_Command"
if eval "$Nsd_Command"; then
_info "Successfully updated the zone"
EOF
Apply Patch
cd /root/.acme.sh/dnsapi
mv /tmp/patch.txt .
patch -p1 < patch.txt
Create a Patch
to create a patch, checkout the code. then fix the code.
git add .
git commit -m "your fix message"
git show > patch.txt
Any Comments ?
sha256: f08fef29b91821d12e9bd281a5408620e1da723edaa5c1ccb5b606e7ee1c0c7e