Skip to content

SSL Certificate maintenance using ACME with legacy applications

Apache

Config

/etc/apache2/sites-enabled/custom.conf
Define server_name "custom-site"
Define domain_name "custom-domain.tld"
Define server_fqdn "${server_name}.${domain_name}"
Define persistent_volume_path "/mnt/persistent-volumes/resinfo-go-acme-lego"
Define acme_challenge_path "${persistent_volume_path}/data/www/.well-known/acme-challenge/"

Alias "/.well-known/acme-challenge/" "${acme_challenge_path}"

<IfModule mod_access_compat.c>
  <Directory "${acme_challenge_path}">
    Satisfy any
  </Directory>
</IfModule>

<VirtualHost ${server_fqdn}:80>
  ServerName ${server_fqdn}
  RewriteEngine on
  RewriteCond %{REQUEST_URI} !^\.well-known/acme-challenge/.*
  RewriteRule ^https://%{HTTP_HOST}%{REQUEST_URI} [END,NE]

  #LogLevel trace5
  LogLevel info
  ErrorLog  ${APACHE_LOG_DIR}/vh_${server_name}_80_.error.log
  CustomLog ${APACHE_LOG_DIR}/vh_${server_name}_80_.access.log combined
</VirtualHost>

<VirtualHost ${server_fqdn}:443>
  ServerName ${server_fqdn}

  DocumentRoot /var/www/${server_name}/html

  #LogLevel trace5
  LogLevel info
  ErrorLog  ${APACHE_LOG_DIR}/vh_${server_name}_443_.error.log
  CustomLog ${APACHE_LOG_DIR}/vh_${server_name}_443_.access.log combined

  # configure SSL
  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/${server_fqdn}.crt
  SSLCertificateKeyFile /etc/apache2/ssl/${server_fqdn}.key
  SSLProtocol All -SSLv2 -SSLv3
  SSLOpenSSLConfCmd DHParameters /etc/ssl/certs/dhparam.pem
  SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
    ...
</VirtualHost>

Systemd

Filesystem monitoring

/etc/systemd/system/apache2-reload-ssl-config.path
1
2
3
4
5
6
7
8
9
[Unit]
Description="Monitor the apache reload file"

[Path]
PathChanged=/mnt/persistent-volumes/resinfo-go-acme-lego/data/reload
Unit=apache2-reload-ssl-config.service

[Install]
WantedBy=multi-user.target

Service reloading

/etc/systemd/system/apache2-reload-ssl-config.service
[Unit]
Description="Reload apache2 with the new certificate"

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c "cp -R /mnt/persistent-volumes/resinfo-go-acme-lego/data/certificates/* /etc/apache2/ssl/"
ExecStart=/usr/sbin/apachectl restart

[Install]
WantedBy=multi-user.target
$ systemctl enable --now apache2-reload-ssl-config.path apache2-reload-ssl-config.service

Docker

Systemd

Service

/etc/systemd/system/resinfo-go-acme-lego.service
[Unit]
Description=resinfo-go-acme-lego
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes

WorkingDirectory=/etc/docker/compose/resingo-go-acme-lego

# Compose up
ExecStart=/usr/bin/docker compose --file docker-compose.json up 

# Compose down, remove containers and volumes
ExecStop=/usr/bin/docker compose --file docker-compose.json down --volumes

[Install]
WantedBy=multi-user.target

Timer

/etc/systemd/system/resinfo-go-acme-lego.timer
[Unit]
Description=resinfo-go-acme-lego
Requires=resinfo-go-acme-lego.service

[Timer]
OnCalendar=weekly
Persistent=true
Unit=resinfo-go-acme-lego.service

[Install]
WantedBy=timers.target

Docker

compose

/etc/docker/compose/resinfo-go-acme-lego/.env
1
2
3
4
5
RESINFO_GO_ACME_LEGO_RELEASE_VERSION='v4.25.2'
RESINFO_GO_ACME_LEGO_UID='1002'
RESINFO_GO_ACME_LEGO_GID='1002'
RESINFO_GO_ACME_LEGO_PERSISTENT_CONF_DIR_PATH='/mnt/persistent-volumes/resinfo-go-acme-lego/conf'
RESINFO_GO_ACME_LEGO_PERSISTENT_DATA_DIR_PATH='/mnt/persistent-volumes/resinfo-go-acme-lego/data'
/etc/docker/compose/resinfo-go-acme-lego/Dockerfile
ARG ARG_RESINFO_GO_ACME_LEGO_RELEASE_VERSION

FROM goacme/lego:${ARG_RESINFO_GO_ACME_LEGO_RELEASE_VERSION}
COPY entrypoint.bash /entrypoint.bash
COPY lego-hook.bash /lego-hook.bash
RUN apk add bash \
 && chmod +x /entrypoint.bash \
 && chmod +x /lego-hook.bash

ENTRYPOINT [ "/entrypoint.bash" ]
/etc/docker/compose/resinfo-go-acme-lego/docker-compose.json
{
    "networks": {
        "external": {
            "external": true
        }
    },
    "services": {
        "resinfo-go-acme-lego": {
            "container_name": "resinfo-go-acme-lego",
            "build": {
                "context": ".",
                "dockerfile": "Dockerfile",
                "args": [
                    "ARG_RESINFO_GO_ACME_LEGO_RELEASE_VERSION=${RESINFO_GO_ACME_LEGO_RELEASE_VERSION}"
                ]
            },
            "image": "goacme-lego:${RESINFO_GO_ACME_LEGO_RELEASE_VERSION}-p0",
            "user": "${RESINFO_GO_ACME_LEGO_UID}:${RESINFO_GO_ACME_LEGO_GID}",
            "networks": {
                "external": {}
            },
            "volumes": [
                "${RESINFO_GO_ACME_LEGO_PERSISTENT_CONF_DIR_PATH}:/conf:ro",
                "${RESINFO_GO_ACME_LEGO_PERSISTENT_DATA_DIR_PATH}:/data:rw"
            ]
        }
    }
}
/etc/docker/compose/resinfo-go-acme-lego/entrypoint.bash
#!/bin/bash

set -e

[ "${DEBUG:=UNSET}" == 'UNSET' ] || set -x

function acme_create {
    local -r acme_domain="${1}"
    local -r http_webroot="${2}"

#    lego --server "${ACME_SERVER_URL}" \
#         --accept-tos \
#         --email "${ACME_EMAIL}" \
#         --eab --kid "${ACME_EAB_KEY_ID}" --hmac "${ACME_EAB_HMAC_KEY}" \
#         --http --http.webroot "${http_webroot}" \
#         --path "/data" \
#         --domains "${acme_domain}" \
#         run --run-hook /lego-hook.bash
    /lego --accept-tos \
         --email "${ACME_EMAIL}" \
         --http --http.webroot "${http_webroot}" \
         --path "/data" \
         --domains "${acme_domain}" \
         run --run-hook /lego-hook.bash
}

function acme_renew {
    local -r acme_domain="${1}"
    local -r http_webroot="${2}"

#    lego --server "${ACME_SERVER_URL}" \
#         --email "${ACME_EMAIL}" \
#         --eab --kid "${ACME_EAB_KEY_ID}" \
#         --http --http.webroot "${http_webroot}" \
#         --path "/data" \
#         --domains "${acme_domain}" \
#         renew --renew-hook /lego-hook.bash
    /lego --email "${ACME_EMAIL}" \
         --http --http.webroot "${http_webroot}" \
         --path "/data" \
         --domains "${acme_domain}" \
         renew --renew-hook /lego-hook.bash
}

function main {
    local -r env_file='/conf/.env'
    source "${env_file}"

    printenv

    local -r http_webroot="/data/www"

    mkdir -p "${http_webroot}"

    set -x
    for acme_domain in "${ACME_DOMAINS}"; do
        if [ -f "/data/certificates/${acme_domain}.crt" ]; then
            acme_renew "${acme_domain}" \
                       "${http_webroot}"
        else
            acme_create "${acme_domain}" \
                        "${http_webroot}"
        fi
    done
}

main
/etc/docker/compose/resinfo-go-acme-lego/lego-hook.bash
1
2
3
4
5
#!/usr/bin/env bash

set -e

touch /data/reload