Utiliser Coolify pour déployer des applications avec wildcard SSL sur des sous-domaines dynamiques

By Hugo LassiègeOct 20, 20255 min read

Dans le dernier billet nous avons vu comment déployer une application multi tenant, faite avec Nuxt, sur Coolify de façon à pouvoir dynamiquement gérer des sous domaines *.monapp.com avec une seule application.

Si vous n'avez pas lu ce billet, je vous conseille de le lire d'abord, notamment pour vous rafraîchir la mémoire sur le vocabulaire et parce que je compte pas le refaire ici.

Par contre, j'avais un peu zappé un détail qui m'a couté quelques jours supplémentaires de travail : le SSL

C'est bien beau de pouvoir gérer dynamiquement des sous domaines, mais si les certificats SSL sont tous invalides, ça inspire pas trop confiance.

Donc désormais, on va voir :

  • comment configurer un resolver de certificat qui fait du DNS challenge
  • comment l'utiliser pour générer un certificat wildcard SSL pour l'ensemble des sous domaines
  • comment ensuite donner la possibilité aux utilisateurs d'utiliser leur propre domaine customs avec leur sous domaine

Un résolveur de certificat

Autant vous l'avouer, je vous aurais pas parié que j'allais écrire un article sur ce sujet il y a quelques semaines parce que c'est loin d'être mon sujet d'expertise.

Mais bon, je me suis fait mal aux cheveux pendant quelques jours, et aussi parce que j'ai eu du mal à comprendre la doc, donc autant en faire profiter les autres.

Bref, revenons surtout au problème de base : on veut un certificat SSL sur chaque sous domaine qu'on va utiliser dynamiquement, donc user1.monapp.com, user2.monapp.com etc...

En temps normal dans Coolify, on déploie une application sur un seul domaine (ou une liste bien définie en tout cas).

Au moment du déploiement, on demande à Let's encrypt de générer un certificat. Celui-ci va effectuer un challenge HTTP pour controler qu'on possède bien le domaine en question. Il va vérifier pour ca un fichier qu'on aura mis sur le serveur, par exemple http://user1.monapp.com/.well-known/acme-challenge/[token]

C'est transparent pour nous avec Coolify et Traefik, tout est géré sans aucun souci.

Le problème c'est qu'avec des sous domaines dynamiques cette méthode ne marche pas très bien.

Soit on les liste dans Coolify un par un à chaque nouvel ajout. C'est une possibilité mais j'ai pas trouvé comment automatiser ça. Dans tout les cas, ça induit beaucoup de validations HTTP à faire, à renouveller régulièrement.

Soit on demande un certificat wildcard. C'est une solution élégante, on ne demandera qu'une seule validation et on s'épargne le temps d'attente pour la disponibilité du certificat. Une fois qu'il est disponible, il fonctionne pour tous les sous-domaines.

Mais avec un certificat wildcard, la validation HTTP challenge ne marche plus. Pourquoi ? Parce que Let's Encrypt ne peut pas vérifier un fichier sur un domaine qui n'existe pas encore. Il faut donc prouver qu'on contrôle le domaine autrement : en modifiant directement le DNS.

C'est le DNS challenge. Au lieu de vérifier un fichier sur le serveur, Let's Encrypt va vérifier un enregistrement TXT dans une zone DNS (genre _acme-challenge.monapp.com).

Et pour ça, Traefik doit pouvoir piloter le serveur DNS pour ajouter automatiquement un record sur notre DNS.

Heureusement, Traefik sait faire ça en utilisant les APIs publiques des providers. Enfin, pour autant que ce provider soit dans la liste des providers géré par Lego

Mauvaise surprise, au moment où j'ai travaillé là-dessus, mon provider "hostinger" n'étais pas géré par Traefik. J'ai donc du migrer sur bunny.net. C'est un provider Européenn sur lequel je gère aussi mon CDN et mon storage. (depuis, hostinger est devenu disponible dans Traefik)

Configurer Traefik pour ajouter un certresolver

Pour configurer Traefik, il faut sélectionner son serveur dans Coolify:

menu latéral
menu latéral

Puis aller dans Proxy

menu proxy
menu proxy

Et ensuite il est possible de rajouter des command qui vont nous permettre de configurer un cert resolver supplémentaire :

      - '--certificatesresolvers.letsencrypt-dns.acme.dnschallenge.provider=bunny'
      - '--certificatesresolvers.letsencrypt-dns.acme.dnschallenge.delaybeforecheck=0'
      - '--certificatesresolvers.letsencrypt-dns.acme.storage=/traefik/acme.json'     

J'ai également rajouté des labels

      - traefik.http.routers.traefik.tls.domains[0].main=writizzy.io
      - traefik.http.routers.traefik.tls.domains[0].sans=*.writizzy.io      

Attention, il faut penser aussi à rajouter la clé d'API bunny dans environment :

    environment:
      - BUNNY_API_KEY=maclé

Il suffit de relancer Traefik, de checker les logs et cette étape est terminée.

Utiliser le nouveau traefik resolver

C'est très bien tout ça, mais il faut désormais expliquer à notre application qu'il faut utiliser ce resolver. Pour ca on retourne sur la configuration de l'application en question, dans les labels du container et on passe de ça :

traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.tls.certresolver=letsencrypt

à ça :

traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.tls.certresolver=letsencrypt-dns

ow8g70707sockck8sgo8kc55g est un nom aléatoire généré par Coolify, c'est certainement pas le même pour vous...

Gérer des custom domains

Imaginons qu'un utilisateur souhaite utiliser sondomaine.com à la place de myuser.myapp.com. C'est ce qu'on appelle un custom domain et c'est utilisé très fréquemment dans beaucoup de SAAS que vous utilisez régulièrement.

En général pour cela, il faut paramétrer un record de type CNAME qui pointe de sondomaine.com vers myuser.myapp.com

Mais, forcément Traefik ne sait pas quoi faire sur une requête sondomaine.com, c'est un domaine qui lui est inconnu donc le résultat sera un magnifique message d'erreur "no available server".

Pour ces domaines, on va donc demander à notre container multi tenant de rajouter un routeur custom qui va capter tout le trafic inconnu :

traefik.http.routers.https-custom.rule=HostRegexp(`^.+$`)
traefik.http.routers.https-custom.entryPoints=https
traefik.http.routers.https-custom.service=https-0-zgwokkcwwcwgcc4gck440o88
traefik.http.routers.https-custom.tls.certresolver=letsencrypt
traefik.http.routers.https-custom.tls=true
traefik.http.routers.https-custom.priority=1

La regexp nous permet de dire que tout trafic recu doit passer par ce routeur custom, avec une priorité de 1 (donc très faible) pour éviter de rentrer en conflit avec tous les autres containers déployés sur le même Traefik. Ici on notera que l'on repasse en résolveur letsencrypt de type http challenge. Pourquoi ? Parce que pour les custom domains, on connaît le domaine exact au moment de la demande de certificat (c'est l'utilisateur qui nous le donne), donc pas besoin de wildcard et le HTTP challenge redevient possible.

Et normalement, le tour est joué.

Mais je peux vous dire que j'ai pris plus de temps à comprendre tout ça qu'à l'écrire...


Share this:

Written by Hugo Lassiège

Software engineer, ex-freelance, ex-cofounder, ex-CTO. I love building things, sharing knowledge and helping others.

Copyright © 2025
 Eventuallycoding
  Powered by Bloggrify