Usage GuideDistribute Certificates

Distribute Certificates

How to distribute ACME certificates across multiple servers and systems

In some cases certificates obtained through ACME must be distributed to other systems or servers following their issuance. This need for distribution can arise due to various operational and logistical reasons. Below are some common scenarios that illustrate why certificate distribution is sometimes necessary:

  1. Multi Server Deployment: When a single certificate, such as a SAN certificate, is used to secure multiple domains across different servers, the certificate must be deployed on each of these servers to ensure secure communication. While reverse proxies are often used in such setups to centralize request handling, there are cases where direct certificate deployment on multiple servers remains necessary.

  2. Wildcard Certificates: Wildcard certificates, which secure all subdomains of a given domain, are sometimes required on multiple servers. For instance, a company may use a wildcard certificate to secure subdomains handled by different services or departments, each hosted on separate servers.

  3. Centralized Certificate Issuance: In some environments, certificates are issued from a dedicated server that handles ACME interactions. This may be due to security policies, limited internet access on production servers, or a desire to centralize certificate management.

  4. High-Availability Setups: In high-availability architectures, certificates may need to be synchronized across all nodes in a cluster to maintain consistent security and ensure seamless failover.

  5. Third-Party Integrations: Certificates may need to be provided to third-party services or devices, such as load balancers, proxies, or embedded systems, that cannot handle ACME client functionality directly.

In all these cases, an effective and secure method for certificate distribution is critical. This ensures that the certificates are deployed where they are needed without compromising their integrity or the security of the private keys.


While this guide primarily focuses on issuing and distributing certificates using lightweight, CLI-based ACME clients such as certbot and acme.sh, it is worth mentioning that more centralized and automated solutions exist. Tools like Certify The Web (Windows) and cert-manager (Kubernetes / OpenShift) offer integrated certificate management at scale. These are particularly useful when dealing with clusters of machines or containerized environments where certificates need to be maintained across many services.

Using ACME client hooks

An effective way to automate certificate distribution is through the use of hooks. Most ACME clients, such as Certbot, acme.sh, and simple-acme, support hooks to execute custom scripts post-issuance. The following examples should provide you with enough information to get started on utilizing these hooks yourself.

Certbot supports using the --deploy-hook parameter to trigger scripts that can distribute certificates to their intended destinations. Such a script could for example use SCP (Secure Copy Protocol) to distribute the certificate to a different server or device.

Consider the following example script deploy.sh:

deploy.sh
#!/bin/bash

# Copy certificate files to another host
scp "$RENEWED_LINEAGE"/*.pem otherserver.example.com:/location/

We can now use this script in the following way:

certbot certonly --server acme.networking4all.com/dv -d example.com --deploy-hook /path/to/deploy.sh

When Certbot finished receiving the new certificate it will set the $RENEWED_LINEAGE environment variable and execute deploy.sh. This variable merely contains the path to where the certificate is stored locally, usually /etc/letsencrypt/live/<common-name>. The script will in turn copy the relevant data (cert.pem, chain.pem, fullchain.pem, privkey.pem) to otherserver.example.com at /location/. The script will automatically be executed on any future renewal request unless it is manually removed from the configuration file which is usually found at /etc/letsencrypt/renewal/<common-name>.conf.

For a more dynamic approach the $RENEWED_DOMAINS environment variable is available to retrieve a list of space-delimited domains related to the order.

Notes

  1. The certbot command shown does not include EAB credentials since it assumes you already made a prior request to the ACME server, please refer to this section to see which command you would need to use when creating your first order on the ACME server and then simply add --deploy-hook /path/to/deploy.sh to it.
  2. The script provided above assumes SSH is able to login to the remote host without a password and SSH keys have been exchanged with the remote host previously.
  3. For a more indepth guide on how to use hooks with Certbot click here for the official documentation.
  4. The script provided above is intended solely as an example. It is essential that you review and adapt it to your environment, as deploying scripts without proper precautions may result in unforeseen issues.

Acme.sh supports various ways for you to distribute your certificates post issuance.

Native deploy method

This method can be used to trigger scripts that distribute certificates to their intended destinations. The deploy functionality from acme.sh comes with several pre-made deploy scripts including ssh, docker, NGINX, gitlab, and many more. As the --deploy-hook can not be ran in combination with your initial --issue request you will need to run the --deploy command after the certificate has initially been issued. After using it with --deploy once it will be saved and executed on every future renewal.

For this example we will be using the SSH deploy hook, which uses SCP (Secure Copy Protocol) to distribute the certificate to a different server or device, but we suggest you explore the available hooks to see if others might be a better fit for your situation. It is also possible to use your own custom deploy hook by simply placing the script in the acme.sh/deploy folder.

For the SSH hook we first need to set up some environment variables:

export DEPLOY_SSH_USER=<ssh-username-here>
export DEPLOY_SSH_SERVER="otherserver.example.com"
export DEPLOY_SSH_KEYFILE="/etc/ssl/example.com.key"
export DEPLOY_SSH_CERTFILE="/etc/ssl/example.com.pem"
export DEPLOY_SSH_FULLCHAIN="/etc/ssl/example.com_full.pem"
export DEPLOY_SSH_REMOTE_CMD="systemctl restart nginx"

We can now use this hook in the following way:

# Issue the certificate first
./acme.sh --server acme.networking4all.com/dv --issue -d example.com -w /var/www/example.com/

# Deploy the certificate utilizing the SSH hook
./acme.sh --deploy -d example.com --deploy-hook ssh

This hook will copy the relevant data (in this case the private key, certificate, and full chain certificate) to otherserver.example.com at /etc/ssl/ and then restart NGINX.

Post-hook and reloadcmd methods

An alternative to the deploy method is using the --post-hook and/or --reloadcmd hooks to run commands post issuance. These hooks can be used with your initial --issue command and will be automatically executed on future renewals. Note that the --post-hook is executed whether the certificate was issued or not so you can not rely on this hook alone to be certain that the certificate was issued when it is executed. The --reloadcmd hook however is only executed when the certificate was successfully issued.

Consider the following example script post-hook.sh:

post-hook.sh
#!/bin/bash

# Check if CERT_FULLCHAIN_PATH is set
if [ -z "$CERT_FULLCHAIN_PATH" ]; then
  echo "Error: it seems like we failed to issue a certificate for '$Le_Domain'." >&2
else
  # Proceed with copying the certificate
  scp "$CERT_FULLCHAIN_PATH" otherserver.example.com:/location
fi

We can now use this script in the following way:

./acme.sh --server acme.networking4all.com/dv --issue -d example.com -w /var/www/example.com/ --post-hook "/path/to/post-hook.sh"

When acme.sh finished receiving the new certificate it will set the $CERT_FULLCHAIN_PATH environment variable and execute post-hook.sh. This variable contains the full path to where the full chain certificate is stored locally. The script will in turn copy the certificate to otherserver.example.com at /location/.

Other relevant variables you might want to use in your script are CERT_PATH, CERT_KEY_PATH, CA_CERT_PATH, DOMAIN_PATH, Le_Domain.

Notes

  1. In these examples: SSH must be able to login to remote host without a password and SSH keys must have been exchanged with the remote host.
  2. For a more indepth guide on how to use hooks with acme.sh check the following links for the official documentation: hooks and deploy hooks.
  3. The demonstration provided above is intended solely as an example. It is essential that you review and adapt it to your environment, as deploying scripts without proper precautions may result in unforeseen issues.

Simple-acme comes with various store plugins such as Azure Key Vault and the Windows Certificate Store which might already be compatible with your current distribution methods.

Simple-acme also supports using the --installation script --script <script> parameter to trigger scripts that can distribute certificates to their intended destinations. Simple-Acme comes with several pre-made (example) deploy scripts which can be found here or at your simple-acme installation directory under Scripts. This includes examples for Active Directory, Azure, Exchange, NTDS, WinRM, and Windows Admin Center (for IIS bindings documentation can be found here). You can however provide your own .bat, .exe, .sh, or .ps1 as a parameter to run custom scripts or executables.

For example, using the pfxfile store plugin like so: --store pfxfile --pfxfilepath 'C:\local\cert\path\' --pfxfilename pfxfile --installation script --script C:\path\to\pfxfile-example.ps1 --scriptparameters "{StorePath}"

Consider this script pfxfile-example.ps1:

pfxfile-example.ps1
param (
    [string]$StorePath # {StorePath} passed by simple-acme
)

$RemotePath = 'C:\remote\path\'
$Session = New-PSSession -ComputerName "ExampleServer" -Credential "Example\User"

Copy-Item $StorePath -Destination $RemotePath -ToSession $Session

$ScriptBlock = {
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.Import(($RemotePath + 'pfxfile.pfx'))

    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "AcmeTest", "LocalMachine"
    $store.Open("ReadWrite")
    $store.Add($cert)
    $store.Close()
}

Invoke-Command -Session $Session -ScriptBlock $ScriptBlock

This script copies a local PFX certificate which was issued through Networking4all using simple-acme to a remote server and then imports it into the local certificate store on the remote server.

Notes

  1. The script provided above assumes the remote (Windows) server supports PowerShell and has PowerShell remoting enabled.
  2. The script provided above assumes Store.PfxFile.DefaultPassword has been set and therefore does not provide the --pfxpassword parameter.
  3. For a more indepth guide on how to use installation scripts with simple-acme check the following links for the official documentation: installation plugins and store plugins.
  4. More information on .NET X509Certificate2 objects.
  5. Some scripts provided by simple-acme, and all the scripts provided by us are merely for demonstration purposes. It is essential that you review and adapt them to your environment, as deploying scripts without proper precautions may result in unforeseen issues.