03 nov. 2025·8 min de lecture

Erreurs SSL uniquement en production : corriger les incompatibilités de mode SSL

Les erreurs SSL qui n'apparaissent qu'en production proviennent souvent d'un mode SSL incompatible ou de certificats manquants. Découvrez les erreurs courantes dans les chaînes de connexion et comment tester localement.

Erreurs SSL uniquement en production : corriger les incompatibilités de mode SSL

Pourquoi les erreurs SSL apparaissent uniquement en production

Les erreurs SSL qui n'apparaissent qu'en production signifient généralement que votre app ne se connecte pas au même type d'environnement de base de données que celui que vous avez en local. Le code peut être identique, mais la production change les règles réseau et de sécurité.

Sur un portable, Postgres tourne souvent sur localhost, parfois dans Docker, et les connexions TCP non chiffrées sont autorisées. En production, un service Postgres géré se trouve typiquement derrière un load balancer, un proxy ou un pooler de connexion, impose le chiffrement et attend un mode SSL précis.

Quand une base hébergée indique « SSL required », elle refuse les connexions non chiffrées. Certains fournisseurs vont plus loin et exigent la validation du certificat, pas seulement le chiffrement. C'est là que des réglages comme sslmode=require vs sslmode=verify-full entrent en jeu : require chiffre la connexion, tandis que verify-full vérifie aussi le certificat du serveur et que le nom d'hôte correspond.

Même sans changement de code, le comportement peut diverger parce que les configs et les valeurs par défaut diffèrent. Votre .env local peut ne contenir aucun paramètre SSL, tandis que les variables d'environnement en production les incluent (ou votre plateforme les injecte). Certains drivers ont par défaut prefer (tente le SSL puis retombe en non‑SSL), comportement qui peut marcher silencieusement en local mais échouer quand la production exige une vérification stricte.

Les détails réseau changent aussi. Les connexions en production passent souvent par un proxy, PgBouncer ou un endpoint privé. Cela peut modifier le nom d'hôte contacté et le certificat présenté. Une chaîne de connexion utilisant une adresse IP peut fonctionner avec require mais échouer avec verify-full, parce que les certificats correspondent rarement à des IP brutes.

Les symptômes typiques incluent des erreurs comme :

  • « SSL is required » ou « no pg_hba.conf entry for host ... SSL off »
  • « certificate verify failed », « unable to get local issuer certificate », ou « self signed certificate »
  • « hostname mismatch » ou « certificate does not match host »
  • Fonctionne en local et en preview, mais échoue après le déploiement sur la base de données de production
  • Échecs intermittents lorsqu'un pooler ou proxy est sur le chemin

Un exemple réaliste : en local vous vous connectez à postgres://localhost:5432/app sans paramètre SSL. En production, la base gérée vous fournit une URL comme postgres://user:[email protected]:5432/app?sslmode=verify-full. Si votre app supprime le paramètre (ou utilise sslmode=disable), la production refuse la connexion même si les mêmes requêtes fonctionnaient chez vous.

10 premières minutes : obtenir l'erreur exacte et le contexte

Le chemin le plus rapide vers une correction est d'arrêter de deviner et de capturer l'échec exact. De petits détails dans le message, l'hôte et l'endroit où le code s'exécute pointent généralement directement vers le décalage.

Copiez le texte complet de l'erreur, y compris les lignes « caused by », et notez la fenêtre temporelle précise. Si vous avez des logs centralisés, filtrez autour de ce timestamp pour voir ce qui s'est passé juste avant l'échec.

Ensuite, notez l'endroit où la connexion est ouverte. Une connexion créée pendant une requête web peut se comporter différemment d'une connexion créée dans un worker en arrière‑plan, une fonction serverless ou un job planifié. Vérifiez aussi si l'échec est immédiat ou seulement après une période. « Ça marche un moment puis ça meurt » pointe souvent vers le pooling, des timeouts, ou quelque chose qui tourne (certificats, routes ou endpoints).

Capturez ces éléments de base avant de changer quoi que ce soit :

  • Texte d'erreur complet et stack trace (la première erreur, pas la dernière tentative)
  • Fenêtre temporelle et ID de requête ou job (si disponible)
  • Emplacement d'exécution (serveur web, serverless, cron, worker)
  • Si ça échoue immédiatement ou après quelques requêtes réussies
  • L'hôte de base de données auquel vous vous connectez réellement

Ce dernier point compte beaucoup. De nombreuses apps ont plusieurs URLs de base de données flottant (vars d'env, gestionnaires de secrets, valeurs par défaut codées en dur, environnements de preview). Loggez l'hôte résolu au runtime (sans les identifiants). Si la production se connecte à un hôte différent de celui que vous pensez, ses exigences SSL et ses certificats peuvent aussi être différents.

Vérifiez aussi si l'échec concerne toutes les régions de production ou seulement une. Une panne sur une seule région peut signifier un endpoint différent, un chemin réseau différent ou une chaîne de certificats différente.

Modes SSL en langage simple (et pourquoi ça compte)

La plupart des échecs SSL uniquement en production viennent d'un désaccord sur le degré d'exigence de la connexion. Les bases locales tolèrent souvent le non‑SSL ou un SSL « best effort ». Les Postgres managés imposent couramment le SSL.

Le SSL fait deux choses distinctes, et il est utile de les séparer :

  • Chiffrement : empêche que le trafic soit lu en transit.
  • Vérification d'identité : prouve que vous parlez au vrai serveur de base de données.

Le « mode SSL » contrôle à quel point vous êtes strict sur ces deux aspects.

Les modes SSL courants

Selon les drivers/clients Postgres, vous verrez généralement :

  • disable : ne jamais utiliser SSL
  • prefer : tenter SSL d'abord, retomber en non‑SSL si SSL échoue
  • require : toujours chiffrer la connexion, sans vérifier strictement l'identité du serveur
  • verify-ca : utiliser SSL et confirmer que le certificat s'enchaîne vers une CA de confiance
  • verify-full : utiliser SSL, valider la CA et vérifier que le nom d'hôte correspond au certificat

Si vous choisissez un mode strict comme verify-full sans la bonne chaîne de certificats ou sans le bon nom d'hôte, vous pouvez toujours obtenir des erreurs SSL même si la connexion est chiffrée.

Validation du nom d'hôte : la cause fréquente de rupture en production

La validation du nom d'hôte exige que le certificat serveur corresponde à l'hôte auquel vous vous connectez. Si votre chaîne de connexion utilise une adresse interne, une IP ou un nom DNS différent de celui pour lequel le certificat a été émis, verify-full échoue. Cela arrive souvent quand la production vous route via un proxy ou un endpoint privé.

Les valeurs par défaut diffèrent selon le driver (et ça compte)

Les valeurs par défaut varient par driver. L'un peut se comporter comme prefer par défaut, un autre comme require, et votre cloud peut refuser le non‑SSL. Avant de changer quoi que ce soit, répondez à ces questions :

  • La production impose‑t‑elle le SSL ou l'autorise‑t‑elle ?
  • Utilisez‑vous require ou verify-full ?
  • Si vous utilisez verify-full, l'hôte dans la chaîne de connexion correspond‑il au certificat ?
  • D'où vient le certificat CA en production (fichier, variable d'env, magasin de confiance système) ?
  • Mon setup local teste‑t‑il réellement le même mode, ou retombe‑t‑il silencieusement ?

Erreurs courantes dans les chaînes de connexion qui causent des échecs

Les problèmes SSL en production sont généralement de petites discordances entre ce que la base attend et ce que l'app envoie.

1) Mauvais nom de paramètre pour votre driver

Différents drivers lisent différents réglages SSL. Certains regardent sslmode, d'autres lisent ssl=true, et certains attendent un objet plutôt qu'une chaîne. Si vous utilisez le mauvais nom, le driver peut l'ignorer et retomber sur une valeur par défaut.

C'est particulièrement courant dans les projets générés par l'IA car le code mélange souvent des exemples de différents écosystèmes. Vous pouvez voir sslMode à un endroit et sslmode à un autre. L'un est pris en compte, l'autre ignoré.

2) Choisir le mauvais niveau d'exigence (require vs verify-full)

Deux schémas d'échec reviennent souvent :

  • Vous mettez sslmode=require, mais votre fournisseur attend une validation de certificat (verify-ca ou verify-full).
  • Vous mettez sslmode=verify-full, mais le nom d'hôte ou la chaîne de certificats ne correspondent pas, donc la production échoue alors que le local semblait tolérer des réglages plus laxistes.

3) CA manquante quand vous utilisez verify-ca ou verify-full

Si vous vérifiez les certificats, vous avez généralement besoin d'un certificat CA (par sslrootcert ou une option spécifique au driver). Sans lui, les erreurs ressemblent à « self signed certificate », « unable to get local issuer certificate » ou « certificate verify failed ».

Un exemple simple de ce que l'on vise (les noms varient selon le driver) :

... sslmode=verify-full sslrootcert=/path/to/ca.pem

4) Utiliser une adresse IP au lieu d'un nom d'hôte

verify-full vérifie que le certificat correspond au nom d'hôte. Si votre URL utilise une IP comme 10.0.0.12, mais que le certificat est émis pour db.myprovider.com, la vérification échoue.

5) Variables d'environnement qui écrasent ce que vous pensez avoir défini

Il est courant de modifier du code, redéployer et toujours échouer parce que la plateforme injecte DATABASE_URL (ou une autre variable) qui écrase vos nouveaux réglages. L'app continue d'utiliser l'ancienne URL.

6) Copier‑coller l'URL et corrompre le mot de passe

Les mots de passe contenant des caractères spéciaux (@, :, #, %) doivent être encodés dans les URLs. Copier‑coller depuis des dashboards, des outils de chat ou des fichiers .env peut les altérer et provoquer des erreurs d'authentification qui ressemblent à des problèmes SSL.

Si vous voulez des vérifications rapides avant d'aller plus loin :

  • Confirmez que votre driver lit bien le réglage SSL que vous avez utilisé (nom et casse).
  • Adaptez sslmode à ce que la production exige, pas à ce que le local tolère.
  • Si vous vérifiez, assurez‑vous que le certificat CA est présent et accessible au runtime.
  • Utilisez un nom d'hôte (pas une IP) si vous utilisez verify-full.
  • Vérifiez qu'aucune variable d'environnement n'écrase votre chaîne de connexion voulue.

Pas à pas : construire correctement les réglages de connexion pour la production

Sécuriser votre couche base de données
Nous sécurisons la couche base de données en verrouillant secrets exposés et risques d'injection courants dans les setups précipités.

La correction commence par rendre explicites et intentionnels les réglages finaux de connexion.

1) Lister tous les endroits d'où peut venir la config

Avant de changer quoi que ce soit, listez toutes les sources qui peuvent influencer la connexion : variables d'environnement runtime, secrets de plateforme, fichiers de config de l'app, injection au build, et réglages de l'ORM.

Puis confirmez laquelle prévaut si le même réglage apparaît deux fois. Beaucoup de bugs « mode SSL » viennent du fait qu'on a édité le mauvais endroit.

2) Créer une unique source de vérité pour la chaîne de connexion

Choisissez une représentation canonique pour la production, généralement une seule DATABASE_URL. Supprimez les autres réglages dispersés (ou dérivez‑les strictement de cette URL).

Une bonne URL de production indique explicitement l'intention SSL :

postgres://USER:PASSWORD@HOST:5432/DBNAME?sslmode=verify-full

Si votre fournisseur exige le chiffrement mais pas la vérification stricte, utilisez sslmode=require. S'il exige la vérification complète, utilisez verify-full et configurez les certificats et la vérification du nom d'hôte.

3) Choisir le mode SSL en fonction des exigences du fournisseur

Ne faites pas de supposition. Choisissez un mode en vous basant sur la documentation du fournisseur et notez la raison (un court commentaire près de la variable suffit). Cela évite les dérives futures.

4) Ajouter le certificat CA et le nom serveur attendu si nécessaire

Pour verify-ca et verify-full, vous aurez souvent besoin du certificat CA du fournisseur disponible au runtime (chemin fichier ou contenu passé directement selon le driver).

Pour verify-full, le nom du serveur doit correspondre au certificat. Si vous vous connectez par IP ou via un alias, vous pouvez rencontrer un mismatch même avec les bonnes credentiels.

5) Logger une version sûre et rédigée de la config finale

Logguez ce que l'app utilisera après tous les overrides, mais ne logguez jamais mots de passe, tokens ou contenus complets de certificats.

DB host=prod-db.example.com port=5432 db=app sslmode=verify-full sslrootcert=set user=app_user password=REDACTED

Cette unique ligne au démarrage suffit souvent à repérer un hôte incorrect, un chemin CA manquant ou un mode SSL inattendu.

Pas à pas : tester le même mode SSL en local

Le but est de faire en sorte que votre portable se comporte comme la production. Si la production exige SSL et que votre local se connecte silencieusement sans, vous enverrez un échec.

1) Faire correspondre les entrées de production, pas seulement les valeurs

Commencez par lancer localement avec les mêmes entrées que la production : la chaîne de connexion complète, le mode SSL et les chemins des certificats. Évitez de mélanger « defaults locaux » et « valeurs prod » dans la même exécution.

Un pattern pratique est un fichier local dédié, comme .env.prodlike, et lancer l'app seulement avec ce fichier chargé.

# Exemple (les noms varient selon framework/driver)
DATABASE_URL=postgres://user:[email protected]:5432/appdb?sslmode=verify-full
PGSSLMODE=verify-full
PGSSLROOTCERT=./certs/prod-ca.pem

2) Importer la même chaîne CA

Exportez ou téléchargez le bundle CA utilisé en production et stockez‑le localement (par exemple ./certs/prod-ca.pem). Pointez votre driver vers ce fichier. Sans lui, verify-ca et verify-full échoueront même si tout le reste est correct.

3) Forcer le même mode SSL et éliminer les fallbacks

Certaines bibliothèques essaient plusieurs options ou retombent en non‑SSL si SSL échoue. Cela masque le vrai problème. Rendre le mode SSL explicite et surveiller les logs indiquant des retries avec des réglages TLS différents. Si vous voyez « retrying without TLS/SSL », considérez cela comme un test échoué.

4) Faire correspondre le nom d'hôte que verify-full vérifie

Si vous vous connectez localement en utilisant une IP, localhost ou un nom DNS différent de la production, verify-full peut échouer même avec la bonne CA. Utilisez le même nom DNS que la production. Si nécessaire, mappez‑le localement via hosts/DNS.

5) Vérifier en dehors de l'app

Avant d'accuser votre code, testez la connexion avec un client simple utilisant les mêmes réglages. Si cela échoue, votre app échouera aussi.

Prouver la correction : tests rapides avant de redéployer

Sauver une app générée par l'IA
Si Lovable, Bolt, v0, Cursor ou Replit a généré votre code, nous le rendons prêt pour la production.

Après avoir changé les réglages SSL, prouvez que la connexion marche avant de livrer. Isolez le problème du code applicatif pour ne pas deviner si le changement l'a résolu.

Un passage de validation court :

  • Connectez‑vous depuis le même environnement que la production (même image de container ou même VM) avec un client minimal.
  • Affichez les réglages finaux que votre app utilisera au runtime (sanitized) et confirmez que le mode SSL est celui attendu.
  • Confirmez que le runtime peut lire le fichier CA configuré (le fichier existe, permissions correctes).
  • Comparez les versions du driver entre local et production (les defaults changent).
  • Si vous utilisez des containers, vérifiez que l'image inclut les bundles CA système (problème courant avec des images minimales).

Après une connexion réussie, cassez volontairement la configuration pour confirmer ce qui est validé :

  • Changez le nom d'hôte pour un mauvais et confirmez que l'erreur devient DNS ou connexion.
  • Passez à un mode plus strict (verify-full) sans la bonne CA et confirmez les échecs de validation de certificat.
  • Pointez le chemin CA vers un fichier manquant et confirmez une erreur de fichier/permission.

Si ces changements n'affectent pas l'erreur, vous n'êtes peut‑être pas sur le même chemin de code qu'en production. Souvent cela signifie que les réglages SSL sont ignorés ou écrasés.

Scénario d'exemple : une base gérée impose le SSL en production

Un cas courant : tout fonctionne sur votre portable, vous déployez, et l'app ne peut plus se connecter. Les logs affichent « SSL handshake failed », « certificate verify failed », ou « server does not support SSL, but SSL was required ».

Ce qui se passe souvent avec un Postgres managé : en local vous lancez Postgres sans SSL, ou votre client local tolère des réglages faibles. En production, la base gérée exige le SSL et votre app tourne dans un container qui n'a pas les bons certificats CA.

La cause racine est généralement un mismatch entre trois éléments : le nom d'hôte contacté, le mode SSL choisi et la capacité du runtime à vérifier la chaîne de certificats.

Par exemple, vous déployez avec :

DATABASE_URL=postgres://app:[email protected]:5432/prod?sslmode=verify-full

Cela peut échouer même si les identifiants sont corrects. verify-full vérifie que le certificat est de confiance et qu'il correspond au nom d'hôte. Une adresse IP (ou un alias) ne correspond souvent pas au nom DNS du certificat.

Une correction minimale : se connecter en utilisant le nom d'hôte attendu par le certificat et s'assurer que le runtime peut le valider :

DATABASE_URL=postgres://app:[email protected]:5432/prod?sslmode=verify-full\u0026sslrootcert=/etc/ssl/certs/ca-certificates.crt

Si vous ne pouvez pas fournir un chemin de bundle CA (ou si votre image en est dépourvue), un contournement temporaire consiste à passer à sslmode=require pour chiffrer le trafic sans vérification stricte. Cela peut débloquer la situation, mais c'est moins sûr que la vérification complète.

Pour éviter une régression :

  • Utilisez le même format de nom d'hôte en local que celui prévu en production (nom DNS, pas IP).
  • Choisissez le mode SSL intentionnellement et documentez le choix.
  • Assurez‑vous que l'image runtime contient les certificats CA et sachez où se trouve le bundle.
  • Ajoutez un test de fumée qui s'exécute dans la même image container que celle déployée.

Pièges courants dans les apps générées par l'IA (Lovable, Bolt, v0, Cursor, Replit)

Identifier le vrai problème SSL
Envoyez-nous votre dépôt et nous identifierons rapidement l'incompatibilité SSL et de configuration.

Les apps générées par des outils comme Lovable, Bolt, v0, Cursor et Replit échouent souvent d'une manière spécifique : les réglages de base de données semblent plausibles, mais ils sont devinés. D'où des erreurs SSL après le déploiement.

Un schéma fréquent est un champ de chaîne de connexion inventé que votre driver n'utilise pas. Une app peut définir ssl=true ou tls=true en supposant que cela force le SSL, alors que le driver ne respecte que sslmode=require (ou un objet ssl structuré). En local la base accepte le TCP non chiffré, donc vous ne le remarquez pas. En production, le Postgres managé exige SSL et la connexion échoue.

Un autre motif est l'override caché. Ces projets définissent souvent la config base de données à plusieurs endroits, puis vous n'en corrigez qu'un. En local, un fichier .env l'emporte. En production, la plateforme utilise un autre nom de variable, injecte sa propre valeur, ou un défaut au build prend la main.

La refactorisation la plus simple pour éviter des pannes répétées est ennuyeuse mais efficace : choisissez une source de vérité (généralement DATABASE_URL), parsez‑la une fois, et transmettez les mêmes réglages à chaque composant qui touche la base (runtime, migrations, jobs en arrière‑plan).

Checklist rapide et prochaines étapes

Quand une erreur SSL n'apparaît qu'en production, ce n'est généralement pas aléatoire. C'est une petite discordance entre ce que votre app demande et ce que la base attend.

Checklist :

  • Confirmez que l'hôte et le port correspondent bien à l'endpoint du service géré (pas d'ancien hôte dev, pas de port manquant).
  • Vérifiez que le mode SSL voulu est défini (par ex. require vs verify-full) et que votre driver le lit vraiment.
  • Si vous vérifiez les certificats, assurez‑vous que le certificat CA est présent dans le runtime (VM, container, serverless) et que le chemin est correct.
  • Vérifiez que le nom d'hôte dans la chaîne de connexion correspond au nom figurant sur le certificat (cause fréquente d'échec verify-full).
  • Assurez‑vous d'avoir une seule source de vérité pour la config, pas des overrides disséminés dans le code et les dashboards.

Si vous continuez à entendre « ça marche sur ma machine », comparez les versions des drivers et les stores de confiance. Une image container peut ne pas inclure un bundle CA, ou la production peut utiliser une librairie cliente avec des valeurs par défaut différentes.

Une étape légère suivante est un préflight au démarrage qui échoue vite avec un message clair :

Idée de preflight : au démarrage, connectez‑vous avec la même chaîne de connexion.
Si cela échoue, logguez : sslmode, host, et si un fichier CA a été trouvé.
Quittez pour que le déploiement échoue tôt au lieu de timeout plus tard.

Si vous avez hérité d'un prototype généré par l'IA et que la logique SSL/config est dispersée dans le code, FixMyMess (fixmymess.ai) se concentre sur le diagnostic et la réparation de ces pannes spécifiques à la production. Un audit rapide identifie les chaînes de connexion conflictuelles, la gestion des CA manquante et les defaults dangereux pour que l'app se comporte de façon prévisible en production.

Questions Fréquentes

Pourquoi les erreurs SSL apparaissent-elles seulement après le déploiement en production ?

Cela signifie généralement que la production se connecte via un endpoint géré qui impose SSL et possiblement la vérification des certificats, alors que votre base locale accepte le TCP simple ou effectue un fallback discret vers du non‑SSL. Le code peut être identique, mais les valeurs par défaut et le chemin réseau diffèrent.

Quelle est la différence entre sslmode=require et sslmode=verify-full ?

require chiffre la connexion mais ne vérifie pas strictement l'identité du serveur. verify-full chiffre la connexion, valide la chaîne de certificats et vérifie que le nom d'hôte auquel vous vous connectez correspond au certificat — ce qui échoue souvent si vous utilisez une IP, un nom de proxy ou un mauvais nom DNS.

Pourquoi ça échoue avec « hostname mismatch » quand j'utilise une adresse IP ?

Parce que verify-full attend une correspondance exacte du nom d'hôte et les certificats sont rarement émis pour des adresses IP brutes. Utilisez le nom DNS du fournisseur qui figure sur le certificat, pas une IP privée ou un alias interne, quand vous avez besoin d'une vérification stricte.

Que signifie « unable to get local issuer certificate » en production ?

Cela signifie généralement que le runtime ne peut pas vérifier la chaîne de certificats. Causes courantes : certificats CA manquants dans le container/VM, chemin sslrootcert incorrect, ou image de base minimale sans bundle CA système.

Que dois‑je logger pour diagnostiquer cela sans divulguer de secrets ?

Consignez l'hôte résolu, le port, le nom de la base et le mode SSL au démarrage, en masquant les identifiants. Capturez aussi la première erreur complète et toute ligne « caused by », car les retries peuvent masquer la vraie cause.

Pourquoi ça échoue encore après avoir « activé SSL » dans ma config ?

Souvent le driver ignore vos réglages SSL à cause d'un mauvais nom d'option ou d'une casse incorrecte, ou bien une variable d'environnement comme DATABASE_URL écrase ce que vous avez modifié dans le code. Une autre cause fréquente est un proxy/pooler en production présentant un certificat différent de celui attendu.

Comment faire pour que mon environnement local se comporte comme la production pour le SSL ?

Exécutez localement la même chaîne de connexion style production et le même mode SSL, y compris les chemins de certificats. L'objectif est d'éviter que des comportements de type « prefer » ou des fallbacks masquent le problème jusqu'au déploiement.

Cela peut‑il être causé par mon DATABASE_URL ou une erreur de copier/coller ?

D'abord confirmez l'URL de base de données que l'app utilise réellement au runtime, car les plateformes injectent souvent ou écrasent cette variable. Ensuite vérifiez que le driver lit bien le paramètre que vous définissez et que les caractères spéciaux du mot de passe sont encodés dans l'URL, car une URL malformée peut ressembler à une erreur SSL.

PgBouncer ou un proxy peut‑il provoquer des échecs SSL intermittents ?

Oui. Si la production route le trafic via PgBouncer, un load balancer ou un endpoint privé, le nom d'hôte et le certificat présentés peuvent différer de ce que vous avez testé. Ça peut provoquer des échecs d'handshake intermittents ou des erreurs de vérification si le pooler fait tourner des endpoints ou utilise une chaîne de certificats différente.

Quand devrais‑je demander à FixMyMess d'intervenir ?

Si le projet est généré par l'IA ou a la config dispersée, la voie la plus rapide est un audit ciblé pour retrouver les réglages effectifs et corriger les incompatibilités. FixMyMess peut faire un audit gratuit du code et réparer la logique de connexion, le traitement des certificats et les defaults dangereux ; la plupart des corrections sont faites en 48–72 heures avec une option de reconstruction propre en ~24 heures si la base de code est trop désordonnée.