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.
📋 Au programme
Tu vois passer python requests certificate verify failed alors que curl, apt ou même ton navigateur ne se plaignent de rien.
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
certifi.where() et ssl.get_default_verify_paths()openssl s_client hors applicationREQUESTS_CA_BUNDLE, CURL_CA_BUNDLE et les proxysLes 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
REQUESTS_CA_BUNDLE gérée par la plateforme, pas au hasardErreurs 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 ?
▶ Faut-il utiliser verify=False pour débloquer l’erreur ?
▶ Quelle différence entre certifi et le bundle système ?
▶ Comment savoir quel bundle est utilisé ?
▶ Le problème apparaît surtout derrière un proxy entreprise, pourquoi ?
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.

