Python requests certificate verify failed : corriger certifi et le CA bundle système

PYTHON · TLS · DEVOPS

L’erreur python requests certificate verify failed apparaît souvent quand requests ne lit pas le même magasin de certificats que ton système. Voici comment identifier la vraie source, corriger sans verify=False et fiabiliser le fix en prod.

Tu vois passer python requests certificate verify failed alors que curl, apt ou même ton navigateur ne se plaignent de rien.

Illustration Python requests certificate verify failed avec certifi et le CA bundle système
Illustration du décalage entre certifi et le bundle CA système dans Python requests.

Ce décalage est fréquent sur Debian, Ubuntu, runners CI, virtualenv et images Docker. Le plus souvent, requests vérifie le certificat avec certifi, alors que le système utilise un bundle différent, enrichi ou patché.

Le piège, c’est que le bug n’est pas toujours côté serveur distant.

Il peut venir d’une CA interne absente, d’un proxy TLS d’entreprise, d’un container sans ca-certificates, d’un venv créé avec pip ou d’une variable d’environnement qui pointe vers un mauvais fichier.

💡

En 30 secondes

Si requests échoue mais curl passe, compare d’abord certifi.where(), ssl.get_default_verify_paths() et REQUESTS_CA_BUNDLE. Tu sauras vite quel store casse la chaîne.

Réponse rapide

L’erreur python requests certificate verify failed signifie souvent que Python requests ne lit pas le même bundle CA que ton système.

En pratique, il faut identifier le store réellement utilisé, ajouter la bonne CA au bon endroit, ou pointer explicitement requests vers le bundle attendu avec REQUESTS_CA_BUNDLE ou verify=....

Python requests certificate verify failed : les symptômes à confirmer

Le signal le plus classique est simple. curl https://service-interne répond correctement. Le même appel via requests.get() remonte une SSLError. Dans un autre contexte, le script passe en local mais casse dans GitLab CI, un container Docker ou un cron lancé avec un environnement plus pauvre.

Autre indice utile, le message mentionne unable to get local issuer certificate, self signed certificate in certificate chain ou certificate verify failed. Ces variantes pointent presque toujours vers une chaîne de confiance incomplète, pas vers un timeout réseau brut.

🖥️

VM Debian/Ubuntu

Le système connaît la CA interne, mais le venv Python continue à utiliser certifi.

🐳

Container

L’image n’a pas ca-certificates ou n’embarque pas la CA d’entreprise.

🏢

Proxy TLS

Le proxy d’entreprise réémet les certificats avec une autorité interne invisible pour requests.

🚦

CI/CD

Le runner n’hérite pas des mêmes variables ni du même magasin cert que le shell interactif.

Pourquoi le CA bundle système diffère de celui de Python requests

Le point clé, c’est l’origine de requests. Installé via pip, il s’appuie généralement sur certifi. Ce paquet embarque sa propre liste de CA publiques. C’est pratique pour la portabilité. En revanche, il ignore les ajouts spécifiques faits par ton entreprise dans le store système.

À l’inverse, certaines distributions packagent python3-requests avec un comportement adapté au système.

Sur Debian ou Ubuntu, tu peux donc observer deux mondes différents. Le Python système suit le bundle OS. Le venv créé à partir de pip repart sur certifi.

Résultat, un service interne fonctionne avec un script A mais casse avec un script B sur le même host.

Le problème se voit aussi en conteneur.

Tu peux avoir ajouté la CA interne sur le nœud Kubernetes ou l’hôte Docker, mais pas dans l’image applicative. Dans ce cas, le système hôte est sain, le pod ne l’est pas.

C’est un écart d’artefact, pas un bug réseau.

⚠️

Piège courant

Ne conclus pas trop vite que le serveur distant a un certificat cassé. Si le navigateur, openssl s_client ou curl valident la chaîne, le vrai problème est souvent ton contexte d’exécution Python.

Méthode de diagnostic fiable avant de corriger

Commence par afficher les chemins réellement utilisés. Tu veux savoir quel Python tourne, quelle version de requests est chargée, quel fichier CA certifi expose et quels sont les chemins par défaut de la librairie SSL.

python3 - <<'PY'
import os, ssl, requests, certifi
print('python requests =', requests.__version__)
print('requests module =', requests.__file__)
print('certifi bundle =', certifi.where())
print('ssl defaults =', ssl.get_default_verify_paths())
print('REQUESTS_CA_BUNDLE =', os.getenv('REQUESTS_CA_BUNDLE'))
print('CURL_CA_BUNDLE =', os.getenv('CURL_CA_BUNDLE'))
PY

Ensuite, compare avec la chaîne vue par OpenSSL. Cette étape aide à séparer un problème de confiance locale d’un vrai problème côté serveur.

openssl s_client -connect service-interne.example.com:443 -showcerts < /dev/null
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt certificat-serveur.pem

Si le bundle système valide mais pas requests, le diagnostic est presque fait. Il faut réaligner requests sur la bonne source de confiance. Si aucun des deux ne valide, reviens au serveur, à la chaîne intermédiaire ou à la CA racine manquante.

✅ Checklist de diagnostic

Confirmer le Python exact utilisé par le service ou le runner
Afficher certifi.where() et ssl.get_default_verify_paths()
Tester le serveur avec openssl s_client hors application
Vérifier les variables REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE et les proxys
Comparer le shell local, le container et la CI avant de patcher

Les corrections propres, sans verify=False

La correction durable dépend du vrai contexte. Si tu es derrière une CA interne, commence par installer cette CA dans le système. Sur Debian et Ubuntu, le chemin le plus propre reste le store OS.

sudo install -m 0644 corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
sudo update-ca-certificates

Si ton application doit explicitement utiliser le bundle système, aligne requests dessus. La solution la plus simple est souvent une variable d’environnement gérée par systemd, Docker Compose ou CI.

export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
python3 app.py

Tu peux aussi fixer le chemin dans le code quand le besoin est localisé. C’est acceptable pour un connecteur bien délimité. En revanche, évite de disperser cette option dans toute la base de code.

import requests
response = requests.get(
    'https://service-interne.example.com/api',
    timeout=10,
    verify='/etc/ssl/certs/ca-certificates.crt',
)
print(response.status_code)

Si le projet embarque un venv créé avec pip, vérifie aussi la version de certifi. Une version obsolète peut casser des services publics récents. Ce scénario n’explique pas une CA interne manquante, mais il reste fréquent dans des images de build figées.

python3 -m pip install --upgrade certifi
python3 - <<'PY'
import certifi
print(certifi.where())
PY

Pour la partie durcissement, garde une règle simple. Le correctif doit vivre au niveau de la plateforme, pas uniquement dans un notebook ou un script ad hoc. Si plusieurs apps Python appellent des endpoints internes, pousse la même stratégie via le service manager, le chart Helm ou le playbook Ansible.

🔴

À éviter absolument

Le réflexe verify=False enlève la vérification TLS. Tu gagnes cinq minutes et tu perds tout le bénéfice du chiffrement authentifié. En prod, c’est un contournement, pas une solution.

Cas proxy entreprise, inspection TLS et certificats internes

Beaucoup d’équipes rencontrent ce bug uniquement en entreprise. Le serveur final présente un certificat public correct. Pourtant, le proxy de sortie intercepte la session TLS et réémet un certificat signé par une CA interne. Le navigateur d’entreprise lui fait confiance. Ton venv Python, non.

Dans ce cas, il faut traiter le proxy comme une autorité intermédiaire de ton environnement.

Ajoute sa CA dans le store système, propage-la dans les images Docker et expose le même bundle aux jobs CI.

Si le runner et l’image de build n’embarquent pas la même racine, les erreurs seront intermittentes et pénibles à reproduire.

FROM python:3.12-slim
RUN apt-get update  && apt-get install -y --no-install-recommends ca-certificates  && rm -rf /var/lib/apt/lists/*
COPY corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
RUN update-ca-certificates
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

Ce pattern marche bien aussi sur runners éphémères. L’important n’est pas seulement d’avoir le fichier CA. Il faut qu’il soit intégré avant l’exécution du code Python, avec une variable cohérente pour tous les jobs.

Prévenir la régression en CI/CD, Docker et exploitation Linux

Le meilleur fix est celui qu’on ne redécouvre pas au prochain build. Ajoute un test de santé très tôt dans le bootstrap. Il doit vérifier le chemin du bundle, la présence de la CA interne et un appel HTTPS réel vers une cible contrôlée.

python3 - <<'PY'
import os, sys, certifi
bundle = os.getenv('REQUESTS_CA_BUNDLE') or certifi.where()
print('bundle=', bundle)
if not os.path.exists(bundle):
    sys.exit('CA bundle introuvable')
PY

En parallèle, documente la différence entre Python système et environnement virtuel. C’est souvent là que la confusion renaît, surtout quand plusieurs équipes partagent la même base Docker. Un README court vaut mieux qu’une future session de crise.

Profite-en aussi pour industrialiser la gestion des certificats.

Si tu gères déjà du TLS côté infra, le sujet se marie bien avec la gestion et le renouvellement des certificats SSL avec Ansible.

Et si tu touches à des dépendances Python sensibles, relis aussi ce retour d’expérience sur un package PyPI compromis.

✅ Ce qu’il faut standardiser

Le même bundle CA dans les services, les conteneurs et les runners
Un check HTTPS exécutable avant le vrai job métier
Une variable REQUESTS_CA_BUNDLE gérée par la plateforme, pas au hasard
Une procédure simple pour ajouter une nouvelle CA interne

Erreurs courantes

⏱ Mettre verify=False “temporairement”

Le temporaire devient vite permanent. Le vrai correctif est d’aligner le store de confiance, pas d’ignorer TLS.

🧱 Corriger le host, oublier le container

La CA est installée sur le serveur, pas dans l’image. Le pod continue à casser malgré le faux sentiment de correction.

🔁 Mélanger plusieurs bundles sans gouvernance

Un service lit certifi, l’autre lit le bundle système, un job CI pointe vers un fichier custom. Sans standard commun, le bug reviendra.

📦 Garder un certifi trop ancien

Même sans CA interne, un bundle périmé peut refuser des chaînes valides. Mets à jour quand le contexte pointe vers du pip packagé depuis longtemps.

FAQ

Pourquoi requests ne lit pas toujours les certificats système ?
Parce que requests s’appuie souvent sur certifi quand il est installé via pip ou en environnement virtuel. Sur certaines distributions, le paquet système peut être patché pour utiliser le magasin de certificats de l’OS.
Faut-il utiliser verify=False pour débloquer l’erreur ?
Non. verify=False masque le problème et désactive la vérification TLS. En production, il vaut mieux aligner le bon bundle CA, installer la CA interne ou définir explicitement REQUESTS_CA_BUNDLE.
Quelle différence entre certifi et le bundle système ?
certifi embarque sa propre liste d’autorités publiques. Le bundle système dépend de la distribution et peut inclure des certificats internes ajoutés par l’entreprise.
Comment savoir quel bundle est utilisé ?
Vérifie le chemin renvoyé par certifi.where(), ssl.get_default_verify_paths() et la variable REQUESTS_CA_BUNDLE. Compare aussi le comportement entre Python système, virtualenv, container et runner CI.
Le problème apparaît surtout derrière un proxy entreprise, pourquoi ?
Parce que le proxy TLS d’entreprise présente souvent une CA interne. Si cette CA n’est pas connue du bundle utilisé par requests, la vérification échoue avec certificate verify failed.

Conclusion

L’erreur python requests certificate verify failed n’est pas qu’un détail Python. C’est souvent un symptôme de divergence entre poste, runner, container et plateforme. Tant que ces couches ne partagent pas la même stratégie de confiance, la panne reviendra.

La bonne approche consiste à identifier le bundle réellement utilisé, intégrer proprement la CA attendue et standardiser la configuration sur toute la chaîne d’exécution. Tu évites ainsi les patchs fragiles, les verify=False oubliés et les incidents TLS qui reviennent à chaque nouveau déploiement.

Besoin d’un regard infra pour fiabiliser TLS, CI/CD et containers ?

Je peux t’aider à standardiser les bundles CA, les proxys d’entreprise et les pipelines Python pour éviter les régressions silencieuses.

Contacte-moi →

Laisser un commentaire