psf/requests · error · OSError

Could not find the TLS key file, invalid path: {conn.key_fil

Error message

Could not find the TLS key file, invalid path: {conn.key_file}

What it means

The companion check to error 22: when `cert` is given as a (cert_file, key_file) tuple, HTTPAdapter.cert_verify verifies the private key path exists and raises this OSError if it doesn't. It only fires for the tuple form, since the string form sets key_file to None (key assumed to be in the same PEM).

Source

Thrown at src/requests/adapters.py:361

        else:
            conn.cert_reqs = "CERT_NONE"
            conn.ca_certs = None
            conn.ca_cert_dir = None

        if cert:
            if not isinstance(cert, basestring):
                conn.cert_file = cert[0]
                conn.key_file = cert[1]
            else:
                conn.cert_file = cert
                conn.key_file = None
            if conn.cert_file and not os.path.exists(conn.cert_file):
                raise OSError(
                    f"Could not find the TLS certificate file, "
                    f"invalid path: {conn.cert_file}"
                )
            if conn.key_file and not os.path.exists(conn.key_file):
                raise OSError(
                    f"Could not find the TLS key file, invalid path: {conn.key_file}"
                )

    def build_response(self, req: PreparedRequest, resp: Any) -> Response:
        """Builds a :class:`Response <requests.Response>` object from a urllib3
        response. This should not be called from user code, and is only exposed
        for use when subclassing the
        :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`

        :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
        :param resp: The urllib3 response object.
        :rtype: requests.Response
        """
        assert _is_prepared(req)
        response = Response()

        # Fallback to None if there's no status_code, for whatever reason.
        response.status_code = getattr(resp, "status", None)  # type: ignore[assignment]

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Confirm the key file exists at the given absolute path and the tuple order is (cert, key)
  2. Provision the private key to the runtime environment (secret mount, deploy step) — it's often intentionally absent from the artifact
  3. If cert and key are combined in one PEM, pass that single path as a string instead of a tuple

Example fix

# before
requests.get(url, cert=('client.crt', 'client.key'))  # key never deployed

# after — combined PEM shipped via secret mount
requests.get(url, cert='/run/secrets/client-combined.pem')
Defensive patterns

Strategy: validation

Validate before calling

import os
if isinstance(cert, tuple):
    key_file = cert[1]
    if not os.path.isfile(key_file):
        raise FileNotFoundError(f"Client TLS key not found: {key_file}")

Try / catch

try:
    resp = session.get(url, cert=(cert_file, key_file))
except OSError as e:
    raise RuntimeError(f"Client key path invalid: {key_file}") from e

Prevention

When it happens

Trigger: requests.get(url, cert=('client.crt','client.key')) where the key path is missing, misspelled, or unreadable-by-path; swapping tuple elements so the 'key' slot holds a nonexistent path. Raised at src/requests/adapters.py:360-363.

Common situations: Private keys deliberately excluded from repos/images (correctly gitignored) and never provisioned to the runtime; key files with restrictive locations (/etc/ssl/private) referenced from a non-root process using a wrong path; cert renewal tooling writing the new key under a different filename.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/b4a8a0373582a990.json. Report an issue: GitHub ↗.