hardware / mailserver

:warning: UNMAINTAINED - Simple and full-featured mail server using Docker
https://store.docker.com/community/images/hardware/mailserver
MIT License
1.29k stars 322 forks source link

Run Letsencrypt with nginx and the mail server [script] #327

Closed mario26 closed 6 years ago

mario26 commented 6 years ago

Hello !

I do not know if this is a good practice, but i use Nginx as a Web server and i had some trouble running with Letsencrypt on the mail server.

I want to explain to you in this issue how to get around my problems

For starters, here is the context, Nginx is executed in a container, i need this one to run hard sites on the server. Without any knowledge of Traefik, being a web server too young, i do not want to use it for now.

Letsencrypt is to install on the server, not finding my happiness in the tutorials of Internet to execute it in docker, i execute this one directly on the server.

So, i create a symbolic link to the Letsencrypt directory:

ln -s /etc/letsencrypt/ /mnt/my-docker/config/

I add the volume to mount for nginx and the mail server in the docker-compose.yml:

- ${VOLUMES_ROOT_PATH}/config/letsencrypt/:/etc/letsencrypt

I generate my certificate:

certbot certonly --standalone --agree-tos -m contact@example.org -d example.org -d www.example.org -d mail.example.org -d spam.example.org

Then i replace the signed auto certificates:

rm /mnt/my-docker/data/mail/ssl/selfsigned/cert.pem
rm /mnt/my-docker/data/mail/ssl/selfsigned/privkey.pem
cat /etc/letsencrypt/live/exemple.com/fullchain.pem >> /mnt/my-docker/data/mail/ssl/selfsigned/cert.pem
cat /etc/letsencrypt/live/exemple.com/privkey.pem >> /mnt/my-docker/data/mail/ssl/selfsigned/privkey.pem

I automate that with a bash script:

#!/bin/bash

echo -e '\033[0;33mContainer NGINX stop...'
docker-compose stop nginx && \
echo -e '\033[1;31mContainer NGINX stoped !'
docker-compose ps nginx && \

echo -e '\033[0;33mLetsencrypt certificates will be renewed...'
certbot renew >> /mnt/my-docker/scripts/logs/renew.txt && \
echo -e '\033[0;32mCertificates are verified/renewed, view: \033[4;32m/mnt/my-docker/scripts/logs/renew.txt'

echo -e '\033[0;33mContainer NGINX start...'
docker-compose start nginx && \
echo -e '\033[1;32mContainer NGINX starting !'
docker-compose ps nginx && \

echo -e '\033[0;33mContainer MAILSERVER stop...'
docker-compose stop mailserver && \
echo -e '\033[1;31mContainer MAILSERVER stoped !'
docker-compose ps mailserver && \

echo -e '\033[0;33mMAILSERVER certificates will be renewed...'
rm /mnt/my-docker/data/mail/ssl/selfsigned/cert.pem && \
rm /mnt/my-docker/data/mail/ssl/selfsigned/privkey.pem && \
cat /etc/letsencrypt/live/devosi.org/fullchain.pem >> /mnt/my-docker/data/mail/ssl/selfsigned/cert.pem && \
cat /etc/letsencrypt/live/devosi.org/privkey.pem >> /mnt/my-docker/data/mail/ssl/selfsigned/privkey.pem && \
echo -e '\033[0;32mMAILSERVER certificates are up-to-date!'

echo -e '\033[0;33mContainer MAILSERVER start...'
docker-compose start mailserver && \
echo -e '\033[1;32mContainer MAILSERVER starting !'
docker-compose ps mailserver && \

echo -e '\033[0;32mWell! All SSL certificates are up to date !'

Run the script:

bash -c /mnt/my-docker/scripts/letsencrypt/renew.sh

Here, i wanted to share my experience in exchange for some return.

Thank you for this beautiful Image!

I have 10/10 on => https://www.mail-tester.com

denji commented 6 years ago

certbot can start without downtime for nginx (master process). You must add location to handle .well-known and customize the operation cerbot (standalone -> webroot).

--deploy-hook "docker-compose -f /mnt/docker/docker-compose.yml exec nginx nginx -s reload"

https://certbot.eff.org/docs/using.html#renewal

  • --pre-hook and --post-hook hooks run before and after every renewal attempt. If you want your hook to run only after a successful renewal, use --deploy-hook in a command like this.

Update the certificate via nginx, just run docker-compose exec nginx nginx -s reload. This will make a gracefully restart of nginx (worker processes) without disconnection and downtime, relevant for the stable operation of HTTP/2.0 & HTTP keep-alive.

https://nginx.org/en/docs/control.html

hardware commented 6 years ago

You can use acme.sh to renew Let's Encrypt certificates, it's a great tool and very simple to use.

acme.sh \
  --issue \
  -w /mnt/docker/nginx/www/acme \
  -k 4096 \
  -d domain.tld \
  -d mail.domain.tld \
  -d webmail.domain.tld \
  -d spam.domain.tld \
  -d postfixadmin.domain.tld

acme.sh --install-cert -d domain.tld \
--ca-file        ${VOLUMES_ROOT_PATH}/ssl/live/mail.domain.tld/chain.pem  \
--cert-file      ${VOLUMES_ROOT_PATH}/ssl/live/mail.domain.tld/cert.pem  \
--key-file       ${VOLUMES_ROOT_PATH}/ssl/live/mail.domain.tld/privkey.pem  \
--fullchain-file ${VOLUMES_ROOT_PATH}/ssl/live/mail.domain.tld/fullchain.pem \
--reloadcmd      "docker restart mailserver"

acme.sh create a cron task to check if your certificates need to be renewed or not :

30 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"

Put this in all your vhosts to renew without down time :

location ~ /\.well-known/acme-challenge {
  root /nginx/www/acme;
  allow all;
}

location / {
   ...
}

Two paths depends on your setup and nginx configuration /mnt/docker/nginx/www/acme and /nginx/www/acme.

All of this is fully compatible with this docker image, I use this kind of setup for one of my mail servers.

denji commented 6 years ago

@hardware *domain.tld/test/.well-known/acme-challenge

  1. location /.well-known/acme-challenge
  2. location ^~ /.well-known/acme-challenge
mario26 commented 6 years ago

Thank you for your feedback!

@hardware: Thanks for your advice, i do not use acme.sh, i'll see how it works and in practice, i will adapt acme.sh to my Docker if I find it useful.

Regarding the "acme-challenge", i created in my VHOST Nginx includes:

server {
    listen 80;
...
    include /var/www/snippets/letsencrypt.conf;
...
}

My letsencrypt.conf

location ^~ /.well-known/acme-challenge/ {
  allow all;
  auth_basic "off";
  root /var/www/snippets/letsencrypt;
  default_type "text/plain";
  try_files $uri =404;
}

Everything to work, thank you for your responses!

denji commented 6 years ago

Here's another addition, besides the cron we can use systemd/Timers https://github.com/Neilpang/acme.sh/wiki/Using-systemd-units-instead-of-cron.