201
import requests
data = {'foo':'bar'}
url = 'https://foo.com/bar'
r = requests.post(url, data=data)

If the URL uses a self signed certificate, this fails with

requests.exceptions.SSLError: [Errno 1] _ssl.c:507: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

I know that I can pass False to the verify parameter, like this:

r = requests.post(url, data=data, verify=False)

However, what I would like to do is point requests to a copy of the public key on disk and tell it to trust that certificate.

3
  • stackoverflow.com/questions/10667960/…
    – Ajay
    Commented May 22, 2015 at 21:08
  • 5
    In some scenaria (notably load tests) it is important to bypass the check altogether as opposed to supplying a valid key for successful check. That's to save CPU cycles of which verification is hungry. So verify=False in combination with import urllib3; urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) might be a better option.
    – wick
    Commented Nov 22, 2022 at 11:19
  • @wick this comment was exactly what I needed for my load tests. just wanted to say thanks. Commented Dec 11, 2023 at 18:26

12 Answers 12

138

try:

r = requests.post(url, data=data, verify='/path/to/public_key.pem')
14
  • 2
    Can you do the same and use client certificates at the same time? I'm getting problems with this. Commented Jun 14, 2019 at 14:46
  • 58
    Note that the .pem file you pass must include the server's certificate and any intermediate certificates. I lost a few hours trying to figure out why it didn't work after adding the server's cert.
    – ChrisBob
    Commented Oct 15, 2019 at 12:39
  • 21
    This technique didn't work for me. I used ssl.get_server_certificate to download a certificate for (self-signed.badssl.com, 443), saved that certificate to cert.pem, and then ran requests.get('https://self-signed.badssl.com/', verify='cert.pem') and it still failed with an SSL error (that certificate is self-signed). Commented Nov 21, 2019 at 3:31
  • 3
    @ChrisBob I can't thank you enough. Your comment is much more valuable than the accepted answers to the many questions about this (which only repeat what is in requests documentation). After pulling my hair for hours, your comment put me on the right direction... Commented Jan 15, 2021 at 9:19
  • 28
    For reference (possibly for my future self), I had to download the certicicate as a .pem file by clicking on the lock icon in Firefox > Show Connection details > More information > View certificate > Download "PEM (chain)". The (chain) is the important part that I was missing as the alternative "PEM (cert)" will not work with requests. Commented Jan 15, 2021 at 9:28
66

The easiest is to export the variable REQUESTS_CA_BUNDLE that points to your private certificate authority, or a specific certificate bundle. On the command line you can do that as follows:

export REQUESTS_CA_BUNDLE=/path/to/your/certificate.pem
python script.py

If you have your certificate authority and you don't want to type the export each time you can add the REQUESTS_CA_BUNDLE to your ~/.bash_profile as follows:

echo "export REQUESTS_CA_BUNDLE=/path/to/your/certificate.pem" >> ~/.bash_profile ; source ~/.bash_profile
4
  • The environment variable was what I needed to get PyCharm to work with the certificates stored in the OpenSSL cert file.
    – Brady
    Commented Aug 30, 2019 at 12:04
  • I have a self-signed certificate in the chain. This solution solved my problem with boto3 library.
    – Ilkin
    Commented Oct 12, 2020 at 11:53
  • A CA bundle is not private. Anybody connecting to your server will be able to read it. The key (which is not included in the bundle) is private. So probably it is more correct to say "your own certificate authority".
    – sekrett
    Commented Jan 13, 2022 at 15:19
  • Thanks, this also helps when a library performs the request and I can't change its calls! Commented Jul 2 at 9:19
58

With the verify parameter you can provide a custom certificate authority bundle

requests.get(url, verify=path_to_bundle_file)

From the docs:

You can pass verify the path to a CA_BUNDLE file with certificates of trusted CAs. This list of trusted CAs can also be specified through the REQUESTS_CA_BUNDLE environment variable.

0
27

All of the answers to this question point to the same path: get the PEM file, but they don't tell you how to get it from the website itself.

Getting the PEM file from the website itself is a valid option if you trust the site, such as on an internal corporate server. If you trust the site, why should you do this? You should do this because it helps protect yourself and others from inadvertently re-using your code on a site that isn't safe.

Here is how you can get the PEM file.

  1. Click on the lock next to the url. Click on the lock

  2. Navigate to where you can see the certificates and open the certificates. Navigate to view certificate

  3. Download the PEM CERT chain. Download the PEM chain

  4. Put the .PEM file somewhere you script can access it and try verify=r"path\to\pem_chain.pem" within your requests call.

r = requests.get(url, verify='\path\to\public_key.pem')

4
  • 1
    echo | openssl s_client -servername NAME -connect HOST:PORT |\ sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > certificate.crt
    – musterjunk
    Commented Jun 29, 2022 at 19:34
  • Yup, this worked for me as I was able to visit my corporate website through my browser, but not through code. This fixed it, thanks!
    – JoshuaHew
    Commented Nov 2, 2022 at 15:37
  • 1
    Foggy, thank you very much for showing people how to acquire the .pem file. It's this kind of thoroughness that makes this place awesome.
    – alexGIS
    Commented Nov 21, 2022 at 20:04
  • 1
    echo | openssl s_client -showcerts -connect HOST:PORT |sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >certificate.pem copy the contents of the certificate.pem into the certifi cacert.pem file your_python_path/lib64/python3.6/site-packages/certifi/cacert.pem Commented Jan 17 at 22:27
17

Case where multiple certificates are needed was solved as follows: Concatenate the multiple root pem files, myCert-A-Root.pem and myCert-B-Root.pem, to a file. Then set the requests REQUESTS_CA_BUNDLE var to that file in my ./.bash_profile.

$ cp myCert-A-Root.pem ca_roots.pem
$ cat myCert-B-Root.pem >> ca_roots.pem
$ echo "export REQUESTS_CA_BUNDLE=~/PATH_TO/CA_CHAIN/ca_roots.pem" >> ~/.bash_profile ; source ~/.bash_profile
1
  • 2
    That was my "ahhh" moment of the day... Thanks a lot... With this hint I got my self signed jira certificate to work... ;-) I know there are maybe hundrets of sites and answers who describe this, but I found yours, so you get my credit for helping me solve my probelm... d
    – alexrjs
    Commented Mar 13, 2019 at 9:04
16

Setting export SSL_CERT_FILE=/path/file.crt should do the job.

2
  • 3
    Thanks. Works for me (Whereas REQUESTS_CA_BUNDLE variable has no effect in my case).
    – Pascal H.
    Commented Dec 3, 2019 at 11:46
  • 1
    in your terminal
    – gizzmole
    Commented May 17, 2021 at 18:13
15

If you're behind a corporate network firewall like I was, ask your network admin where your corporate certificates are, then:

import os
os.environ["REQUESTS_CA_BUNDLE"] = 'path/to/corporate/cert.pem'
os.environ["SSL_CERT_FILE"] = 'path/to/corporate/cert.pem'

This fixed issues I had with requests and openssl.

1
  • 1
    If I also need to use public domains (e.g., google.com), will it still work after setting this in the script? In other words, does this trust this CA bundle additionally, or does it trust this one instead of the default one?
    – Bolong
    Commented Jul 3, 2023 at 18:51
12

In a dev environment, using Poetry as virtual env provider on a Mac with Python 3.8 I used this answer https://stackoverflow.com/a/42982144/15484549 as base and appended the content of my self-signed root certificate to the certifi cacert.pem file.

The steps in detail:

cd project_folder
poetry add requests
# or if you use something else, make sure certifi is among the dependencies
poetry shell
python
>>> import certifi
>>> certifi.where()
/path/to/the/certifi/cacert.pem
>>> exit()
cat /path/to/self-signed-root-cert.pem >> /path/to/the/certifi/cacert.pem
python the_script_you_want_to_run.py
5

I know it is an old thread. However, I run into this issue recently. My python requests code does not accept the self-signed certificate but curl does. It turns out python requests are very strict on the self-signed certificate. It needs to be a root CA certificate. In other words,

Basic Constraints: CA:TRUE

Key Usage: Digital Signature, Non Repudiation, Key Encipherment, Certificate Sign

1
  • If the certificate is signed by a trusted root certificate it works as well. In an intranet environment you might want to distibute a self issued root certificate anyways.
    – Alex
    Commented Mar 8 at 11:20
0

Incase anyone happens to land here (like I did) looking to add a CA (in my case Charles Proxy) for httplib2, it looks like you can append it to the cacerts.txt file included with the python package.

For example:

cat ~/Desktop/charles-ssl-proxying-certificate.pem >> /usr/local/google-cloud-sdk/lib/third_party/httplib2/cacerts.txt

The environment variables referenced in other solutions appear to be requests-specific and were not picked up by httplib2 in my testing.

0

You may try:

settings = s.merge_environment_settings(prepped.url, None, None, None, None)

You can read more here: http://docs.python-requests.org/en/master/user/advanced/

1
0

In my case, I was using self-signed certificate generated by mkcert.

While curl works fine with such self-signed certificates, the Python requests module does not.

With curl:

# works fine
curl https://tests.localhost/hello-world --cacert /etc/apache2/ssl/SSLforMyHosts-certificate.pem

With Python requests:

# does not work with the self-signed certificate:
response = requests.post(url=url, verify="/etc/apache2/ssl/SSLforMyHosts-certificate.pem")

# works fine with the rootCA that signed the certificate:
requests.post(url=url, verify="/etc/mkcert/rootCA.pem")

in addition: I'm running a webapp inside a Docker container, but creating the self-signed certificates on the host on macOS. So I had to share /etc/mkcert/ across macOS and container -- simply mounted it with Docker compose.

Not the answer you're looking for? Browse other questions tagged or ask your own question.