# Python UNSAFE_LEGACY_RENEGOTIATION_DISABLED: causes and fix

> OpenSSL 3 refuses servers without RFC 5746 secure renegotiation. Fix the server, or allow legacy connect for one host with verification on.

Source: https://ssl.sh/errors/python-unsafe-legacy-renegotiation-disabled/
Last updated: 2026-11-10

**`[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled` means the server does not support RFC 5746 secure renegotiation, and your client's OpenSSL 3 refuses to connect to it.** The certificate is fine. The server, or a device in front of it, runs a TLS stack older than the 2010 fix, which OpenSSL 3.0 no longer tolerates by default.

## What the error means

OpenSSL 3 requires the RFC 5746 renegotiation extension by default, and this server does not send it. In 2009 a flaw in TLS renegotiation (CVE-2009-3555) let an attacker splice their own data in front of a victim's session. RFC 5746, published in February 2010, fixed it with the `renegotiation_info` extension: patched clients and servers prove to each other that a renegotiation belongs to the same connection. A server that never sends this extension is "unpatched".

OpenSSL's documentation is explicit: if neither `SSL_OP_LEGACY_SERVER_CONNECT` nor `SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION` is set, initial connections to unpatched servers fail. OpenSSL 1.1.1 set `SSL_OP_LEGACY_SERVER_CONNECT` by default; OpenSSL 3.0 no longer does. That is why the error typically shows up right after moving to Ubuntu 22.04 (which ships OpenSSL 3.0.2), a newer Docker base image, or a Python build linked against OpenSSL 3, while the server itself has not changed for years.

In Python you will usually see it wrapped, for example inside [requests' Max retries exceeded](https://ssl.sh/errors/python-requests-max-retries-ssl-error/) or in AWS CLI output. Other OpenSSL 3 clients print the same reason: `error:0A000152:SSL routines::unsafe legacy renegotiation disabled`.

## Causes, most common first

### 1. An old server or appliance without RFC 5746

Legacy application servers, embedded devices, old hardware load balancers and outdated web application firewalls are the usual suspects.

- **Confirm:** run OpenSSL 3 with the legacy option enabled only for the test, and read the renegotiation line:

```bash
openssl version
openssl s_client -connect example.com:443 -servername example.com -legacy_server_connect </dev/null 2>/dev/null | grep -i renegotiation
# Secure Renegotiation IS NOT supported  -> server is unpatched
```

- **Fix:** update the server's TLS library or the appliance firmware. Any TLS stack maintained after 2010 supports RFC 5746. Enabling TLS 1.3 also removes the problem for clients that support it, because TLS 1.3 forbids renegotiation entirely.

### 2. A TLS-inspecting proxy in the path

If the issuer shown by `openssl s_client` is your company's proxy or security product rather than a public CA, the unpatched party is the proxy, not the website. See [corporate SSL inspection](https://ssl.sh/errors/corporate-ssl-inspection/).

- **Fix:** ask the network team to update the proxy, or to exempt the destination from inspection.

### 3. Your client moved to OpenSSL 3

This is the trigger rather than the cause, but it explains "it worked yesterday". Check which OpenSSL your Python uses:

```bash
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
```

## Fix commands by platform

The server-side fix is always preferred. While you wait for it, allow the legacy connection for that one destination only, and keep certificate verification on.

**Python 3.12+ with requests**, scoped to one host:

```python
import ssl
import requests
from requests.adapters import HTTPAdapter

class LegacyServerConnectAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        ctx = ssl.create_default_context()          # verification stays on
        ctx.options |= getattr(ssl, "OP_LEGACY_SERVER_CONNECT", 0x4)
        kwargs["ssl_context"] = ctx
        return super().init_poolmanager(*args, **kwargs)

session = requests.Session()
session.mount("https://legacy.example.com/", LegacyServerConnectAdapter())
print(session.get("https://legacy.example.com/", timeout=10).status_code)
```

On Python 3.11 and earlier the constant does not exist; `0x4` is the value of `SSL_OP_LEGACY_SERVER_CONNECT` in OpenSSL's headers, which is why the `getattr` fallback is there.

**Any program linked against OpenSSL 3 (Python, and curl or git when they are built with OpenSSL)**, scoped to one process with a dedicated config file:

```ini
# /etc/ssl/openssl-legacy-connect.cnf
openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyServerConnect
```

```bash
OPENSSL_CONF=/etc/ssl/openssl-legacy-connect.cnf python3 job.py
```

`OPENSSL_CONF` replaces the system configuration for that process only, so do not put this file in place of `/etc/ssl/openssl.cnf`.

**Node.js 17+** (also built on OpenSSL 3):

```javascript
const https = require('node:https');
const { constants } = require('node:crypto');
https.get('https://legacy.example.com/', { secureOptions: constants.SSL_OP_LEGACY_SERVER_CONNECT }, res => console.log(res.statusCode));
```

**Windows:** the python.org installer ships its own OpenSSL, so the Python fix above applies unchanged.

## Don't disable certificate verification

`verify=False`, `CERT_NONE` or `curl -k` do not fix this error at all: the handshake fails before any certificate is checked. Adding them only removes protection against impersonation, so tokens and passwords can be intercepted by anyone on the path, and the code stays broken.

Also avoid the broader `UnsafeLegacyRenegotiation` / `SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION` and avoid editing the system-wide OpenSSL configuration, which would weaken every TLS client on the machine. The safer path is the one above: fix the server, or set `OP_LEGACY_SERVER_CONNECT` on one context, for one host, with verification on, and remove it once the server is updated. Track it like any other temporary exception.

## Verify the fix

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep -E "Secure Renegotiation|Protocol"
```

After the server is fixed, this works **without** `-legacy_server_connect` and prints `Secure Renegotiation IS supported` (TLS 1.2) or shows `TLSv1.3`. Then remove the client workaround and rerun your Python code with default settings. You can also check the protocol versions and cipher suites the server offers in the [SSL checker](https://ssl.sh/tools/ssl-checker/), and generate a modern server configuration with the [TLS config generator](https://ssl.sh/tools/tls-config-generator/). For background on how renegotiation fits into the handshake, see [the TLS handshake](https://ssl.sh/learn/tls-handshake/).
