SMTP STARTTLS certificate verification fails because default CA certificates are not loaded

Environment:

  • Ubuntu 22.04.5 LTS
  • Pritunl 1.34.4681.89-0ubuntu1~jammy
  • Pritunl embedded Python 3.12
  • OpenSSL 3.0.2
  • SMTP server: smtp.gmail.com:587
  • STARTTLS enabled

After updating the server, Pritunl profile emails began failing during
STARTTLS with:

ssl.SSLCertVerificationError:
[SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate

The failure occurs in pritunl/utils/mail.py at:

smtp_conn.starttls(context=context)

The SMTP SSL context is currently created with:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

but the default CA certificates are not loaded afterward.

I reproduced the issue using Pritunl’s embedded Python interpreter.

This fails with the same certificate error:

import ssl
import smtplib

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2

smtp = smtplib.SMTP(“smtp.gmail.com”, 587)
smtp.ehlo()
smtp.starttls(context=ctx)

This succeeds when one line is added:

ctx.load_default_certs()

Complete successful test:

import ssl
import smtplib

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_default_certs()
ctx.minimum_version = ssl.TLSVersion.TLSv1_2

smtp = smtplib.SMTP(“smtp.gmail.com”, 587)
smtp.ehlo()
smtp.starttls(context=ctx)
smtp.ehlo()
smtp.quit()

Additional verification:

  • openssl s_client validates smtp.gmail.com successfully
  • Verify return code is 0
  • /usr/lib/ssl/certs correctly points to /etc/ssl/certs
  • CA certificates and OpenSSL packages are current
  • No SSL_CERT_FILE or SSL_CERT_DIR overrides are set
  • No stale or deleted libraries are mapped into the running process
  • Pritunl is the current package version in the Jammy repository

The following CA store behavior was also observed:

Before load_default_certs():

{‘x509’: 0, ‘crl’: 0, ‘x509_ca’: 0}

After load_default_certs(), before the handshake:

{‘x509’: 0, ‘crl’: 0, ‘x509_ca’: 0}

After a successful SMTP TLS handshake:

{‘x509’: 1, ‘crl’: 0, ‘x509_ca’: 1}

This appears to be normal lazy loading from the hashed CA directory, but it
confirms that calling load_default_certs() configures the trust source needed
for verification.

Temporary workaround:

pritunl set app.email_skip_verify true

That allows email delivery, but disables certificate and hostname
verification, so it is not suitable as a permanent fix.

Would it be appropriate to change mail.py to either:

context = ssl.create_default_context()

or:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_default_certs()

before applying the TLS minimum-version setting?

This will be fixed in the next release.

Thank you so much Zach!

UPD: Sorry, didn’t see workaround. It works. i’ll be waiting an update :upside_down_face:

@zach Hi Zach.

I have the same issue on one of my updated Pritunl VPN server (v1.34.4681.89 32a79b)

[2026-09-08 10:51:38,958][ERROR] Exception on /user/**********/********** [PUT]
Traceback (most recent call last):
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/pritunl/auth/app.py", line 10, in _wrapped
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/pritunl/handlers/user.py", line 724, in user_put
    user.send_key_email(utils.get_url_root())
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/pritunl/user/user.py", line 1667, in send_key_email
    utils.send_email(
  File "/usr/lib/pritunl/usr/lib/python3.12/site-packages/pritunl/utils/mail.py", line 45, in send_email
    smtp_conn.starttls(context=context)
  File "/usr/lib/pritunl/usr/lib/python3.12/smtplib.py", line 779, in starttls
    self.sock = context.wrap_socket(self.sock,
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/ssl.py", line 455, in wrap_socket
    return self.sslsocket_class._create(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/pritunl/usr/lib/python3.12/ssl.py", line 1041, in _create
    self.do_handshake()
  File "/usr/lib/pritunl/usr/lib/python3.12/ssl.py", line 1319, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1010)

On other one Pritunl VPN (v1.32.4567.52 255b80) i haven’t this issue, when i try to send email to user.