Difference between revisions of "Let’s Encrypt"

From wiki
m (fix command)
(Wording)
 
(17 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{Debian}}
+
This guide will show you how to get free certificates using [https://letsencrypt.org/ Let’s Encrypt].
{{WIP}}
 
  
This guide will show you how to get free certificates using [https://letsencrypt.org/ Let’s Encrypt].
+
While Let’s Encrypt provide scripts that are able to edit your webserver configuration files, I don’t trust anyone enough to do that. Let’s Encrypt scripts will only be used to create and renew certificates.
  
While Let’s Encrypt provide scripts that are able to edit your webserver configuration files, I don’t trust anyone enough to do that. Let’s Encrypt scripts will only be used to create and renew certificates,
+
{{Warning}} Let’s Encrypt is still a very young project. While certificate creation is working pretty well, scripts are still changing rapidly. Stay tuned and be prepared to update your configuration.
{{warning|Let’s Encrypt is still a very young project. While certificate creation is working pretty well, scripts are still changing rapidly. Stay tuned and be prepared to update your configuration.}}
 
  
 
== Prerequisite ==
 
== Prerequisite ==
 +
This guide assume that you have a [[Nginx|nginx]] server running and listening on port 80.
  
This guide assume that you have an [[Nginx]] server running and listening on port 80.
+
The certificates can be then used for other purposes, like an email server. In that case, nginx is only used for the renewal process.
 
 
The certificates can be then used for other purposes, like email server. Nginx is only used for the renewal process.
 
  
 
== Installation ==
 
== Installation ==
Line 18: Line 15:
  
 
<syntaxhighlight lang="console">
 
<syntaxhighlight lang="console">
# apt install letsencrypt/jessie-backports
+
$ sudo apt install certbot/jessie-backports
 +
</syntaxhighlight>Next disable Cerbot auto-renewal script as it will interfere with the one you will create bellow.<syntaxhighlight lang="console">
 +
$ sudo systemctl stop certbot.timer
 +
$ sudo systemctl mask certbot.timer
 +
Created symlink from /etc/systemd/system/certbot.timer to /dev/null.
 +
$ sudo systemctl mask certbot.service 
 +
Created symlink from /etc/systemd/system/certbot.service to /dev/null.
 +
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 27: Line 31:
 
Although not mandatory, it is recommended to provide an email when registering your account. Make sure you enter it right as Let’s Encrypt will not validate it.
 
Although not mandatory, it is recommended to provide an email when registering your account. Make sure you enter it right as Let’s Encrypt will not validate it.
 
<syntaxhighlight lang="console">
 
<syntaxhighlight lang="console">
# letsencrypt certonly --standalone -n --agree-tos --email youremail@example.org
+
$ sudo certbot register -n --agree-tos --email youremail@example.org
Missing command line flag or config entry for this setting:
 
Please enter in your domain name(s) (comma and/or space separated)
 
 
 
(You can set this with the --domains flag)
 
  
 
IMPORTANT NOTES:
 
IMPORTANT NOTES:
 
  - If you lose your account credentials, you can recover through
 
  - If you lose your account credentials, you can recover through
   e-mails sent to logs-letsencrypt-anjie@meurisse.org.
+
   e-mails sent to youremail@example.org.
  - Your account credentials have been saved in your Let's Encrypt
+
  - Your account credentials have been saved in your Certbot
 
   configuration directory at /etc/letsencrypt. You should make a
 
   configuration directory at /etc/letsencrypt. You should make a
 
   secure backup of this folder now. This configuration directory will
 
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
+
   also contain certificates and private keys obtained by Cerbot so
   Encrypt so making regular backups of this folder is ideal.
+
   making regular backups of this folder is ideal.
 
</syntaxhighlight>
 
</syntaxhighlight>
The error about missing parameter is normal. You should be looking at the ''IMPORTANT NOTES'' section in the output.
 
 
 
=== Nginx ===
 
=== Nginx ===
  
 
* First create folder <code>/var/www/acme-challenge</code>
 
* First create folder <code>/var/www/acme-challenge</code>
 
<syntaxhighlight lang="console">
 
<syntaxhighlight lang="console">
# mkdir -p /var/www/acme-challenge/.well-known/acme-challenge
+
$ sudo mkdir -p /var/www/acme-challenge/.well-known/acme-challenge
# chmod -R 750 /var/www/acme-challenge
+
$ sudo chmod -R 750 /var/www/acme-challenge
# chown -R root:www-data /var/www/acme-challenge
+
$ sudo chown -R root:www-data /var/www/acme-challenge
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 62: Line 60:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Renewal Script ===
+
=== Manage certificates ===
  
Let’s Encrypt delivers certificates that are valid for 90 days. It make automatic renewal an important part of the setup. They also have a [https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769 limit of 5 certificates per week per domain].
+
Let’s Encrypt delivers certificates that are valid for 90 days. It make automatic renewal an important part of the setup. They also have a [https://letsencrypt.org/docs/rate-limits/ limit of 20 certificates per week per domain].
  
In order to avoid blocking your domain (in case you need to create a new certificate), the following script will renew at most one certificate per run and will run every two days.
+
In order to avoid blocking your domain (in case you need to create a new certificate), the following script will renew at most one certificate per run.
  
 
Certificates are renewed 30d before expiry. Additionally, if a certificate is close to expiry (20 days) a warning will be displayed with details.
 
Certificates are renewed 30d before expiry. Additionally, if a certificate is close to expiry (20 days) a warning will be displayed with details.
  
* Save the following file as <code>/usr/local/sbin/renew_certificates</code> and make it executable
+
* Save the following file as <code>/usr/local/sbin/certmanage</code> and make it executable
 
<syntaxhighlight lang="python">
 
<syntaxhighlight lang="python">
 
#! /usr/bin/env python
 
#! /usr/bin/env python
  
 
from datetime import timedelta, date
 
from datetime import timedelta, date
 +
import json
 
import time
 
import time
 +
import os
 
import subprocess
 
import subprocess
 +
import sys
 
import OpenSSL
 
import OpenSSL
 
import pyrfc3339
 
import pyrfc3339
  
# Configure your certificates here
+
RENEW_CMD = '/usr/bin/certbot'
# Each item in the list represent one certificate
+
RENEW_ARGS = ['certonly', '--non-interactive', '-a', 'webroot', '--webroot-path', '/var/www/acme-challenge/', '--renew-by-default', '--expand']
# If domains list contains multiple domains, the first one is used as filename for the certificate
 
config = [{
 
    'domains': ['www.example.com', 'example.com'],
 
    'reload': [['service', 'nginx', 'reload']]
 
}, {
 
    'domains': ['imap.example.rocks', 'smtp.example.com'],
 
    'reload': [['service', 'dovecot', 'reload'], ['service', 'exim4', 'reload']]
 
}]
 
 
 
RENEW_CMD = '/usr/bin/letsencrypt'
 
RENEW_ARGS = ['certonly', '--non-interactive', '-a', 'webroot', '--webroot-path', '/var/www/acme-challenge/', '--keep-until-expiring', '--expand']
 
 
LIVE_FOLDER = '/etc/letsencrypt/live/'
 
LIVE_FOLDER = '/etc/letsencrypt/live/'
 
CERT_FILE = '/cert.pem'
 
CERT_FILE = '/cert.pem'
 +
FORCE_RENEW_FILE = '/force_renew'
 
RENEW_DATE = timedelta(days=30)
 
RENEW_DATE = timedelta(days=30)
 
ALERT_DATE = timedelta(days=20)
 
ALERT_DATE = timedelta(days=20)
  
def get_date(cert_path):
+
def read_config():
     with open(cert_path) as f:
+
     domains = []
        x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
+
    scriptPath = os.path.dirname(os.path.realpath(__file__))
     timestamp = OpenSSL.crypto.X509.get_notAfter(x509)
+
     cfgFolder = os.path.join(scriptPath, '../etc/certmanage/')
     reformatted_timestamp = [timestamp[0:4], "-", timestamp[4:6], "-",
+
     files = [f for f in os.listdir(cfgFolder) if os.path.isfile(os.path.join(cfgFolder, f)) and os.path.splitext(f)[1] == '.json']
                            timestamp[6:8], "T", timestamp[8:10], ":",
+
    for f in files:
                            timestamp[10:12], ":", timestamp[12:]]
+
        with open(os.path.join(cfgFolder, f), 'r') as conf:
    return pyrfc3339.parse("".join(reformatted_timestamp)).date()
+
            domains += json.load(conf)
  
def get_next_renew():
+
    return domains
 +
 
 +
def get_date(cert_path, force_renew_path):
 +
    if os.path.isfile(force_renew_path):
 +
        return date.min
 +
    elif os.path.isfile(cert_path):
 +
        with open(cert_path) as f:
 +
            x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
 +
        timestamp = OpenSSL.crypto.X509.get_notAfter(x509)
 +
        reformatted_timestamp = [timestamp[0:4], "-", timestamp[4:6], "-",
 +
                                timestamp[6:8], "T", timestamp[8:10], ":",
 +
                                timestamp[10:12], ":", timestamp[12:]]
 +
        return pyrfc3339.parse("".join(reformatted_timestamp)).date()
 +
    else:
 +
        return date.min
 +
 
 +
def get_next_renew(domains):
 
     mindate = date.max
 
     mindate = date.max
 
     cert = None
 
     cert = None
     for conf in config:
+
     for conf in domains:
 
         main_name = conf.get('domains')[0]
 
         main_name = conf.get('domains')[0]
 
         filename = LIVE_FOLDER + main_name + CERT_FILE
 
         filename = LIVE_FOLDER + main_name + CERT_FILE
         expiration = get_date(filename)
+
        force_renew_path = LIVE_FOLDER + main_name + FORCE_RENEW_FILE
 +
         expiration = get_date(filename, force_renew_path)
 
         if expiration < mindate:
 
         if expiration < mindate:
 
             mindate = expiration
 
             mindate = expiration
 
             cert = {
 
             cert = {
 
                 'file': filename,
 
                 'file': filename,
 +
                'force_renew_path': force_renew_path,
 +
                'real_path': os.path.realpath(filename),
 
                 'domains': conf.get('domains'),
 
                 'domains': conf.get('domains'),
 
                 'reload': conf.get('reload'),
 
                 'reload': conf.get('reload'),
Line 140: Line 150:
  
 
def after_cert(cert):
 
def after_cert(cert):
 +
    if os.path.isfile(cert.get('force_renew_path')):
 +
        os.remove(cert.get('force_renew_path'))
 +
 
     if 'reload' in cert:
 
     if 'reload' in cert:
 
         print 'Restarting services:'
 
         print 'Restarting services:'
Line 147: Line 160:
  
 
if __name__ == "__main__":
 
if __name__ == "__main__":
     next = get_next_renew()
+
    domains = read_config()
 +
     next = get_next_renew(domains)
 
     if should_renew(next):
 
     if should_renew(next):
         print 'Renewing certificate for ' + ', '.join(next.get('domains')) + ' that will expire on ' + next.get('expiration').isoformat() + '\n\n'
+
         print 'Renewing certificate for ' + ', '.join(next.get('domains')) + ' that will expire on ' + next.get('expiration').isoformat() + '\n'
 +
        sys.stdout.flush()
 
         renew(next)
 
         renew(next)
  
         # Waiting OCSP responses
+
         was_renewed = os.path.realpath(next.get('file')) != next.get('real_path')
        # https://community.letsencrypt.org/t/ocsp-server-sometimes-has-malformed-response-of-5-bytes-or-unauthorized/10568/10
 
        time.sleep(5)
 
       
 
        was_renewed = get_date(next.get('file')) != next.get('expiration')
 
 
         if was_renewed:
 
         if was_renewed:
 +
            # Waiting OCSP responses
 +
            # https://community.letsencrypt.org/t/ocsp-server-sometimes-has-malformed-response-of-5-bytes-or-unauthorized/10568/10
 +
            time.sleep(5)
 +
 
             after_cert(next)
 
             after_cert(next)
  
         next = get_next_renew()
+
         next = get_next_renew(domains)
 
         if should_alert(next):
 
         if should_alert(next):
 
             print """
 
             print """
Line 176: Line 191:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Don’t forget to edit the config on top of the file. If you don't have certificates from Let’s Encrypt yet, keep an empty array.
+
* Create folder the config folder<syntaxhighlight lang="console">
 
+
$ mkdir /usr/local/etc/certmanage
 +
$ echo [] | sudo tee /usr/local/etc/certmanage/main.json > /dev/null
 +
</syntaxhighlight>
 
* You can then run it automatically during the night. Add this to the file <code>/etc/crontab</code>
 
* You can then run it automatically during the night. Add this to the file <code>/etc/crontab</code>
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
12 4    */2 * * root    /usr/local/sbin/renew_certificates
+
12 4    * * * root    /usr/local/sbin/certmanage
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
== Create Certificate ==
 +
 +
=== Web Server ===
 +
 +
Let’s encrypt require a website to work on port 80 before delivering a certificate for your domain.
 +
 +
If you are creating a certificate for a website, you simply need to first create the configuration for [[Nginx]].
 +
 +
If your certificate is not intended for the web (SMTP, IMAP, Jabber…), you can create a dedicated config in Nginx
 +
<syntaxhighlight lang="console">
 +
$ touch /etc/nginx/sites-available/noweb
 +
$ ln -s /etc/nginx/sites-available/noweb /etc/nginx/sites-enabled
 +
</syntaxhighlight>
 +
 +
Edit the file as bellow. There should be one <code>server_name</code> per domain in your certificates. Note that this configuration need to stay in place after you got the certificate as certificates are renewed automatically every 60 day.
 +
<syntaxhighlight lang="nginx">
 +
server {
 +
    include snippets/listen-http.conf;
 +
    server_name smtp.example.org;
 +
    server_name imap.example.org;
 +
 +
    include snippets/acme-challenge.conf;
 +
    deny all;
 +
}
 +
</syntaxhighlight>
 +
 +
Make sure you reload Nginx config
 +
<syntaxhighlight lang="console">
 +
$ service nginx reload
 +
</syntaxhighlight>
 +
 +
=== Certificate ===
 +
 +
Now, you just need to add your certificate in <code>/usr/local/etc/certmanage</code>. All file whose name end in <code>.json</code> can contain certificate configuration. You can either use <code>main.json</code> or create a new file.
 +
 +
Each certificate is represented y a dict with two keys:
 +
;<code>domains</code>
 +
:The list of domains to be included in the certificate. The order is important as the first is domain is used for certificate file name.
 +
;<code>reload</code>
 +
:List of commands to be executed after certificate creation. Each command is represented by a list: first item is the command, next ones are arguments.
 +
 +
Here are some examples:
 +
<syntaxhighlight lang="python">
 +
[{
 +
    "domains": ["www.example.org", "example.org"],
 +
    "reload": [["/bin/systemctl", "reload", "nginx.service"]]
 +
}, {
 +
    "domains": ["imap.example.org"]
 +
}, {
 +
    "domains": ["smtp.example.org"],
 +
    "reload": [["/usr/local/bin/myCustomCommand"], ["/bin/systemctl", "reload", "exim.service"]]
 +
}]
 +
</syntaxhighlight>
 +
 +
And finally just get you certificate
 +
 +
{{Let’s Encrypt/New Cert Command|domain = imap.example.org|command = service dovecot reload
 +
[ ok ] Reloading IMAP/POP3 mail server: dovecot}}Note that the command will create only one certificate per execution. If you added multiple of them, you need to run the command multiple times.
 +
 +
== Revoke Certificate ==
 +
If it has been possible for someone to access the private key of one of your certificate, it is strongly recommended to revoke it.
 +
 +
=== Identify the Certificate to Revoke ===
 +
The first thing to do if to know which certificate you want to revoke.<syntaxhighlight lang="console">
 +
$ ls -l /etc/letsencrypt/live/www.example.org/cert.pem
 +
lrwxrwxrwx 1 root root 43 Jan 01 00:00 /etc/letsencrypt/live/www.example.org/cert.pem -> ../../archive/www.example.org/cert7.pem
 +
</syntaxhighlight>This will give you the path of the currently active certificate in the <code>/etc/letsencrypt/archive/</code> folder.
 +
 +
=== Renew the Certificate ===
 +
If the certificate you want to revoke is active, you need to renew it before revoking it{{Let’s Encrypt/New Cert Command|domain = www.example.org|command = /bin/systemctl reload nginx.service|beforeCommand = $ sudo touch /etc/letsencrypt/live/www.example.org/force_renew}}
 +
 +
=== Revoke the Old Certificate ===
 +
Use the path found at step 1<syntaxhighlight lang="console">
 +
$ sudo certbot revoke --cert-path /etc/letsencrypt/archive/www.example.org/cert7.pem
 +
</syntaxhighlight>The command doesn't output anything in case of success.
 
[[Category:Debian Release]]
 
[[Category:Debian Release]]
 
[[Category:Linux Server]]
 
[[Category:Linux Server]]
 +
[[Category:Web Server]]

Latest revision as of 06:02, 5 April 2017

This guide will show you how to get free certificates using Let’s Encrypt.

While Let’s Encrypt provide scripts that are able to edit your webserver configuration files, I don’t trust anyone enough to do that. Let’s Encrypt scripts will only be used to create and renew certificates.


Warning Warning: Let’s Encrypt is still a very young project. While certificate creation is working pretty well, scripts are still changing rapidly. Stay tuned and be prepared to update your configuration.

Prerequisite

This guide assume that you have a nginx server running and listening on port 80.

The certificates can be then used for other purposes, like an email server. In that case, nginx is only used for the renewal process.

Installation

If you are using Debian Jessie, you will need to configure jessie-backports source for the following command to work.

$ sudo apt install certbot/jessie-backports

Next disable Cerbot auto-renewal script as it will interfere with the one you will create bellow.

$ sudo systemctl stop certbot.timer
$ sudo systemctl mask certbot.timer
Created symlink from /etc/systemd/system/certbot.timer to /dev/null.
$ sudo systemctl mask certbot.service  
Created symlink from /etc/systemd/system/certbot.service to /dev/null.

Configuration

Register Account

Although not mandatory, it is recommended to provide an email when registering your account. Make sure you enter it right as Let’s Encrypt will not validate it.

$ sudo certbot register -n --agree-tos --email youremail@example.org

IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to youremail@example.org.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Cerbot so
   making regular backups of this folder is ideal.

Nginx

  • First create folder /var/www/acme-challenge
$ sudo mkdir -p /var/www/acme-challenge/.well-known/acme-challenge
$ sudo chmod -R 750 /var/www/acme-challenge
$ sudo chown -R root:www-data /var/www/acme-challenge
  • Create file /etc/nginx/snippets/acme-challenge.conf
location ^~ /.well-known/acme-challenge/ {
    root /var/www/acme-challenge;
    auth_basic off;
    allow all;
}

Manage certificates

Let’s Encrypt delivers certificates that are valid for 90 days. It make automatic renewal an important part of the setup. They also have a limit of 20 certificates per week per domain.

In order to avoid blocking your domain (in case you need to create a new certificate), the following script will renew at most one certificate per run.

Certificates are renewed 30d before expiry. Additionally, if a certificate is close to expiry (20 days) a warning will be displayed with details.

  • Save the following file as /usr/local/sbin/certmanage and make it executable
#! /usr/bin/env python

from datetime import timedelta, date
import json
import time
import os
import subprocess
import sys
import OpenSSL
import pyrfc3339

RENEW_CMD = '/usr/bin/certbot'
RENEW_ARGS = ['certonly', '--non-interactive', '-a', 'webroot', '--webroot-path', '/var/www/acme-challenge/', '--renew-by-default', '--expand']
LIVE_FOLDER = '/etc/letsencrypt/live/'
CERT_FILE = '/cert.pem'
FORCE_RENEW_FILE = '/force_renew'
RENEW_DATE = timedelta(days=30)
ALERT_DATE = timedelta(days=20)

def read_config():
    domains = []
    scriptPath = os.path.dirname(os.path.realpath(__file__))
    cfgFolder = os.path.join(scriptPath, '../etc/certmanage/')
    files = [f for f in os.listdir(cfgFolder) if os.path.isfile(os.path.join(cfgFolder, f)) and os.path.splitext(f)[1] == '.json']
    for f in files:
        with open(os.path.join(cfgFolder, f), 'r') as conf:
            domains += json.load(conf)

    return domains

def get_date(cert_path, force_renew_path):
    if os.path.isfile(force_renew_path):
        return date.min
    elif os.path.isfile(cert_path):
        with open(cert_path) as f:
            x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
        timestamp = OpenSSL.crypto.X509.get_notAfter(x509)
        reformatted_timestamp = [timestamp[0:4], "-", timestamp[4:6], "-",
                                 timestamp[6:8], "T", timestamp[8:10], ":",
                                 timestamp[10:12], ":", timestamp[12:]]
        return pyrfc3339.parse("".join(reformatted_timestamp)).date()
    else:
        return date.min

def get_next_renew(domains):
    mindate = date.max
    cert = None
    for conf in domains:
        main_name = conf.get('domains')[0]
        filename = LIVE_FOLDER + main_name + CERT_FILE
        force_renew_path = LIVE_FOLDER + main_name + FORCE_RENEW_FILE
        expiration = get_date(filename, force_renew_path)
        if expiration < mindate:
            mindate = expiration
            cert = {
                'file': filename,
                'force_renew_path': force_renew_path,
                'real_path': os.path.realpath(filename),
                'domains': conf.get('domains'),
                'reload': conf.get('reload'),
                'expiration': expiration
            }
    return cert

def should_renew(cert):
    now = date.today()
    return cert.get('expiration') - now < RENEW_DATE

def should_alert(cert):
    now = date.today()
    return cert.get('expiration') - now < ALERT_DATE

def renew(cert):
    cmd = renew_cmd(cert)
    subprocess.call(cmd)

def renew_cmd(cert):
    return [RENEW_CMD] + RENEW_ARGS + [arg for domain in cert.get('domains') for arg in ['-d', domain]]

def after_cert(cert):
    if os.path.isfile(cert.get('force_renew_path')):
        os.remove(cert.get('force_renew_path'))

    if 'reload' in cert:
        print 'Restarting services:'
        for cmd in cert.get('reload'):
            print ' '.join(cmd)
            subprocess.call(cmd)

if __name__ == "__main__":
    domains = read_config()
    next = get_next_renew(domains)
    if should_renew(next):
        print 'Renewing certificate for ' + ', '.join(next.get('domains')) + ' that will expire on ' + next.get('expiration').isoformat() + '\n'
        sys.stdout.flush()
        renew(next)

        was_renewed = os.path.realpath(next.get('file')) != next.get('real_path')
        if was_renewed:
            # Waiting OCSP responses
            # https://community.letsencrypt.org/t/ocsp-server-sometimes-has-malformed-response-of-5-bytes-or-unauthorized/10568/10
            time.sleep(5)

            after_cert(next)

        next = get_next_renew(domains)
        if should_alert(next):
            print """
=============================================================
                          WARNING
=============================================================

Your certificate for %s will expire on %s

Certificate should have been renewed already. Maybe there is a issue with renewal process.

Renew command
%s
""" % (', '.join(next.get('domains')), next.get('expiration').isoformat(), ' '.join(renew_cmd(next)))
  • Create folder the config folder
    $ mkdir /usr/local/etc/certmanage
    $ echo [] | sudo tee /usr/local/etc/certmanage/main.json > /dev/null
    
  • You can then run it automatically during the night. Add this to the file /etc/crontab
12 4    * * * root     /usr/local/sbin/certmanage

Create Certificate

Web Server

Let’s encrypt require a website to work on port 80 before delivering a certificate for your domain.

If you are creating a certificate for a website, you simply need to first create the configuration for Nginx.

If your certificate is not intended for the web (SMTP, IMAP, Jabber…), you can create a dedicated config in Nginx

$ touch /etc/nginx/sites-available/noweb
$ ln -s /etc/nginx/sites-available/noweb /etc/nginx/sites-enabled

Edit the file as bellow. There should be one server_name per domain in your certificates. Note that this configuration need to stay in place after you got the certificate as certificates are renewed automatically every 60 day.

server {
    include snippets/listen-http.conf;
    server_name smtp.example.org;
    server_name imap.example.org;

    include snippets/acme-challenge.conf;
    deny all;
}

Make sure you reload Nginx config

$ service nginx reload

Certificate

Now, you just need to add your certificate in /usr/local/etc/certmanage. All file whose name end in .json can contain certificate configuration. You can either use main.json or create a new file.

Each certificate is represented y a dict with two keys:

domains
The list of domains to be included in the certificate. The order is important as the first is domain is used for certificate file name.
reload
List of commands to be executed after certificate creation. Each command is represented by a list: first item is the command, next ones are arguments.

Here are some examples:

[{
    "domains": ["www.example.org", "example.org"],
    "reload": [["/bin/systemctl", "reload", "nginx.service"]]
}, {
    "domains": ["imap.example.org"]
}, {
    "domains": ["smtp.example.org"],
    "reload": [["/usr/local/bin/myCustomCommand"], ["/bin/systemctl", "reload", "exim.service"]]
}]

And finally just get you certificate

$ sudo /usr/local/sbin/certmanage
Renewing certificate for imap.example.org that will expire on 0001-01-01

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for imap.example.org
Using the webroot path /var/www/acme-challenge for all unmatched domains.
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/1764_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/1764_csr-certbot.pem

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/imap.example.org/fullchain.pem. Your cert
   will expire on 2025-03-03. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Restarting services:
service dovecot reload
[ ok ] Reloading IMAP/POP3 mail server: dovecot

Note that the command will create only one certificate per execution. If you added multiple of them, you need to run the command multiple times.

Revoke Certificate

If it has been possible for someone to access the private key of one of your certificate, it is strongly recommended to revoke it.

Identify the Certificate to Revoke

The first thing to do if to know which certificate you want to revoke.

$ ls -l /etc/letsencrypt/live/www.example.org/cert.pem 
lrwxrwxrwx 1 root root 43 Jan 01 00:00 /etc/letsencrypt/live/www.example.org/cert.pem -> ../../archive/www.example.org/cert7.pem

This will give you the path of the currently active certificate in the /etc/letsencrypt/archive/ folder.

Renew the Certificate

If the certificate you want to revoke is active, you need to renew it before revoking it

$ sudo touch /etc/letsencrypt/live/www.example.org/force_renew
$ sudo /usr/local/sbin/certmanage
Renewing certificate for www.example.org that will expire on 0001-01-01

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.example.org
Using the webroot path /var/www/acme-challenge for all unmatched domains.
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/1764_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/1764_csr-certbot.pem

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/www.example.org/fullchain.pem. Your cert
   will expire on 2025-03-03. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Restarting services:
/bin/systemctl reload nginx.service

Revoke the Old Certificate

Use the path found at step 1

$ sudo certbot revoke --cert-path /etc/letsencrypt/archive/www.example.org/cert7.pem

The command doesn't output anything in case of success.