acme.sh

Page content

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.

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