SSL: CERTIFICATE_VERIFY_FAILED when submitting forms in Django run on Windows VPS with Apache web server

I have a Django website hosted on a VPS (OS Windows server 2019). The web server is Apache 2.4 and Python version is 3.12.8. A Digicert SSL certificate successfully installed and the website is working in production under https. However, I get this error when I try to submit a login or signup form from the website in production:

URLError at /login-page/
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)>

Here is the complete error traceback:

URLError at /login-page/
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)>
Request Method: POST
Request URL:    https://www.mywebsite.com/login-page/
Django Version: 5.0.6
Exception Type: URLError
Exception Value:    <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)>
Exception Location: C:\Python 3.12.8\Lib\urllib\request.py, line 1347, in do_open
Raised during:  users_authentication.views._wrapped_view
Python Executable:  C:\Apache24\bin\httpd.exe
Python Version: 3.12.8
Python Path:    ['C:\\Apache24\\htdocs\\project',
 'C:\\Python 3.12.8\\python312.zip',
 'C:\\Python 3.12.8\\DLLs',
 'C:\\Python 3.12.8\\Lib',
 'C:\\Apache24\\bin',
 'C:\\Python 3.12.8',
 'C:\\Python 3.12.8\\Lib\\site-packages']
C:\Python 3.12.8\Lib\urllib\request.py, line 1344, in do_open
                h.request(req.get_method(), req.selector, req.data, headers,
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1336, in request
        self._send_request(method, url, body, headers, encode_chunked)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1382, in _send_request
        self.endheaders(body, encode_chunked=encode_chunked)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1331, in endheaders
        self._send_output(message_body, encode_chunked=encode_chunked)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1091, in _send_output
        self.send(msg)
              ^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1035, in send
                self.connect()
                      ^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\http\client.py, line 1477, in connect
            self.sock = self._context.wrap_socket(self.sock,
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\ssl.py, line 455, in wrap_socket
        return self.sslsocket_class._create(
                     …
C:\Python 3.12.8\Lib\ssl.py, line 1041, in _create
                    self.do_handshake()
                          ^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\ssl.py, line 1319, in do_handshake
            self._sslobj.do_handshake()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
During handling of the above exception ([SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)), another exception occurred:
C:\Python 3.12.8\Lib\site-packages\django\core\handlers\exception.py, line 55, in inner
                response = get_response(request)
                               ^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\core\handlers\base.py, line 197, in _get_response
                response = wrapped_callback(request, *callback_args, **callback_kwargs)
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Apache24\htdocs\project\users_authentication\views.py, line 48, in _wrapped_view
            return view_func(request, *args, **kwargs)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Apache24\htdocs\project\users_authentication\views.py, line 142, in login_vf
        if form.is_valid():
                ^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\forms\forms.py, line 197, in is_valid
        return self.is_bound and not self.errors
                                          ^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\forms\forms.py, line 192, in errors
            self.full_clean()
                 ^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\forms\forms.py, line 327, in full_clean
        self._clean_fields()
             ^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\forms\forms.py, line 339, in _clean_fields
                    value = field.clean(value)
                                 ^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django\forms\fields.py, line 205, in clean
        self.validate(value)
             ^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django_recaptcha\fields.py, line 69, in validate
            check_captcha = client.submit(
                                 …
C:\Python 3.12.8\Lib\site-packages\django_recaptcha\client.py, line 64, in submit
    response = recaptcha_request(params)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\site-packages\django_recaptcha\client.py, line 39, in recaptcha_request
    return opener.open(
                …
C:\Python 3.12.8\Lib\urllib\request.py, line 515, in open
        response = self._open(req, data)
                        ^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\urllib\request.py, line 532, in _open
        result = self._call_chain(self.handle_open, protocol, protocol +
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\urllib\request.py, line 492, in _call_chain
            result = func(*args)
                          ^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\urllib\request.py, line 1392, in https_open
            return self.do_open(http.client.HTTPSConnection, req,
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ …
C:\Python 3.12.8\Lib\urllib\request.py, line 1347, in do_open
                raise URLError(err)

Below is what my login html page look like:

    <form method="POST">
        {% csrf_token %}
        <fieldset>
          <legend >Login</legend>
            {{form|crispy}}
        </fieldset>
        <div class="form-group mt-5">
             <button type="submit">Login</button>
        </div>
    </form>

Below is the form from the project’s app:

class LoginForm(forms.Form):
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)
    captcha = ReCaptchaField(widget=CustomReCaptchaV2Checkbox())

I have unsuccesfuly explored multiple solutions proposed by previous threads with similar error:

pip install pip-system-certs 
 pip install certifi 
python -m pip install pip-system-certs --use-feature=truststore
REQUESTS_CA_BUNDLE in the environment variable.

Please, your help is greatly appreciated!

Generally speaking, unless you need to perform some key authentication within Django, there’s no need for Django to even be aware that SSL is being used.

What is your Apache configuration for your project? How are you running your project? (Are you using mod_wsgi, or are you proxying the requests through Apache to something like uwsgi?)

Hi @KenWhitesell. I hope you are having a great Sunday. Thanks for stepping in for help. I wish you a happy new year 2025 in advance.

How are you running your project? : I am using mod_wsgi
What is your Apache configuration for your project? : Below is my Apache configurations:

1- My httpd-vhost configuration:

<VirtualHost *:80>
    ServerAdmin info@mysite.com
    DocumentRoot "${SRVROOT}/htdocs/myproject"
    ServerName www.mysite.com
    ServerAlias mysite.com
    ErrorLog "logs/mysite.com-error.log"
    CustomLog "logs/mysite-access.log" common
    Redirect permanent / https://www.mysite.com/
    RewriteEngine on
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
</VirtualHost>

2- Here is my httpd-ssl configuration:

<VirtualHost _default_:443>
    DocumentRoot "C:/Apache24/htdocs/myproject/"
    ServerName www.mysite.com
    ServerAlias mysite.com
    ServerAdmin name@gmail.com
    ErrorLog "${SRVROOT}/logs/error.log"
    TransferLog "${SRVROOT}/logs/access.log"
        SSLEngine on
        SSLCertificateFile "C:/Apache24/conf/www_mysite_com.crt"
        SSLCertificateKeyFile "C:/Apache24/conf/server.key"
        SSLCertificateChainFile "C:/Apache24/conf/DigiCertCA.crt"

    <FilesMatch "\.(cgi|shtml|phtml|php)$">
    	SSLOptions +StdEnvVars
    </FilesMatch>
    <Directory "${SRVROOT}/cgi-bin">
    	SSLOptions +StdEnvVars
    </Directory>
    BrowserMatch "MSIE [2-5]" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0
    CustomLog "${SRVROOT}/logs/ssl_request.log" \
          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
    Options Indexes
    
</VirtualHost> 

My python dependencies are installed from the venv with the path being:

C:\Apache24\htdocs\myproject\.venv

The path to the project’s main setting is:

C:\Apache24\htdocs\myproject\mypreoject\settings.py

Many thanks for your help @KenWhitesell

Below is my Apache’s httpd.conf file:


Define SRVROOT "c:/Apache24"

ServerRoot "${SRVROOT}"


#Listen 12.34.56.78:80
Listen localhost:8080

LoadModule actions_module modules/mod_actions.so
LoadModule alias_module modules/mod_alias.so
LoadModule allowmethods_module modules/mod_allowmethods.so
LoadModule asis_module modules/mod_asis.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule cgi_module modules/mod_cgi.so
LoadModule dir_module modules/mod_dir.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule include_module modules/mod_include.so
LoadModule isapi_module modules/mod_isapi.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

<IfModule unixd_module>
User daemon
Group daemon

</IfModule>

ServerAdmin name@gmail.com


ServerName www.mysite.com:80
ServerName localhost:8080


<Directory />
    AllowOverride none
    Require all denied
</Directory>

DocumentRoot "${SRVROOT}/htdocs"
<Directory "${SRVROOT}/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>


<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>

<Files ".ht*">
    Require all denied
</Files>

ErrorLog "logs/error.log"

LogLevel warn

<IfModule log_config_module>

    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common

    <IfModule logio_module>
      # You need to enable mod_logio.c to use %I and %O
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>

    CustomLog "logs/access.log" common
</IfModule>

<IfModule alias_module>
    ScriptAlias /cgi-bin/ "${SRVROOT}/cgi-bin/"
</IfModule>

<IfModule cgid_module>
    # ScriptSock: On threaded servers, designate the path to the UNIX
    # socket used to communicate with the CGI daemon of mod_cgid.
    #
    #Scriptsock cgisock
</IfModule>

<Directory "${SRVROOT}/cgi-bin">
    AllowOverride None
    Options None
    Require all granted
</Directory>

<IfModule headers_module>
    RequestHeader unset Proxy early
</IfModule>

<IfModule mime_module>
    TypesConfig conf/mime.types
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
</IfModule>


# Virtual hosts
Include conf/extra/httpd-vhosts.conf

<IfModule proxy_html_module>
Include conf/extra/proxy-html.conf
</IfModule>

# Secure (SSL/TLS) connections
Include conf/extra/httpd-ssl.conf

<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>

#ADDED BY ME: From https://stackoverflow.com/questions/68170765/ubuntu-python3-8-apache2-wsgi-numpy-module-not-found-error
WSGIApplicationGroup %{GLOBAL}

#ADDED BY ME: From https://moodle.org/mod/forum/discuss.php?d=378263&parent=1542552
<IfModule mpm_winnt_module>
   ThreadStackSize 8888888
</IfModule>


LoadFile "C:/Python 3.12.8/python312.dll"
LoadModule wsgi_module "C:/Python 3.12.8/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp312-win_amd64.pyd"
WSGIPythonHome "C:/Python 3.12.8"
WSGIScriptAlias / "C:/Apache24/htdocs/myproject/myproject/wsgi.py"
WSGIPythonPath "C:/Apache24/htdocs/myproject/"

<Directory "C:/Apache24/htdocs/myproject/myproject/">
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

Alias /static "C:/Apache24/htdocs/myproject/staticfiles/"
<Directory "C:/Apache24/htdocs/myproject/staticfiles/">
    Require all granted
</Directory>

Thanks for your help!

Caveat: It’s been more than 10 years since I’ve had to deal with Apache, and at least 15 since I’ve had to deploy anything on a Windows server.

With that in mind, the first thing I notice is that I’ve always had my Django-related configuration information within the appropriate <VirtualHost ...> paragraph. I think you want to remove the <FilesMatch ...> and <Directory ...> paragraphs and replace that with your Django configuration. Those directives should not be in the base “httpd.conf” file outside the <VirtualHost ...>.

For examples of this see How To Serve Django Applications with Apache and mod_wsgi on Ubuntu 16.04 | DigitalOcean and https://computingforgeeks.com/deploy-python-3-django-application-on-centos-with-apache-and-mod-wsgi/ for a couple of examples. (I’m pointing these out as samples to illustrate the structure of the file, not that you should use these exactly as written. Also note that the comments about using “daemon mode” don’t apply to Windows.)

Many thanks @KenWhitesell for your swift reply.
I followed your advice (and also helped by the links you shared); I removed the <FilesMatch ...> and <Directory...> and put the directives inside the <VirtualHost...> . The base httpd.conf becomes as follows:


Define SRVROOT "c:/Apache24"

ServerRoot "${SRVROOT}"

#Listen 12.34.56.78:80
Listen localhost:8080

LoadModule actions_module modules/mod_actions.so
LoadModule alias_module modules/mod_alias.so
LoadModule allowmethods_module modules/mod_allowmethods.so
LoadModule asis_module modules/mod_asis.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule cgi_module modules/mod_cgi.so
LoadModule dir_module modules/mod_dir.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule include_module modules/mod_include.so
LoadModule isapi_module modules/mod_isapi.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

# Secure (SSL/TLS) connections
Include conf/extra/httpd-ssl.conf


<VirtualHost *:80>
        ServerAdmin info@mysite.com
        ServerName mysite.com
        DocumentRoot "${SRVROOT}/htdocs"

        Alias /static "C:/Apache24/htdocs/myproject/staticfiles"
        <Directory "C:/Apache24/htdocs/myproject/staticfiles">
                Options FollowSymLinks
                Require all granted
        </Directory>

        Alias /media C:/Apache24/htdocs/myproject/media
        <Directory "C:/Apache24/htdocs/myproject/media">
                Options FollowSymLinks
                Require all granted
        </Directory>
        ErrorLog C:/Apache24/logs/apis_error.log
        CustomLog C:/Apache24/logs/apis_access.log combined

        WSGIPassAuthorization On
	WSGIPythonHome "C:/Python 3.12.8"
	WSGIScriptAlias / "C:/Apache24/htdocs/myproject/myproject/wsgi.py"
	WSGIPythonPath "C:/Apache24/htdocs/myproject/"
        LoadFile "C:/Python 3.12.8/python312.dll"
	LoadModule wsgi_module "C:/Python 3.12.8/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp312-win_amd64.pyd"

        <Directory C:/Apache24/htdocs/myproject/myproject>
                <Files wsgi.py>
                        Require all granted
                </Files>
        </Directory>
</VirtualHost>

It appears that the Python path can’t stay inside the <VirtualHost ...> as Apache it produces the following error:
WSGIPythonHome cannot occur within <VirtualHost> section

Even with the WSGIPythonHome outside the <VirtualHost...>, the initial error still occurs when trying to log in.

Please, I am about to be frustrated. Almost two days on this issue. I don’t really know what is wrong where.

Your help is geatly appreciated!

You’ve got these set up in your virtual host for http (port 80), and not https (port 443).

But there may also be an issue with your cert files.

Did you actually get public certificates for the domain mysite.com? Or is this a self-signed cert?

I got a public certificate from DigitCert.

There are https (port 443) settings in the file httpd-ssl.conf that are imported into the main https.conf file, as shown in my first post.

Thanks

For completeness, below is the view function that’s in the users authentication’s view.py.

@user_not_authenticated #Own custom function for checking the visitor is not yet authenticated
def login_vf(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data['email']
            password = form.cleaned_data['password']
            user = authenticate(request, email=email, password=password)
            if user:
                login(request, user)
                messages.success(request, f"Hello {user.last_name}! You have been successfully logged in")    
                if request.user.groups.filter(name="Clients").exists():
                        # user is a a trialer or a client
                        return redirect("/dashboard-countries")
                elif request.user.groups.filter(name="Trailers").exists():
                        # user is a a trialer or a client
                        return redirect("/dashboard-countries")
                else:
                     return redirect("/")         
            else:
                 messages.error(request, "Please, email address and/or password invalid. Please, try again!")
                 return redirect("/login-page")
        else:
            for key, error in list(form.errors.items()):
                if key == 'captcha' and error[0] == 'This field is required.':
                    messages.error(request, "You must pass the reCAPTCHA test")
                    continue
    else:
        #for error in list(form.errors.values()):
             #messages.error(request, error)     
        form = LoginForm()
    return render(request, 'login-page.html', {'form': form})

Thanks

But is it for mysite.com, or for a different domain? If it’s for a different domain, then your configuration doesn’t match the certificate.

Also, are you trying to do anything with the certificates in your code? Are you using these for some kind of authentication process?

Hi @KenWhitesell
The certificate is exclisively for mysite.com. There is nothing else I am trying to do with the certificate in my code. My aim of having the SSL was to authenticate mysite.com and have encrypted connection.

But your error message is showing a request for a different name

Are you trying to anonymize your references here?

Also, check your apache logs for errors - both the access log and the errors log.

Finally, configure apache to server a static html page just to verify that you have the right key files and have them configured correctly - getting Django out of the way is likely to help.

Problem solved:

It turns out that on Windows, Python does not look at the system’s certificate; But it relies on its own verification process, specifically using the library certifi.

Although I had certifi installed, my REQUESTS_CA_BUNDLE what pointing to the server’s certification *crt file and the path to the SSL_CERT_FILE was missing.

So, what I did, with the help of this post was in 3 steps:

1- I edited the cacert.pem file from certify, which is located at located at C:\Python 3.12.8\Lib\site-packages\certifi to add my website certificate. It’s simple: Open the cacert.pem, and paste at the bottom the content of the certificate*.crt, (intermediate certificate and root certificate if needed).

2- I located the file request.py which is at C:\Python 3.12.8\Lib\urllib\request.py, import the library certifi and set the environment paths for REQUESTS_CA_BUNDLE and SSL_CERT_FILE to the certifi’s cacert.pem (located at C:\Python 3.12.8\Lib\site-packages\certifi\cacert.pem) as follows:

import certifi

os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
os.environ["SSL_CERT_FILE"] = certifi.where()

3- I altered the context line in the request.py file by adding certifi.where() as the value of the cafile argument, as follows:

context = ssl.create_default_context(cafile=certifi.where())

Thank you @KenWhitesell for your support and efforts