OAuth failing: SSLError "certificate verify failed"

I had been using Spotcast for a few years with very few issues. About a year ago, it just stopped working. I have been trying to get Spotcast (and more recently, SpotifyPlus) to authenticate ever since.

The developer of SpotifyPlus has been incredibly helpful in trying to get this over the line with me, but we have hit a brick wall. The issue appears to be a core network/SSL handshake failure occurring within Home Assistant’s Python environment when it tries to reach the Spotify API to exchange the token.

System Details:

  • Installation: Home Assistant OS on an Intel NUC (HAOS 16.3, Core 2025.12.5, Supervisor 2026.02.2)
  • External Access: Nabu Casa
  • Internal URL: http://192.168.1.10:8123
  • Router: BT Smart Hub 2 (UK)

When adding the integration, the initial Spotify login page loads, but when returning to Home Assistant to finalise the OAuth linking, it immediately fails with a certificate verify failed error.

Here is the traceback from the logs:

This error originated from a custom integration.

Logger: custom_components.spotifyplus.config_flow
Source: custom_components/spotifyplus/config_flow.py:168
integration: SpotifyPlus (documentation, issues)
First occurred: 16 February 2026 at 16:16:32 (3 occurrences)
Last logged: 09:32:21

SpotifyApiError: SAM0001E - An unhandled exception occured while processing method "MakeRequest". HTTPSConnectionPool(host='api.spotify.com', port=443): Max retries exceeded with url: /v1/me (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])")))
Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/urllib3/contrib/pyopenssl.py", line 520, in wrap_socket
    cnx.do_handshake()
    ~~~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.13/site-packages/OpenSSL/SSL.py", line 2432, in do_handshake
    self._raise_ssl_error(self._ssl, result)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/OpenSSL/SSL.py", line 2048, in _raise_ssl_error
    _openssl_assert(
    ~~~~~~~~~~~~~~~^
        reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/OpenSSL/_util.py", line 76, in openssl_assert
    exception_from_error_queue(error)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/OpenSSL/_util.py", line 62, in exception_from_error_queue
    raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', '', 'certificate verify failed')]

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 464, in _make_request
    self._validate_conn(conn)
    ~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 1093, in _validate_conn
    conn.connect()
    ~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.13/site-packages/urllib3/connection.py", line 796, in connect
    sock_and_verified = _ssl_wrap_socket_and_match_hostname(
        sock=sock,
    ...<14 lines>...
        assert_fingerprint=self.assert_fingerprint,
    )
  File "/usr/local/lib/python3.13/site-packages/urllib3/connection.py", line 975, in _ssl_wrap_socket_and_match_hostname
    ssl_sock = ssl_wrap_socket(
        sock=sock,
    ...<8 lines>...
        tls_in_tls=tls_in_tls,
    )
  File "/usr/local/lib/python3.13/site-packages/urllib3/util/ssl_.py", line 483, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
  File "/usr/local/lib/python3.13/site-packages/urllib3/util/ssl_.py", line 527, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/urllib3/contrib/pyopenssl.py", line 526, in wrap_socket
    raise ssl.SSLError(f"bad handshake: {e!r}") from e
ssl.SSLError: ("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])",)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 787, in urlopen
    response = self._make_request(
        conn,
    ...<10 lines>...
        **response_kw,
    )
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 488, in _make_request
    raise new_e
urllib3.exceptions.SSLError: ("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])",)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/spotifywebapipython/spotifyclient.py", line 1400, in MakeRequest
    response = self._Manager.request(method, url, headers=msg.RequestHeaders)
  File "/usr/local/lib/python3.13/site-packages/urllib3/_request_methods.py", line 135, in request
    return self.request_encode_url(
           ~~~~~~~~~~~~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<3 lines>...
        **urlopen_kw,
        ^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/urllib3/_request_methods.py", line 182, in request_encode_url
    return self.urlopen(method, url, **extra_kw)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/urllib3/poolmanager.py", line 457, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
    return self.urlopen(
           ~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<13 lines>...
        **response_kw,
        ^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
    return self.urlopen(
           ~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<13 lines>...
        **response_kw,
        ^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 871, in urlopen
    return self.urlopen(
           ~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<13 lines>...
        **response_kw,
        ^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py", line 841, in urlopen
    retries = retries.increment(
        method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    )
  File "/usr/local/lib/python3.13/site-packages/urllib3/util/retry.py", line 535, in increment
    raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='api.spotify.com', port=443): Max retries exceeded with url: /v1/me (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])")))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/config/custom_components/spotifyplus/config_flow.py", line 168, in async_oauth_create_entry
    await self.hass.async_add_executor_job(
        spotifyClient.SetAuthTokenFromToken, clientId, data["token"], tokenProfileId
    )
  File "/usr/local/lib/python3.13/concurrent/futures/thread.py", line 59, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.13/site-packages/spotifywebapipython/spotifyclient.py", line 17931, in SetAuthTokenFromToken
    self.MakeRequest('GET', msg)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/spotifywebapipython/spotifyclient.py", line 1458, in MakeRequest
    raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
spotifywebapipython.spotifyapierror.SpotifyApiError: SpotifyApiError: SAM0001E - An unhandled exception occured while processing method "MakeRequest".
HTTPSConnectionPool(host='api.spotify.com', port=443): Max retries exceeded with url: /v1/me (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])")))

What we have already tried: We strongly suspected a Man-in-the-Middle (MITM) SSL interception or a DNS issue, but I have systematically disabled everything on my network to isolate it, and the error persists.

  • System Time is Correct: Checked via the template editor ({{ now() }}); the year and time are perfectly synced, so it is not rejecting an “expired” cert due to a bad clock.
  • Nginx Proxy Manager (NPM): I run NPM for a separate container (Mealie), but my HA instance is NOT behind it. I completely stopped the NPM add-on anyway, restarted HA Core, and the handshake still failed.
  • AdGuard Home: Completely disabled the AdGuard add-on to rule out DNS filtering or blocklists. Still failed.
  • BT Smart Hub 2 Interception: Disabled the “Web Protect” and “Smart Setup” features on the BT router, which are known to act as transparent proxies. Still failed.
  • IPv6 Disabled: Disabled IPv6 entirely within the HA Network settings to force an IPv4 handshake, bypassing any weird BT Hub IPv6 routing issues. Still failed.
  • URLs Configured Correctly: My internal_url is correctly set to http://192.168.1.10:8123 (no .local misconfigurations).
  • Fresh Credentials: Deleted and regenerated the Spotify Application Credentials multiple times.

Since I am on HAOS, I shouldn’t have missing CA certificates. If HA is not behind NPM, AdGuard is off, and my router’s security features are disabled, what else could be breaking the outbound TLS trust chain specifically for Spotify?

Is there a specific SSH command I can run in the HA terminal to expose exactly which certificate the system is receiving so I can see who is faking the signature?

Any advanced networking advice would be massively appreciated, I am at an absolute loss and losing the will to live fixing this! I would even be willing to pay!

Are the URLs correct, not just the certificates?
What DNS do you have configured?

Thanks for getting back to me!

Regarding URLs: Yes, the URLs should now be correct. During troubleshooting, I found that my internal URL was incorrectly set to http://.local:8123, but I have since corrected it in Settings > System > Network to explicitly be my static IP: http://192.168.1.10:8123. My external access is handled natively by Nabu Casa.

Regarding DNS: Normally, I use the AdGuard Home add-on within Home Assistant to handle my network’s DNS.

However, to rule out AdGuard blocking or redirecting the Spotify API requests, I have completely stopped the AdGuard add-on during my recent tests. With AdGuard disabled, Home Assistant falls back to the default DNS provided by my router (BT Smart Hub 2 / standard ISP DNS).

Even with AdGuard completely switched off, the certificate verify failed error still occurs during the handshake.

http or https? You don’t need certificates for the first.

https My internal connection is strictly HTTP (http://192.168.1.10:8123). I am not using any local certificates or SSL for my internal network.

However, the error in the logs is not about my incoming connection. The traceback shows the SSL failure is happening on the outbound request from HA to the Spotify API: HTTPSConnectionPool(host='api.spotify.com', port=443): ... url: /v1/me

Home Assistant’s Python environment is refusing to trust the certificate that the external server is presenting during the OAuth token exchange.

@Jakesa
SpotifyPlus author here again …
Some more suggestions from a chatGPT search …

I broke them down into 3 separate parts. All of them require the HA Terminal AddOn to be installed. You should also be access the HA instance via the internal URL (e.g. http://192.168.1.10:8123) from a desktop browser, preferably Chrome (do not use a mobile / tablet device, nor the HA Companion App).


CURL Command to Spotify API URL

  1. Issue the following command from an HA Terminal window:
curl https://api.spotify.com
  1. what output do you see?

If the command fails with certificate errors, then it’s a CA Certificate bundle problem.

If it succeeds, then it’s a Python trust store problem.


List SSL Certificate Chain

  1. Issue the following command from an HA Terminal window to list the SSL Certificate chain.
openssl s_client -connect api.spotify.com:443|more
  1. Output should look something like this:
verify return:1
---
Certificate chain
 0 s:C=SE, L=Stockholm, O=Spotify AB, CN=*.spotify.com
   i:C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Dec  8 00:00:00 2025 GMT; NotAfter: Dec  8 23:59:59 2026 GMT
 1 s:C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1
   i:C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Mar 30 00:00:00 2021 GMT; NotAfter: Mar 29 23:59:59 2031 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
  1. press Control-C to stop the command, and return to terminal prompt.
  2. what output do you see for the “Certificate chain” section?

List OpenSSL Version

Create a temporary python script to list the current version of OpenSSL

  1. Issue the following command from an HA Terminal window. This should open up a new NANO editor window in the terminal
nano /usr/share/testcert.py
  1. copy / paste the following code into the editor window:
import ssl
print(ssl.OPENSSL_VERSION)

import urllib3.contrib.pyopenssl
print("PyOpenSSL active")
  1. press Control-X keys to exit the editor. You will be prompted to “Save Modified Buffer?”
  2. press Y key to “Save Modified Buffer”.
  3. press ENTER key to “Write to file …”.
  4. you should now be back to the terminal prompt.
  5. enter the following to execute the python script:
python /usr/share/testcert.py
  1. what is the output? It should look something like this:
OpenSSL 3.5.5 27 Jan 2026
Traceback (most recent call last):
  File "/usr/share/testcert.py", line 4, in <module>
    import urllib3.contrib.pyopenssl
ModuleNotFoundError: No module named 'urllib3'

Hi, thanks for your message. I have followed all the instructions provided. As you can see below some of the commands were not found.

  1. what output do you see?

CleanShot 2026-02-19 at 12.18.50

  1. what output do you see for the “Certificate chain” section?

CleanShot 2026-02-19 at 12.20.18

  1. what is the output? It should look something like this:

CleanShot 2026-02-19 at 12.21.39

@Jakesa
Based on your output, it looks like you are running the stock SSH add-on by Home Assistant, which only provides basic SSH connectivity (openssl command is not supported in the terminal). I would suggest uninstalling this if so, and replace it with the following …

Install the Advanced SSH & Web Terminal addon. This is an enhanced version of the provided SSH add-on by Home Assistant and focuses on security, usability, flexibility and also provides access using a web interface.

It will allow you to execute the openssl command to display SSL certificate info.

Apologies, I wasn’t aware of the advanced version, please see below:

  1. what output do you see for the “Certificate chain” section?

  1. what is the output? It should look something like this:

1 Like

@Jakesa
Not a problem, for most people the stock HA version is sufficient; for networking issues like this though, the more powerful tools are helpful in diagnosing.

What concerns me in the openssl output you posted is the unexpected eof while reading ... toward the end of the response output. I can’t tell if that’s due to the |more functionality, or if it truly can’t read something related to the certificate response.

If it truly is an error, then it mirrors the reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING in the exception output from your original text.

Here’s what I see for the end of the response when I run the openssl command without the |more suffix:

...
SSL handshake has read 3458 bytes and written 1628 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 2048 bit
This TLS version forbids renegotiation.
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Can you re-run the following command, and reply with the output located after the Verification: OK line:

openssl s_client -connect api.spotify.com:443

If you can, do a copy/paste of the text instead of a screen capture. I want to paste the response into a chatGPT query for possible explanation.

1 Like

@Jakesa
Did some more digging …
The problem is caused by TLS “Server Name Indication” logic.
More info about SNI can be found at https://www.cloudflare.com/learning/ssl/what-is-sni/

The problem is that the standard Python requests module is enabling urllib3 library fallback for SNI support, as the standard library doesn’t support SNI (or the ‘ssl’ library isn’t available). This happens in the requests.__init__ logic by calling the pyopenssl.inject_into_urllib3() method, which basically replaces the standard Python OpenSSL with the pyopenssl module.

I have proven this by manually calling the pyopenssl.inject_into_urllib3() method in the SpotifywebapiPython on my test machine; after doing so, I get the same SSLError(SSLError("bad handshake: Error([('SSL routines', '', 'certificate verify failed')])")) errors that you do.

So the REAL question is this:

  • What causes TLS SNI support to be disabled in the standard Python OpenSSL library?
  • If it’s NOT disabled by default, then is something in your environment purposefully (or inadvertantly) disabling TLS SNI?

To answer that, we need more details. The following script will list details about your default OpenSSL environment. It checks:

  • Python SSL capabilities (SNI support)
  • Which OpenSSL build Python is using
  • Whether urllib3 is injecting PyOpenSSL
  • If TLS + SNI works against Spotify and Cloudflare endpoints commonly used in HA integrations.

Create a temporary python script to view OpenSSL info.

  1. Issue the following command from an HA Terminal window. This should open up a new NANO editor window in the terminal:
nano /usr/share/testsslinfo.py
  1. copy / paste the following code into the editor window:
#!/usr/bin/env python3
"""
Home Assistant TLS / SNI diagnostic
Designed to detect common causes of:
SSL_R_UNEXPECTED_EOF_WHILE_READING
OAuth failures
Spotify / Cloudflare handshake problems
"""

import ssl
import socket
import sys

print("\n=== Python Environment ===")
print("Python version:", sys.version)

print("\n=== SSL Module Info ===")
print("OpenSSL version:", ssl.OPENSSL_VERSION)
print("HAS_SNI:", getattr(ssl, "HAS_SNI", False))
print("Default verify paths:", ssl.get_default_verify_paths())

print("\n=== urllib3 / PyOpenSSL Check ===")
try:
    import urllib3.util.ssl_
    print("urllib3 present: YES")
    print("Using PyOpenSSL:", urllib3.util.ssl_.IS_PYOPENSSL)
except Exception as e:
    print("urllib3 check failed:", e)

print("\n=== cryptography / pyOpenSSL versions ===")
try:
    import cryptography
    print("cryptography:", cryptography.__version__)
except Exception:
    print("cryptography: NOT INSTALLED")

try:
    import OpenSSL
    print("pyOpenSSL:", OpenSSL.__version__)
except Exception:
    print("pyOpenSSL: NOT INSTALLED")

print("\n=== TLS + SNI handshake test ===")

targets = [
    ("api.spotify.com", 443),
    ("accounts.spotify.com", 443),
    ("cloudflare.com", 443),
]

def test_sni(host, port):
    try:
        ctx = ssl.create_default_context()
        with socket.create_connection((host, port), timeout=10) as sock:
            with ctx.wrap_socket(sock, server_hostname=host) as ssock:
                cert = ssock.getpeercert()
                print(f"[OK] {host}")
                print("     TLS version:", ssock.version())
                print("     Cipher:", ssock.cipher())
                print("     Cert CN:", cert.get("subject", [["", "unknown"]])[0][0][1])
    except Exception as e:
        print(f"[FAIL] {host} -> {e}")

for host, port in targets:
    test_sni(host, port)

print("\n=== Direct SNI capability test ===")
if getattr(ssl, "HAS_SNI", False):
    print("SNI support is ENABLED in Python")
else:
    print("SNI support is DISABLED (this will break Spotify OAuth)")
  1. press Control-X keys to exit the editor.
  2. press Y key to “Save Modified Buffer”.
  3. press ENTER key to “Write to file …”.
  4. you should now be back to the terminal prompt.
  5. enter the following to execute the python script:
python /usr/share/testsslinfo.py
  1. what is the output? Please post it in the reply; it should look something like this:
=== Python Environment ===
Python version: 3.12.12 (main, Oct 11 2025, 01:16:26) [GCC 15.2.0]

=== SSL Module Info ===
OpenSSL version: OpenSSL 3.5.5 27 Jan 2026
HAS_SNI: True
Default verify paths: DefaultVerifyPaths(cafile='/etc/ssl/cert.pem', capath='/etc/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/ssl/certs')

=== urllib3 / PyOpenSSL Check ===
urllib3 check failed: No module named 'urllib3'

=== cryptography / pyOpenSSL versions ===
cryptography: NOT INSTALLED
pyOpenSSL: NOT INSTALLED

=== TLS + SNI handshake test ===
[OK] api.spotify.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: SE
[OK] accounts.spotify.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: SE
[OK] cloudflare.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: cloudflare.com

=== Direct SNI capability test ===
SNI support is ENABLED in Python
1 Like
  1. what is the output? Please post it in the reply; it should look something like this:

Thank you. This is my output:

➜  ~ python /usr/share/testsslinfo.py

=== Python Environment ===
Python version: 3.12.12 (main, Oct 11 2025, 01:16:26) [GCC 15.2.0]

=== SSL Module Info ===
OpenSSL version: OpenSSL 3.5.5 27 Jan 2026
HAS_SNI: True
Default verify paths: DefaultVerifyPaths(cafile='/etc/ssl/cert.pem', capath='/etc/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/ssl/certs')

=== urllib3 / PyOpenSSL Check ===
urllib3 check failed: No module named 'urllib3'

=== cryptography / pyOpenSSL versions ===
cryptography: NOT INSTALLED
pyOpenSSL: NOT INSTALLED

=== TLS + SNI handshake test ===
[OK] api.spotify.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: SE
[OK] accounts.spotify.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: SE
[OK] cloudflare.com
     TLS version: TLSv1.3
     Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
     Cert CN: cloudflare.com

=== Direct SNI capability test ===
SNI support is ENABLED in Python
1 Like

Can you re-run the following command, and reply with the output located after the Verification: OK line:

Just in case this was still needed:

New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 2048 bit
This TLS version forbids renegotiation.
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

@Jakesa
Your output pretty much matches what I have for mine. That tells me that it could be another integration that is being loaded and is importing PyOpenSSL.

Can you search all files and sub-folders in your HA /config/custom_components/ folder for the following phrases:

pyopenssl
urllib3
inject_into_urllib3

Just want to see if there are any other integrations that utilize the pyopenssl library.

1 Like

@Jakesa
Another good test would be to add some code into the SpotifyPlus integration init module to list the actual Python environment that is in play when the integration is ran. This could be different from the Python environment ran for the SSH Terminal addon, as an addon runs in its own docker container.

Try the following:

  1. find the HA /config/custom_components/spotifyplus folder; this is where the spotifyplus integration code resides.
  2. open the __init__.py folder in the editor of your choice (e.g. VsCode, Notepad, etc).
  3. find the following phrase: """ Configuration schema. """ (should be @line 194 in the file).
  4. copy / paste the following code on the line after the """ Configuration schema. """ line.

# --------------------------------------------------------------------------------------
import ssl
import socket
import sys

_logsi.LogWarning("SSLINFO - === Python Environment ===")
_logsi.LogWarning("SSLINFO - Python version: %s" % str(sys.version))

_logsi.LogWarning("SSLINFO - === SSL Module Info ===")
_logsi.LogWarning("SSLINFO - OpenSSL version: %s" % str(ssl.OPENSSL_VERSION))
_logsi.LogWarning("SSLINFO - HAS_SNI: %s" % str(getattr(ssl, "HAS_SNI", False)))
_logsi.LogWarning("SSLINFO - Default verify paths: %s" % str(ssl.get_default_verify_paths()))

_logsi.LogWarning("SSLINFO - === urllib3 / PyOpenSSL Check ===")
try:
    import urllib3.util.ssl_
    _logsi.LogWarning("SSLINFO - urllib3 present: YES")
    _logsi.LogWarning("SSLINFO - Using PyOpenSSL: %s" % str(urllib3.util.ssl_.IS_PYOPENSSL))
except Exception as e:
    _logsi.LogWarning("SSLINFO - urllib3 check failed: %s" % str(e))

_logsi.LogWarning("SSLINFO - === cryptography / pyOpenSSL versions ===")
try:
    import cryptography
    _logsi.LogWarning("SSLINFO - cryptography: %s" % str(cryptography.__version__))
except Exception:
    _logsi.LogWarning("SSLINFO - cryptography: NOT INSTALLED")

try:
    import OpenSSL
    _logsi.LogWarning("SSLINFO - pyOpenSSL: %s" % str(OpenSSL.__version__))
except Exception:
    _logsi.LogWarning("SSLINFO - pyOpenSSL: NOT INSTALLED")

_logsi.LogWarning("SSLINFO - === TLS + SNI handshake test ===")

targets = [
    ("api.spotify.com", 443),
    ("accounts.spotify.com", 443),
    ("cloudflare.com", 443),
]

def test_sni(host, port):
    try:
        ctx = ssl.create_default_context()
        with socket.create_connection((host, port), timeout=10) as sock:
            with ctx.wrap_socket(sock, server_hostname=host) as ssock:
                cert = ssock.getpeercert()
                _logsi.LogWarning("SSLINFO - [OK] %s" % str(host))
                _logsi.LogWarning("SSLINFO -      TLS version: %s" % str(ssock.version()))
                _logsi.LogWarning("SSLINFO -      Cipher: %s" % str(ssock.cipher()))
                _logsi.LogWarning("SSLINFO -      Cert CN: %s" % str(cert.get("subject", [["", "unknown"]])[0][0][1]))
    except Exception as e:
        _logsi.LogWarning("SSLINFO - [FAIL] %s -> %s" % (host, e))

for host, port in targets:
    test_sni(host, port)

_logsi.LogWarning("SSLINFO - === Direct SNI capability test ===")
if getattr(ssl, "HAS_SNI", False):
    _logsi.LogWarning("SSLINFO - SNI support is ENABLED in Python")
else:
    _logsi.LogWarning("SSLINFO - SNI support is DISABLED (this will break Spotify OAuth)")
  1. save the changes, and exit the editor.
  2. go back to your HA instance in your browser, and restart HA. This will apply the changes.
  3. give HA a minute to start up, then display the Settings \ System \ Logs in raw file format (available from the 3 dots menu in upper right).
  4. copy all lines that begin with SSLINFO - (all warnings); it should look something like this (minus the log line prefix data):
SSLINFO - === Python Environment ===
SSLINFO - Python version: 3.13.11 (main, Dec 10 2025, 17:01:53) [GCC 14.2.0]
SSLINFO - === SSL Module Info ===
SSLINFO - OpenSSL version: OpenSSL 3.5.4 30 Sep 2025
SSLINFO - HAS_SNI: True
SSLINFO - Default verify paths: DefaultVerifyPaths(cafile='/etc/ssl/cert.pem', capath='/etc/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/ssl/certs')
SSLINFO - === urllib3 / PyOpenSSL Check ===
SSLINFO - urllib3 present: YES
SSLINFO - Using PyOpenSSL: False
SSLINFO - === cryptography / pyOpenSSL versions ===
SSLINFO - cryptography: 46.0.5
SSLINFO - pyOpenSSL: 25.3.0
SSLINFO - === TLS + SNI handshake test ===
SSLINFO - [OK] api.spotify.com
SSLINFO -      TLS version: TLSv1.3
SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
SSLINFO -      Cert CN: SE
SSLINFO - [OK] accounts.spotify.com
SSLINFO -      TLS version: TLSv1.3
SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
SSLINFO -      Cert CN: SE
SSLINFO - [OK] cloudflare.com
SSLINFO -      TLS version: TLSv1.3
SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
SSLINFO -      Cert CN: cloudflare.com
SSLINFO - === Direct SNI capability test ===
SSLINFO - SNI support is ENABLED in Python
  1. paste the results in your reply.

Thanks for your patience with this. The problem is definitely some sort of conflict in your environment, as no other users are reporting the issue. I think this will tell us exactly what’s causing it, as it’s running inside the integration.

1 Like

No. Thank you for your patience, as you say this is clearly an issue with my configuration and you helping me resolve is very much appreciated.

I have a large amount of files in the custom components, and I’m heading out for the day. However, I will review them this evening, searching for the mentioned phrases.

  1. paste the results in your reply.

Nothing is showing up in the raw logs for SSLINFO just to confirm I placed it after """ Configuration schema. """ and before Custom Service Schema Please see below:

PLATFORMS = [Platform.MEDIA_PLAYER]
""" 
List of platforms to support. 
There should be a matching .py file for each (e.g. "media_player")
"""

CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
""" Configuration schema. """

# --------------------------------------------------------------------------------------
import ssl
import socket
import sys

_logsi.LogWarning("SSLINFO - === Python Environment ===")
_logsi.LogWarning("SSLINFO - Python version: %s" % str(sys.version))

_logsi.LogWarning("SSLINFO - === SSL Module Info ===")
_logsi.LogWarning("SSLINFO - OpenSSL version: %s" % str(ssl.OPENSSL_VERSION))
_logsi.LogWarning("SSLINFO - HAS_SNI: %s" % str(getattr(ssl, "HAS_SNI", False)))
_logsi.LogWarning("SSLINFO - Default verify paths: %s" % str(ssl.get_default_verify_paths()))

_logsi.LogWarning("SSLINFO - === urllib3 / PyOpenSSL Check ===")
try:
    import urllib3.util.ssl_
    _logsi.LogWarning("SSLINFO - urllib3 present: YES")
    _logsi.LogWarning("SSLINFO - Using PyOpenSSL: %s" % str(urllib3.util.ssl_.IS_PYOPENSSL))
except Exception as e:
    _logsi.LogWarning("SSLINFO - urllib3 check failed: %s" % str(e))

_logsi.LogWarning("SSLINFO - === cryptography / pyOpenSSL versions ===")
try:
    import cryptography
    _logsi.LogWarning("SSLINFO - cryptography: %s" % str(cryptography.__version__))
except Exception:
    _logsi.LogWarning("SSLINFO - cryptography: NOT INSTALLED")

try:
    import OpenSSL
    _logsi.LogWarning("SSLINFO - pyOpenSSL: %s" % str(OpenSSL.__version__))
except Exception:
    _logsi.LogWarning("SSLINFO - pyOpenSSL: NOT INSTALLED")

_logsi.LogWarning("SSLINFO - === TLS + SNI handshake test ===")

targets = [
    ("api.spotify.com", 443),
    ("accounts.spotify.com", 443),
    ("cloudflare.com", 443),
]

def test_sni(host, port):
    try:
        ctx = ssl.create_default_context()
        with socket.create_connection((host, port), timeout=10) as sock:
            with ctx.wrap_socket(sock, server_hostname=host) as ssock:
                cert = ssock.getpeercert()
                _logsi.LogWarning("SSLINFO - [OK] %s" % str(host))
                _logsi.LogWarning("SSLINFO -      TLS version: %s" % str(ssock.version()))
                _logsi.LogWarning("SSLINFO -      Cipher: %s" % str(ssock.cipher()))
                _logsi.LogWarning("SSLINFO -      Cert CN: %s" % str(cert.get("subject", [["", "unknown"]])[0][0][1]))
    except Exception as e:
        _logsi.LogWarning("SSLINFO - [FAIL] %s -> %s" % (host, e))

for host, port in targets:
    test_sni(host, port)

_logsi.LogWarning("SSLINFO - === Direct SNI capability test ===")
if getattr(ssl, "HAS_SNI", False):
    _logsi.LogWarning("SSLINFO - SNI support is ENABLED in Python")
else:
    _logsi.LogWarning("SSLINFO - SNI support is DISABLED (this will break Spotify OAuth)")


# -----------------------------------------------------------------------------------
# Custom Service schemas.
# -----------------------------------------------------------------------------------
SERVICE_SPOTIFY_ADD_PLAYER_QUEUE_ITEMS_SCHEMA = vol.Schema(
    {
        vol.Required("entity_id"): cv.entity_id,
        vol.Optional("uris"): cv.string,
        vol.Optional("device_id"): cv.string,
        vol.Optional("verify_device_id"): cv.boolean,
        vol.Optional("delay", default=0.15): vol.All(vol.Range(min=0,max=10.0)),

@Jakesa
Ok, I realize what’s going on … you are in the process of installing the integration, so I had the trace code in the wrong place. It should be added to the config_flow.py module (not the __init__.py module).

Just leave the trace code in the __init__py for now, as that will verify the environment once the integration is installed (we are getting there, I promise :D).

Try the following (similar to the previous instructions, but different so read them carefully):

  1. find the HA /config/custom_components/spotifyplus folder; this is where the spotifyplus integration code resides.
  2. open the config_flow.py folder in the editor of your choice (e.g. VsCode, Notepad, etc).
  3. find the following phrase: _logsi.LogDictionary(SILevel.Verbose, "Configure Component extra_authorize_data - data (dictionary)", data, colorValue=SIColors.Tan) (should be @line 103 in the file).
  4. copy / paste the following code on the line after the found phrase:
        # --------------------------------------------------------------------------------------
        import ssl
        import socket
        import sys

        _logsi.LogWarning("SSLINFO - === Application Credentials Environment ===")
        _logsi.LogWarning("SSLINFO - === Python Environment ===")
        _logsi.LogWarning("SSLINFO - Python version: %s" % str(sys.version))

        _logsi.LogWarning("SSLINFO - === SSL Module Info ===")
        _logsi.LogWarning("SSLINFO - OpenSSL version: %s" % str(ssl.OPENSSL_VERSION))
        _logsi.LogWarning("SSLINFO - HAS_SNI: %s" % str(getattr(ssl, "HAS_SNI", False)))
        _logsi.LogWarning("SSLINFO - Default verify paths: %s" % str(ssl.get_default_verify_paths()))

        _logsi.LogWarning("SSLINFO - === urllib3 / PyOpenSSL Check ===")
        try:
            import urllib3.util.ssl_
            _logsi.LogWarning("SSLINFO - urllib3 present: YES")
            _logsi.LogWarning("SSLINFO - Using PyOpenSSL: %s" % str(urllib3.util.ssl_.IS_PYOPENSSL))
        except Exception as e:
            _logsi.LogWarning("SSLINFO - urllib3 check failed: %s" % str(e))

        _logsi.LogWarning("SSLINFO - === cryptography / pyOpenSSL versions ===")
        try:
            import cryptography
            _logsi.LogWarning("SSLINFO - cryptography: %s" % str(cryptography.__version__))
        except Exception:
            _logsi.LogWarning("SSLINFO - cryptography: NOT INSTALLED")

        try:
            import OpenSSL
            _logsi.LogWarning("SSLINFO - pyOpenSSL: %s" % str(OpenSSL.__version__))
        except Exception:
            _logsi.LogWarning("SSLINFO - pyOpenSSL: NOT INSTALLED")

        _logsi.LogWarning("SSLINFO - === TLS + SNI handshake test ===")

        targets = [
            ("api.spotify.com", 443),
            ("accounts.spotify.com", 443),
            ("cloudflare.com", 443),
        ]

        def test_sni(host, port):
            try:
                ctx = ssl.create_default_context()
                with socket.create_connection((host, port), timeout=10) as sock:
                    with ctx.wrap_socket(sock, server_hostname=host) as ssock:
                        cert = ssock.getpeercert()
                        _logsi.LogWarning("SSLINFO - [OK] %s" % str(host))
                        _logsi.LogWarning("SSLINFO -      TLS version: %s" % str(ssock.version()))
                        _logsi.LogWarning("SSLINFO -      Cipher: %s" % str(ssock.cipher()))
                        _logsi.LogWarning("SSLINFO -      Cert CN: %s" % str(cert.get("subject", [["", "unknown"]])[0][0][1]))
            except Exception as e:
                _logsi.LogWarning("SSLINFO - [FAIL] %s -> %s" % (host, e))

        for host, port in targets:
            test_sni(host, port)

        _logsi.LogWarning("SSLINFO - === Direct SNI capability test ===")
        if getattr(ssl, "HAS_SNI", False):
            _logsi.LogWarning("SSLINFO - SNI support is ENABLED in Python")
        else:
            _logsi.LogWarning("SSLINFO - SNI support is DISABLED (this will break Spotify OAuth)")
        # --------------------------------------------------------------------------------------
  1. save the changes, and exit the editor.
  2. go back to your HA instance in your browser, and restart HA. This will apply the changes.
  3. give HA a minute to start up, then try adding the SpotifyPlus integration again via Settings \ Devices & Services, Add Integration button. It should display the initial Spotify login page, and return to HA to finalize the OAuth linking, and fail with a certificate verify failed error.
  4. display the Settings \ System \ Logs in raw file format (available from the 3 dots menu in upper right) and filter the list using SSLINFO.
  5. copy all lines that begin with SSLINFO - (all warnings).
2 Likes

Please see below. Does this mean it is most likely a custom_component or integration? I will start looking for those keywords you mentioned earlier:

2026-02-21 10:59:58.921 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === Python Environment ===
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - Python version: 3.13.9 (main, Nov 25 2025, 17:58:43) [GCC 14.2.0]
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === SSL Module Info ===
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - OpenSSL version: OpenSSL 3.5.4 30 Sep 2025
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - HAS_SNI: True
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - Default verify paths: DefaultVerifyPaths(cafile='/etc/ssl/cert.pem', capath='/etc/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/ssl/certs')
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === urllib3 / PyOpenSSL Check ===
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - urllib3 present: YES
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - Using PyOpenSSL: True
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === cryptography / pyOpenSSL versions ===
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - cryptography: 46.0.2
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - pyOpenSSL: 25.3.0
2026-02-21 10:59:58.922 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === TLS + SNI handshake test ===
2026-02-21 10:59:58.953 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - [OK] api.spotify.com
2026-02-21 10:59:58.953 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      TLS version: TLSv1.3
2026-02-21 10:59:58.953 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
2026-02-21 10:59:58.954 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cert CN: SE
2026-02-21 10:59:58.986 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - [OK] accounts.spotify.com
2026-02-21 10:59:58.986 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      TLS version: TLSv1.3
2026-02-21 10:59:58.986 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
2026-02-21 10:59:58.986 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cert CN: SE
2026-02-21 10:59:59.035 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - [OK] cloudflare.com
2026-02-21 10:59:59.035 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      TLS version: TLSv1.3
2026-02-21 10:59:59.035 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
2026-02-21 10:59:59.035 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO -      Cert CN: cloudflare.com
2026-02-21 10:59:59.036 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - === Direct SNI capability test ===
2026-02-21 10:59:59.036 WARNING (ImportExecutor_0) [custom_components.spotifyplus] SSLINFO - SNI support is ENABLED in Python

ok so I’ve got some hits on one of these keywords.

Waste Collection Schedule Integration
SSLError.py:

#Work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED errors using method discussed in
# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled

import requests
import ssl
import urllib3

class CustomHttpAdapter (requests.adapters.HTTPAdapter):
    # "Transport adapter" that allows us to use custom ssl_context.

    def __init__(self, ssl_context=None, **kwargs):
        self.ssl_context = ssl_context
        super().__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = urllib3.poolmanager.PoolManager(
            num_pools=connections, maxsize=maxsize,
            block=block, ssl_context=self.ssl_context)


def get_legacy_session():
    ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    ctx.options |= 0x4  # OP_LEGACY_SERVER_CONNECT
    session = requests.session()
    session.mount('https://', CustomHttpAdapter(ctx))
    return session

It also has many other .py files in this custom_component, which contain the keyword urllib3 for each council.

However, I have no hits for:

pyopenssl - other than HA logs and SpotifyPlus 
inject_into_urllib3

It is finally working! I cannot thank you enough for all your help and patience.

Your diagnostic script was the absolute turning point. When it output Using PyOpenSSL: True, it proved exactly what you suspected: another custom integration was globally injecting the outdated pyOpenSSL and poisoning the certificate verification for Spotify.

Since my SSH terminal is sandboxed and I couldn’t easily search the core site-packages for the dependency, I decided to do a massive spring clean of my custom integrations using a process of elimination.

I completely deleted the following integrations as I no longer needed them:

  • dyson_local
  • hildebrandglow_dcc
  • nest_protect
  • webrtc
  • yahoofinance
  • trello
  • xboxone

I also disabled petkit and waste_collection_schedule (though I had tested the waste schedule one prior and it didn’t fix it on its own).

After deleting that batch and restarting, the script finally flipped to Using PyOpenSSL: False, and SpotifyPlus authenticated instantly! After a year of banging my head against the wall thinking it was my BT router or an IPv6 issue, it was just a rogue third-party dependency the entire time.

Seriously, thank you for sticking with this and writing that script. Do you have a Ko-fi, GitHub Sponsors, or PayPal link? I would love to buy you a few beers or coffees to say cheers for getting this over the line! Found it!

2 Likes