Avec OpenTofu et le provider ACME, min_days_remaining ne renouvelle rien tout seul : l’attribut dit seulement au prochain tofu apply de recréer le certificat s’il reste moins de N jours de validité. Entre deux apply, rien ne tourne. J’avais quatre certificats Let’s Encrypt dans un module OpenTofu, avec un commentaire rassurant en tête du fichier, « Auto-renewal : tofu apply recrée le cert quand < 30j ». Exact, mais rien ne lançait tofu apply. Fin juillet 2026, j’ai confié ces certificats à certbot.timer.
Le module qui promettait un renouvellement automatique
Le module certs utilisait OpenTofu 1.12.0 (installé via tenv) et le provider vancluever/acme 2.48.1, avec un challenge DNS-01 chez OVH. Chaque certificat suivait le même modèle :
resource "acme_certificate" "chat_example_com" {
account_key_pem = acme_registration.main.account_key_pem
common_name = "chat.example.com"
# Force re-create quand il reste < 30j de validité
min_days_remaining = 30
dns_challenge {
provider = "ovh"
config = {
OVH_ENDPOINT = var.ovh_endpoint
OVH_APPLICATION_KEY = var.ovh_application_key
OVH_APPLICATION_SECRET = var.ovh_application_secret
OVH_CONSUMER_KEY = var.ovh_consumer_key
}
}
recursive_nameservers = ["1.1.1.1:53", "8.8.8.8:53"]
}
Les PEM étaient écrits en local, puis une null_resource les copiait en SSH sur srv-a et rechargeait Apache. Le README du module prévoyait la suite : « Cron mensuel sur le PC qui apply », ou « à la main à chaque session ». Le message du commit qui a remplacé ce montage le résume : « pas automatisé ».
Pourquoi min_days_remaining ne renouvelle rien tout seul ?
OpenTofu n’est pas un démon. Le provider ACME compare la date d’expiration au seuil uniquement quand OpenTofu s’exécute ; le reste du temps, le certificat vit sa vie sur le serveur, sans surveillance.
min_days_remainingest un seuil évalué au moment d’un apply, pas un planificateur : sans apply, pas de renouvellement.
S’y ajoutait une dépendance au poste de travail : l’apply devait tourner sur le PC qui avait l’alias SSH vers srv-a, avec sudo sans mot de passe pour mv, chmod et systemctl reload apache2. Un renouvellement qui dépend d’un portable allumé n’est pas automatique. Et les clés privées dormaient dans le state.
| Provider ACME (OpenTofu) | certbot + certbot.timer | |
|---|---|---|
| Déclencheur | un tofu apply lancé par quelqu’un | timer systemd, 2 passages par jour |
| Où ça tourne | le poste qui applique | le serveur lui-même |
| Déploiement | scp + ssh depuis le poste | rien à copier, deploy-hook qui recharge Apache |
| Clé privée | dans le state OpenTofu | sur le disque du serveur |
La migration vers certbot.timer en DNS-01 OVH
Sur srv-a, certbot.timer gérait déjà cloud.example.com, coolify.example.com et le wildcard dev.example.com, avec le même challenge DNS-01 OVH. La migration a consisté à réémettre chaque nom avec certbot :
sudo certbot certonly --dns-ovh --dns-ovh-credentials /etc/letsencrypt/ovh.ini \
--dns-ovh-propagation-seconds 60 \
-d chat.example.com --cert-name chat.example.com
Le deploy-hook /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh recharge Apache après chaque renouvellement. Bilan du module :
notes.example.cometchat.example.compassent sous certbot, ainsi queapi.supabase.dev.example.com, d’abord émis par OpenTofu puis confié à certbot le jour même.rdv.example.orgest supprimé du module : le domaine pointe désormais vers srv-b, où Traefik émet lui-même le certificat en HTTP-01.event.example.orgreste sous OpenTofu, faute de mieux (voir ci-dessous).
⚠️ Passer à certbot-dns-ovh a un effet de bord : le plugin Debian ne supprime pas ses TXT de validation, et un résidu a cassé la résolution de deux noms couverts par un wildcard. C’est l’objet de l’article sur le _acme-challenge oublié qui fait taire le wildcard DNS, avec le hook qui purge la zone.
Le reste : un token OVH limité à une zone
Le token OVH de /etc/letsencrypt/ovh.ini est limité à la zone example.com et renvoie 403 sur example.org. Le certificat de event.example.org ne peut donc pas passer sous certbot avec ce token ; il reste le seul dans le module, dont l’en-tête annonce désormais un « périmètre réduit » et rappelle que la réémission n’a lieu qu’au tofu apply. Pour finir la migration, il faut un token OVH dont les règles couvrent aussi la seconde zone.
Lessons learned
min_days_remainingest un seuil, pas un planificateur. Le provider ACME ne renouvelle qu’au moment d’untofu apply, et rien ne s’exécute entre deux apply.- Un commentaire « auto-renewal » ne remplace pas un timer. Le cron mensuel documenté dans le README n’avait jamais été installé, et rien ne le signalait.
- Le renouvellement doit tourner là où vit le certificat.
certbot.timersur le serveur ne dépend ni d’un poste allumé ni d’un accès SSH sudo. - Changer d’outil de renouvellement change aussi ses effets de bord. Le plugin
certbot-dns-ovhlaisse des TXT_acme-challengequ’il faut purger. - Un token OVH limité à une zone bloque la migration de l’autre. Le 403 sur la seconde zone a laissé un certificat sous OpenTofu.