Making Letsencrypt Certs with acme.sh
Letsencrypt is a free, automated, and open Certificate Authority to generate all your PKI certificates. Instead of installing a development environment like other Letsencrypt methods, this article describes a single bash script called acme.sh which uses the acme protocol to issue and verify certificates. It can be installed and operated without being root. Here is how to get Zimbra up and running with Letsencrypt certificates.
Summary of Steps
- Install and configure acme.sh
- Issue certficate
- Deploy certficate
- Verify automatic renewals work
Step 1: Installation of acme.sh
Zimbra uses a program called zmcertmgr that can verify a certificate and install them. It needs to run as the zimbra user and letsencrypt certificates will be renewed every 60 days automatically. They expire in 90 days if not renewed. The first problem is that /opt/zimbra is owned by root and we want to install this as the zimbra user. We install acme.sh like this:
% su - # mkdir /opt/zimbra/.acme.sh # chown zimbra:zimbra /opt/zimbra/.acme.sh # su - zimbra % wget -O - https://get.acme.sh | sh % ls /opt/zimbra/.acme.sh account.conf acme.sh acme.sh.env deploy dnsapi http.header notify
At this point we will have /opt/zimbra/.acme.sh populated and need to make a few changes to the defaults to work with zimbra.
% cd .acme.sh % ./acme.sh --set-default-ca --server letsencrypt % ./acme.sh --set-default-chain --preferred-chain ISRG --server letsencrypt
Next we need to choose a dns provider as acme.sh will create and delete DNS text records as part of the verification process for your certificate.
% ls dnsapi README.md dns_dgon.sh dns_hexonet.sh dns_mythic_beasts.sh dns_rcode0.sh dns_1984hosting.sh dns_dnsexit.sh dns_hostingde.sh dns_namecheap.sh dns_regru.sh dns_acmedns.sh dns_dnshome.sh dns_huaweicloud.sh dns_namecom.sh dns_scaleway.sh dns_acmeproxy.sh dns_dnsimple.sh dns_infoblox.sh dns_namesilo.sh dns_schlundtech.sh dns_active24.sh dns_dnsservices.sh dns_infomaniak.sh dns_nanelo.sh dns_selectel.sh dns_ad.sh dns_do.sh dns_internetbs.sh dns_nederhost.sh dns_selfhost.sh dns_ali.sh dns_doapi.sh dns_inwx.sh dns_neodigit.sh dns_servercow.sh dns_anx.sh dns_domeneshop.sh dns_ionos.sh dns_netcup.sh dns_simply.sh dns_artfiles.sh dns_dp.sh dns_ipv64.sh dns_netlify.sh dns_tele3.sh dns_arvan.sh dns_dpi.sh dns_ispconfig.sh dns_nic.sh dns_tencent.sh dns_aurora.sh dns_dreamhost.sh dns_jd.sh dns_njalla.sh dns_transip.sh dns_autodns.sh dns_duckdns.sh dns_joker.sh dns_nm.sh dns_udr.sh dns_aws.sh dns_durabledns.sh dns_kappernet.sh dns_nsd.sh dns_ultra.sh dns_azion.sh dns_dyn.sh dns_kas.sh dns_nsone.sh dns_unoeuro.sh dns_azure.sh dns_dynu.sh dns_kinghost.sh dns_nsupdate.sh dns_variomedia.sh dns_bookmyname.sh dns_dynv6.sh dns_knot.sh dns_nw.sh dns_veesp.sh dns_bunny.sh dns_easydns.sh dns_la.sh dns_oci.sh dns_vercel.sh dns_cf.sh dns_edgedns.sh dns_leaseweb.sh dns_one.sh dns_vscale.sh dns_clouddns.sh dns_euserv.sh dns_lexicon.sh dns_online.sh dns_vultr.sh dns_cloudns.sh dns_exoscale.sh dns_linode.sh dns_openprovider.sh dns_websupport.sh dns_cn.sh dns_fornex.sh dns_linode_v4.sh dns_openstack.sh dns_world4you.sh dns_conoha.sh dns_freedns.sh dns_loopia.sh dns_opnsense.sh dns_yandex.sh dns_constellix.sh dns_gandi_livedns.sh dns_lua.sh dns_ovh.sh dns_yc.sh dns_cpanel.sh dns_gcloud.sh dns_maradns.sh dns_pdns.sh dns_zilore.sh dns_curanet.sh dns_gcore.sh dns_me.sh dns_pleskxml.sh dns_zone.sh dns_cyon.sh dns_gd.sh dns_miab.sh dns_pointhq.sh dns_zonomi.sh dns_da.sh dns_geoscaling.sh dns_misaka.sh dns_porkbun.sh dns_ddnss.sh dns_googledomains.sh dns_myapi.sh dns_rackcorp.sh dns_desec.sh dns_he.sh dns_mydevil.sh dns_rackspace.sh dns_df.sh dns_hetzner.sh dns_mydnsjp.sh dns_rage4.sh
These are text files and inside they list what shell variables will be needed to use them.
% vi account.conf
For example, dns_gd.sh is goddady and it requires that following be added to account.conf
Those values can be found from the godaddy account. Similarily clouldflare dns_cf.sh will work with
CF_Token="xxxx" CF_Account_ID="xxxx" CF_Zone_ID="xxxx"
Once account.conf has these values for your dns hosting provider, you will be able to issue certificates. The last step is to do the following:
% wget 'https://raw.githubusercontent.com/JimDunphy/acme.sh/master/deploy/zimbra.sh' -O /opt/zimbra/.acme.sh/deploy/zimbra.sh
This script will be called at deploy time and will do zimbra specific calls. It will handle anything required by zimbra to have a working certificate. It will validate the certificate and install it. Finally if everything worked, it will restart zimbra so the new certificate can be used.
Step 2 - Issue Certificate
acme.sh version v3.0.6 and above has switched the default certificate type to ec-256 from rsa certificates. The problem is that zmcertmgr still requires rsa certificates which is specified with the option --keylength 2048.
% acme.sh --issue --keylength 2048 --dns dns_cf -d mail.example.com
If everything worked, you will have a valid certificate that looks like this.
-----END CERTIFICATE----- [Fri Oct 20 14:44:02 PDT 2023] Your cert is in: /opt/zimbra/.acme.sh/mail.example.com/mail.example.com.cer [Fri Oct 20 14:44:02 PDT 2023] Your cert key is in: /opt/zimbra/.acme.sh/mail.example.com/mail.example.com.key [Fri Oct 20 14:44:02 PDT 2023] The intermediate CA cert is in: /opt/zimbra/.acme.sh/mail.example.com/ca.cer [Fri Oct 20 14:44:02 PDT 2023] And the full chain certs is there: /opt/zimbra/.acme.sh/mail.example.com/fullchain.cer
Step 3 - Deploy Certificate
We have a valid certificate but nothing has been done to zimbra yet. It has not been installed nor has the certificate been validated. The next step accomplishes this.
$ ./acme.sh --deploy --deploy-hook zimbra --d mail.example.com
This step will validate the certificate. If that checks out, it will copy the certificate to various locations under the /opt/zimbra tree for postfix/ldap/nginx/mailboxd. It will then restart zimbra so that the new certificate is active.
Step 4 - Automatic Renewal
Letsencrypt certificates expire every 90 days. When you installed acme.sh, it created the following cron job for zimbra:
1 10 * * * "/opt/zimbra/.acme.sh"/acme.sh --cron --home "/opt/zimbra/.acme.sh" > /dev/null
Every night it will check to see if it can be renewed. In 60 days, it can be renewed. What this will do is the following:
- 1) acme.sh --issue --keylength 2048 --dns dns_cf -d mail.example.com
- 2) acme.sh --deploy --deploy-hook zimbra --d mail.example.com
If you wish to test it out and renew sooner. Do this:
% "/opt/zimbra/.acme.sh"/acme.sh --cron --force --home "/opt/zimbra/.acme.sh"
If everything goes right, you will have a new certificate and zimbra will have been restarted. If there was a problem in the validation, it will stop and nothing in zimbra will have been changed.
Everything is self contained in /opt/zimbra/.acme.sh and you can copy or backup this folder or move it from machine to machine. Even better, if you created a certificate like this:
% ./acme.sh --issue --keylength 2048 --dns dns_cf -d mail.example.com
The folder /opt/zimbra/.acme.sh/mail.example.com contains everything required for the deploy. You could copy the deploy directory to various machines and then install them like this:
% ./acme.sh --deploy --deploy-hook zimbra -d mail.example.com
More articles written by me, https://wiki.zimbra.com/wiki/JDunphy-Notes