Auto ACME with Traefik for non-Traefik services
I've started using Traefik as a reverse proxy for many of my services since it can handle auto-renewing SSL certificates via ACME/Let's Encrypt for you. But what if you have a service like Postfix which aren't a great fit for Traefik? Can you still automate certs?
Turns out you definitely can and it's fairly simple once you have Traefik renewing certificates for FQDN's it controls. The following directions are for Postfix, but can easily be adapted to any other service.
Configure Traefik to renew your mail server certificate
Ensure you have a certificateResolver configured in your static config file (typically traefik.yaml
):
certificatesResolvers:
myresolver:
acme:
httpChallenge:
entryPoint: web
email: 'postmaster@synfin.net'
storage: '/letsencrypt/acme.json'
keyType: 'RSA4096'
You can use any ACME challenge you want, but you need to specify a storage
location that is a mounted directory so we can read the acme.json
file with the certificate data.
Then in your Traefik dynamic config (not to be confused with the static config used for the previous block!) you need to configure the SSL certificate renewal:
mail:
service: "nginx-traefik@docker"
rule: "Host(`mail.synfin.net`)"
entrypoints:
- websecure
- web
tls:
certresolver: myresolver
domains:
- main: mail.synfin.net
The above tells Traefik to use my above certificatesResolvers.myresolver
to renew the certificate for mail.synfin.net
and attach an entry point to my dynamic Nginx service running via docker. Ngnix doesn't actually need to serve any content for this domain- Traefik will do everything necessary. Since I'm using HTTP-01 challenges, I need to specify both my http (web
) and https (websecure
) entrypoints.
Generate the PEM files
Add a new shell script named /etc/cron.daily/renew-mail-cert
:
#!/bin/bash
set -e
# set directory to where your Traefik acme.json file lives
ACME_FILE=/home/docker/traefik/acme.json
# the FQDN of the domain to renew
DOMAIN=mail.synfin.net
# Base directory for where to write the SSL certificate files
BASE_DIR=/etc/postfix
# Path/name of GNU Sed binary, jq and others
GSED=/usr/bin/sed
JQ=/usr/bin/jq
BASE64=/usr/bin/base64
SHA1SUM=/usr/bin/sha1sum
POSTFIX=/usr/sbin/postfix
##### END CONFIGURATION #####
command -v $GSED >/dev/null || (echo "Missing sed binary" && exit 1)
command -v $JQ >/dev/null || (echo "Missing jq binary" && exit 1)
command -v $BASE64 >/dev/null || (echo "Missing base64 binary" && exit 1)
command -v $SHA1SUM >/dev/null || (echo "Missing sha1sum binary" && exit 1)
command -v $POSTFIX >/dev/null || (echo "Missing postfix binary" && exit 1)
# GNU sed search replace for the certificate (1st cert)
CERT_MATCH='s|^-----BEGIN CERTIFICATE-----([^-]*)-----END CERTIFICATE-----(.*)|-----BEGIN CERTIFICATE-----\1-----END CERTIFICATE-----\n|'
# GNU sed search replace for the CA (2nd cert)
CA_MATCH='s|^-----BEGIN CERTIFICATE-----([^-]*)-----END CERTIFICATE-----\n(.*)|\2|'
# JQ queries to extract the cert & key base64 data
JQ_QUERY_CERT=".myresolver.Certificates[] | select(.domain.main==\"${DOMAIN}\") | .certificate"
JQ_QUERY_KEY=".myresolver.Certificates[] | select(.domain.main==\"${DOMAIN}\") | .key"
CERT_CHAIN=$(cat $ACME_FILE | $JQ -r "$JQ_QUERY_CERT" | $BASE64 -d)
CERT=$(echo -n "${CERT_CHAIN}" | $GSED -zEe "$CERT_MATCH")
CA=$(echo -n "${CERT_CHAIN}" | $GSED -zEe "$CA_MATCH")
NEW_CERT_HASH=$(echo "$CERT" | $SHA1SUM)
OLD_CERT_HASH=$(cat ${BASE_DIR}/cert.pem | $SHA1SUM)
NEW_CA_HASH=$(echo "$CA" | $SHA1SUM)
OLD_CA_HASH=$(cat ${BASE_DIR}/ca.pem | $SHA1SUM)
if [ "${NEW_CERT_HASH}" != "${OLD_CERT_HASH}" -o "${NEW_CA_HASH}" != "${OLD_CA_HASH}" ]; then
echo "Updating SSL certificate and CA for postfix"
echo "$CERT" >${BASE_DIR}/cert.pem
echo "$CA" >${BASE_DIR}/ca.pem
umask 077
cat $ACME_FILE | $JQ -r "$JQ_QUERY_KEY" | $BASE64 -d > ${BASE_DIR}/privkey.pem
$POSTFIX reload
fi
Validate SSL cert generation
- Restart Traefik if necessary and verify the new cert is in
acme.json
- Run
/etc/cron.daily/renew-mail-cert
- Verify the
*.pem
files are correct in$BASE_DIR
. I'd recommend inspecting the generatedca.pem
andcert.pem
usingopenssl x509 -text -in <filename>
.
Configure and restart Postfix
From my /etc/postfix/main.cf
:
# smtp SSL config...
smtp_tls_key_file = /etc/postfix/privkey.pem
smtp_tls_cert_file = /etc/postfix/cert.pem
smtp_tls_CAfile = /etc/postfix/ca.pem
# as well as smtpd...
smtpd_tls_key_file = /etc/postfix/privkey.pem
smtpd_tls_cert_file = /etc/postfix/cert.pem
smtpd_tls_CAfile = /etc/postfix/ca.pem
And then run postfix check && postfix reload
.