blog.vfg.lol

Incident Caddy — lucien.best offline (vhosts perdus) + prep zevent.vfg.lol

Post-mortem 2026-09-05 : TLS alert internal error alors que Node répondait encore sur :3000. Cause : Caddyfile du 8 juillet sans import Lucien. Fix + règles anti-régression pour ne plus écraser les blocs no-touch.

TL;DR

Le 2026-09-05, https://lucien.best était injoignable en HTTPS (tlsv1 alert internal error / SEC_E_ILLEGAL_MESSAGE) alors que :

  • le DNS A pointait bien vers le VPS 51.77.244.222 ;
  • lucien-site.service était actif ;
  • http://127.0.0.1:3000/ renvoyait 200.

Cause racine : le /etc/caddy/Caddyfile modifié le 2026-07-08 ne contenait plus que :

{
	admin off
}
import /etc/caddy/vfg-fleet.caddy
import /etc/caddy/vfg-admin.caddy

Les blocs lucien.best / www.lucien.best (historiquement no-touch) avaient disparu. Sans site Caddy pour ce SNI, Caddy refuse le handshake TLS → symptôme « SSL cassé », pas « app down ».

Fix : restaurer un snippet /etc/caddy/lucien.caddy + systemctl restart caddy (pas reload : voir plus bas). Certificat Let's Encrypt réémis le jour même. Smoke : lucien.best / hero.vfg.lol / msn.vfg.lol / blog.vfg.lol en 200.

Bonus session : DNS zevent.vfg.lol déjà prêt → déploiement ZDASH (fg-zevent, :5200, vhost Caddy) live sur https://zevent.vfg.lol.


Chronologie

Heure (approx, CEST)Événement
~12:09Signal FG : domaine zevent.vfg.lol ajouté ; lucien.best ne marche plus
~12:10Probes Windows : DNS OK pour lucien + zevent ; HTTPS échoue ; hero.vfg.lol OK
~12:12SSH debian@ : app Lucien OK en local ; openssl SNI lucien.best → no peer certificate
~12:13Lecture Caddyfile : aucun lucien dans /etc/caddy/ actif
~12:14Source intacte trouvée : /opt/lucien/deploy/Caddyfile
~12:17Tentative systemctl reload caddy → échec (admin off → API :2019 refusée) ; process Caddy reste sur l'ancienne config (fleet intact)
~12:19systemctl restart caddy → cert LE pour lucien.best ; HTTPS 200
~12:20–12:21Upload + build fg-zevent, compose up, import zevent.caddy, restart Caddy → zevent.vfg.lol 200

Symptômes (ce que ça donnait vraiment)

Côté client

curl https://lucien.best/
→ curl: (35) ... tlsv1 alert internal error

# Windows / schannel
SEC_E_ILLEGAL_MESSAGE (0x80090326)

HTTP clair répondait encore avec un redirect Caddy vers HTTPS :

curl -I http://lucien.best/
→ 308 Location: https://lucien.best/

Donc « le domaine répond » en HTTP, mais tout navigateur moderne casse au TLS.

Côté VPS (preuves)

CheckRésultat
systemctl is-active lucien-siteactive
ss :3000127.0.0.1:3000 LISTEN
curl http://127.0.0.1:3000/200
curl -k https://lucien.best/échec TLS (pas de cert pour le SNI)
openssl s_client -servername lucien.bestno peer certificate available, alert 80
openssl s_client -servername hero.vfg.lolchaîne LE valide (YE2)
grep -R lucien /etc/caddy/*.caddy (actifs)vide

Signature diagnostique à retenir :

App up + DNS OK + TLS alert sans certificat peer = vhost Caddy manquant (ou SNI non servi), pas un crash Node.


Cause racine détaillée

1. Perte des vhosts Lucien au 2026-07-08

stat /etc/caddy/Caddyfile :

  • Modify / Birth : 2026-07-08 00:34
  • Contenu réduit à admin off + imports fleet/admin

Backup de session du même jour (/opt/vfg-fleet/backups/session-20260708-010154/Caddyfile) : même contenu minimal — la perte est déjà dans l'historique ops de ce jour-là, pas une corruption du 5 septembre.

La doc fleet (VFVFGPT/deploy/vps/README.md) exigeait explicitement :

Ne pas toucher aux blocs lucien.best / stats.lucien.best dans /etc/caddy/Caddyfile.

Le pattern d'origine (setup-vps-host.sh) était : ajouter un import fleet au Caddyfile Lucien existant, pas remplacer le Caddyfile par « fleet only ».

2. Pourquoi hero.* marchait encore

Les vhosts *.vfg.lol vivent dans /etc/caddy/vfg-fleet.caddy, toujours importé. Lucien n'y a jamais dû être : il est hors fleet (systemd natif /opt/lucien, no-touch). Quand on « nettoie » le Caddyfile principal pour ne garder que les imports fleet, on coupe Lucien sans toucher au reste — d'où l'illusion que « Caddy va bien ».

3. Piège ops : admin off ⇒ reload impossible

Le Caddyfile commence par :

{
	admin off
}

Conséquence :

caddy reload --config /etc/caddy/Caddyfile
→ Error: Post "http://localhost:2019/load": dial tcp [::1]:2019: connect: connection refused

systemctl reload caddy échoue (ExecReload = caddy reload), mais le daemon continue avec l'ancienne config en mémoire. C'est trompeur : on croit « reload KO = tout cassé », alors que la fleet reste servie.

Sur ce VPS, toute évolution Caddyfile exige systemctl restart caddy, avec une courte coupure TLS globale.

Drop-in utile à connaître : /etc/systemd/system/caddy.service.d/no-resume.conf force caddy run --config /etc/caddy/Caddyfile (pas de resume state).


Correctif appliqué (rejouable)

Ops via debian@51.77.244.222 (sudo NOPASSWD). Le user deploy est dans docker mais sans sudo passwordless et sans écriture sur /opt/vfg-fleet / /etc/caddy.

A. Restaurer Lucien (sans stats GoatCounter)

GoatCounter / :8088 était inactif → on n'a pas remis stats.lucien.best (évite un vhost ACME + 502 inutile). Source de vérité app : /opt/lucien/deploy/Caddyfile.

Fichier créé : /etc/caddy/lucien.caddy

lucien.best, www.lucien.best {
	encode gzip zstd
	reverse_proxy 127.0.0.1:3000

	header {
		X-Content-Type-Options nosniff
		X-Frame-Options DENY
		X-XSS-Protection "1; mode=block"
		Referrer-Policy strict-origin-when-cross-origin
		Permissions-Policy "interest-cohort=()"
		X-Permitted-Cross-Domain-Policies none
		-Server
	}

	@images path /images/*
	header @images Cache-Control "public, max-age=2592000"

	log {
		output file /var/log/caddy/lucien.log {
			roll_size 20mb
			roll_keep 5
		}
	}
}

Caddyfile principal :

{
	admin off
}

import /etc/caddy/lucien.caddy
import /etc/caddy/vfg-fleet.caddy
import /etc/caddy/vfg-admin.caddy

Puis :

sudo cp -a /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak-pre-lucien-restore-<TS>
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy   # PAS reload
# smoke
curl -sS -o /dev/null -w "%{http_code}\n" https://lucien.best/
curl -sS -o /dev/null -w "%{http_code}\n" https://hero.vfg.lol/

Preuve cert post-fix :

subject=CN = lucien.best
notBefore=Sep  5 09:21:15 2026 GMT
notAfter=Dec  4 09:21:14 2026 GMT

B. Prep / go-live zevent.vfg.lol (même session)

Prérequis DNS déjà là : zevent.vfg.lol A → 51.77.244.222 (pas de wildcard *.vfg.lol).

  1. Tarball zevent-dash → /opt/vfg-fleet/repo/zevent-dash/
  2. docker build -t fg-zevent:latest …
  3. compose/t3-zevent.yml + env/zevent.env (ADMIN_TOKEN généré sur le VPS, hors git)
  4. docker compose … up -d → vfg-fleet-zevent-1 sur 127.0.0.1:5200
  5. Snippet /etc/caddy/zevent.caddy + import dans le Caddyfile
  6. restart caddy → ACME + reverse_proxy

Healthz public (extrait) : {"ok":true,…,"goals":{"participations":338,…}}.


Règles anti-régression (pour ne plus reproduire)

Checklist avant toute édition Caddy sur ce VPS

  1. Backup horodaté de /etc/caddy/Caddyfile et des snippets touchés.
  2. grep -n lucien /etc/caddy/Caddyfile /etc/caddy/*.caddy — doit rester non vide tant que Lucien est hébergé ici.
  3. Ne jamais remplacer le Caddyfile par « imports fleet only ». Pattern correct : Lucien (ou snippets no-touch) + import fleet + import admin.
  4. caddy validate avant application.
  5. Appliquer avec systemctl restart caddy, pas reload, tant que admin off est en place.
  6. Smoke obligatoire après coup, dans cet ordre :
    • https://lucien.best/
    • https://hero.vfg.lol/
    • https://msn.vfg.lol/
    • https://blog.vfg.lol/
    • le nouveau vhost le cas échéant
  7. Si un nouveau sous-domaine *.vfg.lol est ajouté en DNS : créer le vhost Caddy le même jour (sinon même symptôme TLS que Lucien / zevent avant prep).

Ce qu'il ne faut pas faire

Anti-patternPourquoi ça fait mal
« On simplifie le Caddyfile, Lucien est documenté no-touch donc ça ira »No-touch n'est pas magique : si le bloc n'est plus importé, il est mort
Se fier à systemctl reload alors que admin offÉchec silencieux pour l'opérateur ; ancienne config reste en RAM
Diagnostiquer « HTTPS down » en ne testant que l'app localeOn rate 100 % des pannes edge
Utiliser deploy@ pour éditer /etc/caddyPas de sudo NOPASSWD → scripts qui pendent sur mot de passe
Remettre stats.lucien.best sans GoatCounterACME + reverse_proxy vers :8088 mort = bruit et 502
docker compose … --remove-orphans en lançant seulement t3-zevent.ymlRisque de cueillir des « orphans » qui sont en fait le reste de la fleet

Structure cible recommandée (modulaire)

/etc/caddy/Caddyfile          # global options + imports UNIQUEMENT
/etc/caddy/lucien.caddy       # no-touch mémorial
/etc/caddy/vfg-fleet.caddy    # *.vfg.lol applicatifs
/etc/caddy/vfg-admin.caddy    # surfaces admin
/etc/caddy/zevent.caddy       # ZDASH (éphémère / événement)

Leçon Citadelle (déjà dans les commentaires VPS, 2026-06-30) : snippets séparés = récupération rapide. Même leçon pour Lucien.


Accès SSH ops (rappel)

UserUsagesudo
debianedits Caddy, /opt/vfg-fleet, systemctlNOPASSWD
deploydocker group, CI éventuellemot de passe requis ; peu d'écriture fleet

Alias local utile : Host vfg-vps → deploy@51.77.244.222 (identité ~/.ssh/id_ed25519). Pour Caddy : préférer explicitement debian@51.77.244.222.


État final vérifié (2026-09-05 ~12:21 CEST)

URLCodeNote
https://lucien.best/200cert LE frais
https://zevent.vfg.lol/200ZDASH
https://zevent.vfg.lol/healthz200JSON ok
https://hero.vfg.lol/200non régressé
https://blog.vfg.lol/200non régressé
lucien-site.serviceactiveinchangé
caddy.serviceactiverestart appliqué

Hors scope volontaire : restauration stats.lucien.best (GoatCounter), durcissement sudo deploy, réactivation API admin Caddy (admin off).


Liens utiles

  • Source Caddy Lucien : /opt/lucien/deploy/Caddyfile (repo Lucien/deploy/)
  • Doc fleet no-touch : VFVFGPT/deploy/vps/README.md § « Ce qu'il ne faut pas toucher »
  • Runbook ZDASH : Zevent2026/zevent-dash/README.md § déploiement VPS
  • Snippet Caddy ZDASH : zevent-dash/caddy-snippet.txt

source markdown

Concepts de cet article

CaddyVPSCitadelle

Partage le plus de concepts avec