Instalación de Certbot y el plugin certbot_dns_duckdns
Instalación de las herramientas necesarias
Instalar los certificados en OMV 8
Renovación automática de los certificados
Renovar los certificados en OpenMediaVault 8
Script renovación certificados OMV 8
Script: /etc/letsencrypt/renewal-hooks/deploy/script_actualiza_omv.sh
Script: /etc/letsencrypt/renewal-omv/update_cert_omv.sh
Obtener y mantener renovado un certificado Let's Ecncrypt para un dominio propor-
cionado por Duckdns desde un equipo doméstico con OpenMediaVault 8 conectado a
internet a través de un router. La tarea de renovación también incluye la actua-
lización de los certificados en OMV 8.
Se usará Certbot usando como método de valildación del domino DNS-01 que no re-
quiere como el método HTTP-01 redireccionar puertos en el router y el plugin DNS
certbot_dns_duckdns.
Enlaces
En OVM 8 el administrador de paquetes del sistema está administrando Python
packages, lo que al intentar instalar el plugin certbot_dns_duckdns genera el
error externally-managed-environment, por ello se recurrira a un entrono para
realizar la instalación y posterior uso.
Instalar paquetes:
apt install certbot
apt install python3-pip
apt install python3-venv
Crear directorios en /etc/letsencrypt
mkdir /etc/letsencrypt/venv-ip
mkdir /etc/letsencrypt/credentials
El primero para el entrono de python en el que instalará certbot_dns_duckdns.
El segundo en el que se almacenerán los archivos de credenciales que usará
certbot para crear/renovar los certificados.
Crear el entorno y acceder a él
python3 -m venv /etc/letsencrypt/venv-ip
source /etc/letsencrypt/venv-ip/bin/activate
Instalar certbot_dns_duckdns y comprobar que está disponible
pip install certbot_dns_duckdns
certbot plugins
Certbot se puede usar manual o automáticamente, esto último lo hará el servi-
cio automático que renueva los certificados, pero en ambos casos ha de hacer-
lo en el entorno creado para tener acceso a certbot_dns_duckdns.
Manualmente Certbot puede ser llamado de dos formas:
Si se está dentro del entrono que se ha creado: certbot certonly ...
Si no se está: /etc/letsencrypt/venv-ip/bin/certbot certonly ...
La tarea automática1 de renovación necesita para realicese correctamente que se
sobreescriba la entrada ExecStart del Certbot.service de tal forma que la llama-
da que realice la haga desde el entrono -2ª opción anterior-, para ello:
# Editar
systemctl edit certbot.service
# Añadir
# Sobreescribir para que cerbot renew se ejecute desde el entono donde
# se ha instalado el plugin certbot_dns_duckdns
[Service]
ExecStart=
ExecStart=/etc/letsencrypt/venv-ip/bin/certbot -q renew --no-random-sleep-on-renew
# Verificar
systemctl daemon-reload
systemd-analyze verify certbot.service
systemctl cat certbot.service
# Resultado de sudo systemctl cat certbot.service
### Editing /etc/systemd/system/certbot.service.d/override.conf
### Anything between here and the comment below will become the contents of the drop-in file
[Service]
ExecStart=
ExecStart=/etc/letsencrypt/venv-ip/bin/certbot -q renew --no-random-sleep-on-renew
### Edits below this comment will be discarded
### /usr/lib/systemd/system/certbot.service
# [Unit]
# Description=Certbot
# Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
# Documentation=https://certbot.eff.org/docs
# [Service]
# Type=oneshot
# ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew
# PrivateTmp=true
En primer lugar se crea el archivo que se usará como credenciales en duckdns
para obtener los certificados
mkdir /etc/letsencrypt/credentials
cd /etc/letsencrypt/credentials
nano cfg_lozjim
El archivo sólo contendrá una línea:
dns_duckdns_token = [token asignado al crear el dominio]
Tras crear el archivo dar acceso exclusivamente al root
chmod 600 /etc/letsencrypt/credentials/cfg_lozjim
Obtener los certificados:
Desde el entrono donde se ha instalado el plugin certbot_dns_duckdns:
certbot certonly \
--authenticator dns-duckdns \
--dns-duckdns-credentials /etc/letsencrypt/credentials/cfg_lozjim \
--email favoritos.jp@gmail.com \
--agree-tos \
-d lozjim.duckdns.org
Desde fuera el entrono donde se ha instalado el plugin certbot_dns_duckdns:
/etc/letsencrypt/venv-ip/bin/certbot certonly \
--authenticator dns-duckdns \
--dns-duckdns-credentials /etc/letsencrypt/credentials/cfg_lozjim \
--email favoritos.jp@gmail.com \
--agree-tos \
-d lozjim.duckdns.org \
Detalle del comando:
certbot certonly
Pide/obtiene el certificado, pero no configura automáticamente un webserver
(nginx/apache). Solo gestiona el certificado.
--authenticator dns-duckdns
Indica que el desafío para validar el dominio será por DNS usando el plugin
de DuckDNS. En la práctica, Certbot hará modificaciones temporales (crea/-
ajusta registros DNS) para que Let’s Encrypt pueda verificar la propiedad
del dominio.
--dns-duckdns-credentials /etc/letsencrypt/credentials/cfg
Le pasa el archivo con las credenciales necesarias para que el plugin pueda
actualizar DNS en DuckDNS (normalmente un token/credencial, según cómo lo
tengas configurado).
--email myemail@gmail.com
Dirección de correo para comunicaciones de Let’s Encrypt (por ejemplo, avi-
sos importantes sobre el certificado).
--agree-tos
Acepta automáticamente los Términos de Servicio de Let’s Encrypt para que el
comando no pregunte de forma interactiva
-d mydomino.duckdns.org
Es el dominio objetivo para el que quieres el certificado
Se puede añdir --dry-run> para hacer simulaciones.
Certbot genera los certificados compuestos por los siguientes archivos:
/etc/letsencrypt/live/$domain/privkey.pem
/etc/letsencrypt/live/$domain/fullchain.pem
/etc/letsencrypt/live/$domain/cert.pem
/etc/letsencrypt/live/$domain/chain.pem
La instalación en OMV de los certificados consiste en copiar el contenido de dos
de los archivos obtenidos.
En Sistema - Certificados - + (Añdir) - Importar
Hay que seleccionar el archivo que contenga:
La clave privada RSA o ECC enformato X.509 PEM
/etc/letsencrypt/live/lozjim.duckdns.org/privkey.pem
El certificado RSA o ECC en formato X.509 PEM aqui.
/etc/letsencrypt/live/lozjim.duckdns.org/fullchain.pem
La instalación de Certbot preconfigura una tarea para actualizar los certifica-
dos que sean instalados por la aplicación. Los usuarios del plugin Manual deben
tener en cuenta que los certificados --manual no se renovarán automáticamente,
a menos que se combinen con scripts de gancho de autenticación.
Se puede consultar con el comando:
systemctl list-timers
Debería generar una salida con las tareas programadas y entre ellas
NEXT Mon 2026-07-27 07:11:39 CEST
LEFT LAST 11h Sun 2026-07-26 18:51:05 CEST
PASSED UNIT 31min ago certbot.timer
ACTIVATES certbot.service
El estado de la programación se puede ver:
systemctl status certbot.timer
● certbot.timer - Run certbot twice daily
Loaded: loaded (/usr/lib/systemd/system/certbot.timer; enabled; preset: enabled)
Active: active (waiting) since Sun 2026-07-26 16:49:24 CEST; 4h 6min ago
Invocation: 88cae7e70f194398b9e4a4693cd41126
Trigger: Mon 2026-07-27 07:11:39 CEST; 10h left
Triggers: ● certbot.service
Jul 26 16:49:24 homenas systemd[1]: Started certbot.timer - Run certbot twice daily.
Una vez obtenidos los certificados, esta tarea se encargará de comprobar la necesi-
dad de renovar los certificados de los dominios que se hayan obtenido con certbot
y los renovará en caso necesario. Sin embargo, la renovación no actualizará los cer-
tificados en OMV 8.
Al renovar certificados Certbot ejecutar -si existen- unos scripts que pueden
ser usados para realizar tareas adicionales. Por ejemplo, para actualizar los
certificdos en OMV8.
Los scripts de certbot renew:
1. Inicia proceso de renovación
2. Ejecuta /etc/letsencrypt/renewal-hooks/pre/* (una sola vez)
3. Para cada certificado a renovar:
├─ Intenta renovar
├─ Si éxito → ejecuta /etc/letsencrypt/renewal-hooks/deploy/*
└─ Si fallo → detiene
4. Si al menos uno se renovó → ejecuta /etc/letsencrypt/renewal-hooks/post/*
En post y prev puede haber varios scripts, se ejecutan en orden por lo que
convien nombrarlos como 01_xxx.sh, 02_xxx.sh ...
Durante el proceso Certbot también asigna valores a unas variables de entrono
que podrán ser usadas por estos scripts.
CERTBOT_DOMAIN
Dominio principal del certificado
example.com
CERTBOT_VALIDATION
Token de validación (en desafíos HTTP-01)
validation_string_12345...
CERTBOT_AUTH_OUTPUT
Salida del plugin de autenticación
Depende del plugin usado
CERTBOT_CERT_PATH
Ruta al archivo de certificado
/etc/letsencrypt/live/example.com/cert.pem
CERTBOT_KEY_PATH
Ruta a la clave privada
/etc/letsencrypt/live/example.com/privkey.pem
CERTBOT_CA_CERT_PATH
Ruta al certificado de la CA intermedia
/etc/letsencrypt/live/example.com/chain.pem
CERTBOT_FULLCHAIN_PATH
Ruta al certificado completo (cert + chain)
/etc/letsencrypt/live/example.com/fullchain.pem
RENEWED_LINEAGE
Ruta al directorio del dominio
/etc/letsencrypt/live/example.com
RENEWED_DOMAINS
FAILED_DOMAINS
La variable RENEWED_LINEAGE sólo estará disponible cuando se ha realizado
correctamente la renovación.
Este script se encontrará en /etc/letsencrypt/renewal-hooks/deploy y se ejecu-
tará cada vez que certbot renueve con exito un certificado. Partiendo de la variable
RENEWED_LINEAGE obtiene los datos necesarios del dominio que se ha actualizado
para lanzar otro script genérico que realiza la tarea con los datos proporcionados.
#!/bin/bash
# Nombre: actualiza_certificado_omv.sh
# Descripción: Deploy-hook de Certbot que sincroniza un certificado SSL
# renovado con OpenMediaVault 8, actualizando la entrada
# existente en la base de datos de configuración de OMV
# (conf.system.certificate.ssl) sin crear duplicados.
# Ubicación: /etc/letsencrypt/renewal-hooks/deploy/
# Disparo: Certbot ejecuta automáticamente este script tras CADA
# renovación exitosa de CUALQUIER certificado gestionado
# en este sistema (vía certbot renew, manual o por
# certbot.timer), con independencia del authenticator
# usado (dns-duckdns, standalone, webroot, etc.) y de si
# dicho certificado fue emitido con o sin --deploy-hook.
# Requisitos:
# - Debe ejecutarse como root (así lo hace Certbot vía systemd timer).
# - Paquete 'jq' instalado (parseo de JSON).
# - omv-confdbadm disponible (CLI de configuración de OMV).
# - Script auxiliar update_cert.sh en /ruta/a/update_cert.sh, que
# realiza el 'write' sobre el UUID existente en OMV.
# - El certificado debe existir previamente en OMV (Sistema →
# Certificados → SSL).
# Variables de entorno usadas (inyectadas por Certbot):
# RENEWED_LINEAGE Ruta al directorio live/ del certificado renovado
# (ej. /etc/letsencrypt/live/midominio.duckdns.org)
# Salida:
# exit 0 → sincronización con OMV completada correctamente
# exit 1 → error (variable no definida, ficheros no encontrados,
# UUID no localizado en OMV, o fallo en update_cert.sh)
# Fecha: 2026-07-23
# Versión: 1.0
set -euo pipefail
# script que realiza la actualización
SCRIPT_RENEW_OMV="/etc/letsencrypt/renewal-omv/update_cert_omv.sh"
# archivo de log
LOGFILE="/var/log/letsencrypt/renovaciones-omv.log"
# --- Datos derivados de la variable de entorno que Certbot inyecta ---
# RENEWED_LINEAGE="/etc/letsencrypt/live/lozjim.duckdns.org"
_echo(){
local instante
instante="$(date '+%Y-%m-%d %H:%M:%S.%3N')"
echo "[$instante] $1" >> "$LOGFILE"
echo "[$instante] $1"
# Comprobar la existencia del archivo
if [ ! -f "${SCRIPT_RENEW_OMV}" ]; then
_echo "Error: ${SCRIPT_RENEW_OMV} no existe."
exit 1
fi
# Comprobar que existe la variable y tiene contenido
if [ -z "${RENEWED_LINEAGE:-}" ]; then
_echo "Error: RENEWED_LINEAGE no está definida. Este script debe ejecutarse como deploy-hook de Certbot."
exit 1
fi
DOMINIO=$(basename "$RENEWED_LINEAGE")
CERTIFICADO="$RENEWED_LINEAGE/fullchain.pem"
KEY="$RENEWED_LINEAGE/privkey.pem"
COMENTARIO="$DOMINIO - Updated $(date '+%Y-%m-%d %T')"
# --- Validar que los ficheros existen ---
if [ ! -f "$CERTIFICADO" ] || [ ! -f "$KEY" ]; then
_echo "Error: no se encuentran fullchain.pem o privkey.pem en $RENEWED_LINEAGE"
exit 1
fi
# --- Buscar el UUID del certificado en OMV comprobando CN + SAN del propio certificado ---
UIDD=""
while IFS=$'\t' read -r uuid cert_b64; do
cert_pem=$(echo "$cert_b64" | base64 -d 2>/dev/null)
[ -z "$cert_pem" ] && continue
nombres=$(echo "$cert_pem" | openssl x509 -noout -text 2>/dev/null | \
awk '/Subject:/{print} /X509v3 Subject Alternative Name/{getline; print}')
if echo "$nombres" | grep -q "$DOMINIO"; then
UIDD="$uuid"
break
fi
done < <(omv-confdbadm read "conf.system.certificate.ssl" | jq -r '.[] | [.uuid, (.certificate | @base64)] | @tsv')
if [ -z "$UIDD" ]; then
_echo "Error: No se encontró certificado para $DOMINIO en OMV"
exit 1
fi
# --- Actualizar el certificado en OMV ---
if ! bash "${SCRIPT_RENEW_OMV}" "$UIDD" "$CERTIFICADO" "$KEY" "$COMENTARIO"; then
_echo "Error: fallo al actualizar el certificado en OMV para $DOMINIO"
exit 1
fi
_echo "Certificado de $DOMINIO actualizado correctamente en OMV (UUID: $UIDD)"
exit 0
#!/bin/bash
# shellcheck disable=SC1091
. /usr/share/openmediavault/scripts/helper-functions
. /etc/default/openmediavault
# archivo de log
LOGFILE="/var/log/letsencrypt/renovaciones-omv.log"
_echo(){
local instante
instante="$(date '+%Y-%m-%d %H:%M:%S.%3N')"
echo "[$instante] $1" >> "$LOGFILE"
echo "[$instante] $1"
if [[ $(id -u) -ne 0 ]]; then
_echo "This script must be executed as root or using sudo."
exit 99
fi
uuid="${1}"
cert="${2}"
key="${3}"
if [ -z "${uuid}" ]; then
xpath="/config/webadmin/sslcertificateref"
uuid=$(omv_config_get "${xpath}")
fi
if ! omv_isuuid "${uuid}"; then
_echo "Invalid uuid"
exit 1
fi
if [ ! -f "${cert}" ]; then
_echo "Cert not found"
exit 2
fi
if [ ! -f "${key}" ]; then
_echo "Key not found"
exit 3
fi
_echo "Cert file :: ${cert}"
_echo "Key file :: ${key}"
xpath="/config/system/certificates/sslcertificate[uuid='${uuid}']"
_echo "xpath :: ${xpath}"
echo
if ! omv_config_exists "${xpath}"; then
_echo "Config for ${uuid} does not exist"
exit 4
fi
_echo "Updating certificate in database ..."
omv_config_update "${xpath}/certificate" "$(cat ${cert})"
_echo "Updating private key in database ..."
omv_config_update "${xpath}/privatekey" "$(cat ${key})"
if [ -n "${4}" ]; then
_echo "Updating comment in database ..."
omv_config_update "${xpath}/comment" "${4}"
fi
_echo "Updating certs and nginx..."
omv-salt deploy run certificates nginx
systemctl restart nginx