Error message
Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. MalformedFraming
Python cryptography — ValueError from load_pem_* functions
What it means
load_pem_private_key / load_pem_public_key / load_pem_x509_certificate could not find intact PEM framing — matching BEGIN and END lines with a base64 body between them. The trailing token (MalformedFraming here) is the parser's diagnosis; the FAQ link ships inside the error text itself.
Why it happens
Escaped newlines from environment/config
commonPEMs delivered through .env files, docker-compose, Kubernetes manifests or JSON often arrive as one line with literal \n sequences. The framing regex needs real newlines around the BEGIN/END markers.
Truncated or partially copied PEM
commonA missing END line (clipboard cut short, a YAML block scalar eating the last line) breaks the frame. Every PEM must close with its matching -----END ...----- line.
Stray characters around the block
occasionalQuotes from JSON serialization, indentation added by YAML, a UTF-8 BOM, or CRLF line endings in strict contexts corrupt the framing without changing anything visible.
Concatenated bundle with a damaged separator
rareChain files carrying several PEM blocks fail as a whole when one block lost its END line or two blocks ran together. The loader reports the file, not the block — so bisect: split the bundle and load each object individually to find the damaged one.
How to fix it
- 1.
Normalize before loading
Restore newlines, strip whitespace and quotes, and confirm the first line before handing bytes to the loader.
python import os pem = os.environ["PRIVATE_KEY"].replace("\\n", "\n").strip().strip('"').encode() assert pem.startswith(b"-----BEGIN"), pem[:30] from cryptography.hazmat.primitives.serialization import load_pem_private_key key = load_pem_private_key(pem, password=None) - 2.
Prefer files or native multiline secrets
Mounted secret files avoid the escaping problem entirely; Kubernetes Secrets and most vaults can deliver the PEM byte-exact.
python with open("/run/secrets/signing_key.pem", "rb") as f: key = load_pem_private_key(f.read(), password=None) - 3.
Validate the framing mechanically
A quick check that BEGIN/END lines exist and match narrows the fault to content vs transport.
python lines = pem.decode().splitlines() print(lines[0]) # -----BEGIN PRIVATE KEY----- print(lines[-1]) # -----END PRIVATE KEY-----