Fix error:0906D06C:PEM routines:PEM_read_bio:no start line When PEM File Is Malformed

intermediate๐Ÿ”’ SSL/TLS2026-06-20| Linux / macOS / Windows WSL โ€” OpenSSL 1.0.x, 1.1.x, 3.x โ€” any tool that reads PEM files (nginx, Apache, curl, Python ssl, Node.js tls)

Error Message

error:0906D06C:PEM routines:PEM_read_bio:no start line
#openssl#pem#ssl-tls

The Scenario

It's 2 AM. You're deploying a new TLS certificate on the server. You run the reload command and get hit with:

error:0906D06C:PEM routines:PEM_read_bio:no start line

nginx refuses to start. The site is down. OpenSSL can't parse your certificate file โ€” but the file is clearly sitting there on disk. What went wrong?

OpenSSL scanned the file and never found a header like -----BEGIN CERTIFICATE-----. That means the file is the wrong format entirely, got corrupted in transit, picked up encoding garbage, or simply isn't the file your config thinks it is.

Quick Diagnosis

Run these three checks first โ€” they'll narrow down the cause in under a minute:

# Check the first line โ€” should be a -----BEGIN ... ----- header
head -1 /etc/ssl/certs/server.crt

# Check file encoding and BOM
file /etc/ssl/certs/server.crt

# Try to parse it with OpenSSL
openssl x509 -in /etc/ssl/certs/server.crt -text -noout 2>&1 | head -5

If head -1 spits out garbage bytes, a blank line, or raw binary โ€” that's your answer right there.

Root Causes and Fixes

Cause 1: File Is in DER Format, Not PEM

DER is the binary encoding of the same certificate data. Certificate authorities โ€” and tools like Java's keytool โ€” default to DER output. The dead giveaway: the file starts with binary bytes, not -----BEGIN. Extensions like .cer and .der are strong hints too.

Confirm it's DER:

file server.crt
# Output: server.crt: data  (binary, not ASCII)

Convert DER to PEM:

# For a certificate
openssl x509 -inform DER -in server.crt -out server.pem

# For a private key
openssl rsa -inform DER -in server.key -out server.key.pem

# For a PKCS#12 bundle (.p12 / .pfx)
openssl pkcs12 -in bundle.p12 -out server.pem -nodes

Cause 2: File Has a BOM (Byte Order Mark)

Notepad and many Windows-based tools quietly prepend a UTF-8 BOM (\xEF\xBB\xBF) when saving text files. Three invisible bytes before -----BEGIN is enough to break OpenSSL's header detection entirely.

Check for BOM:

xxd server.pem | head -1
# If you see: ef bb bf 2d 2d 2d 2d 2d  โ€” that's a BOM before the dashes

Strip it:

sed -i '1s/^\xEF\xBB\xBF//' server.pem

Cause 3: Windows CRLF Line Endings

Files touched by Windows tools often carry \r\n line endings instead of \n. OpenSSL expects Unix-style line endings and will choke on the extra carriage returns.

# Check for carriage returns
cat -A server.pem | head -3
# ^M at end of lines = \r is present

# Fix with dos2unix
dos2unix server.pem
# Or without dos2unix:
sed -i 's/\r//' server.pem

Cause 4: File Is Empty or Wrong Path

Worth ruling out early: a botched scp, a misconfigured $CERT_PATH env var, or a config typo can silently point nginx at a zero-byte or nonexistent file. The error looks identical to a malformed cert.

# Check file size
ls -lh server.pem
# 0 bytes = empty file

# Check what the app is actually reading
strace -e openat nginx -t 2>&1 | grep pem
# Or check your config directly
nginx -T | grep ssl_certificate

Cause 5: Only Part of the PEM Chain Is Present

Many setups need the full chain โ€” leaf cert plus intermediate CAs. A truncated paste or a missing intermediate triggers this error just as reliably as a corrupted file.

# Count how many certs are in the file
grep -c 'BEGIN CERTIFICATE' server.pem

# Inspect each cert in the bundle
openssl crl2pkcs7 -nocrl -certfile server.pem | openssl pkcs7 -print_certs -text -noout | grep Subject

Rebuild the chain if needed:

cat server.crt intermediate.crt root.crt > fullchain.pem

Cause 6: Private Key and Certificate Mixed Up

nginx and Apache use separate directives for the cert and the key. Swapping the paths is an easy mistake โ€” and OpenSSL's error message won't tell you that's the problem.

# Confirm which file is which
openssl x509 -in server.pem -noout -subject 2>/dev/null && echo "This is a certificate"
openssl rsa -in server.pem -noout -check 2>/dev/null && echo "This is a private key"

Verification

After applying any fix, verify OpenSSL can parse the file cleanly before restarting your server:

# Certificate
openssl x509 -in server.pem -text -noout
# Should print: Subject:, Issuer:, Validity:, etc.

# Private key
openssl rsa -in server.key -check
# Should print: RSA key ok

# Check cert and key match (modulus must be identical)
openssl x509 -noout -modulus -in server.pem | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
# Both lines must output the same hash

# Test your full TLS config (nginx example)
nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Permanent Fix: Certificate Handling Checklist

  • Transfer cert files with scp or a secrets manager โ€” never copy-paste through a terminal or text editor.
  • Touched a cert on a Windows machine? Run dos2unix the moment it lands on the server.
  • After every CA export, validate with openssl x509 -in file -noout -subject โ€” takes two seconds and saves you from midnight incidents.
  • Keep cert, key, and chain in separate files. Combine into fullchain only when the app explicitly requires it.
  • In CI/CD, add a pre-deploy step that runs openssl verify before reloading nginx or Apache. Catch broken certs in the pipeline, not in production.

Python / Node.js Context

The same error surfaces in application logs when your code loads certs directly:

# Python: ssl.SSLError wrapping the same OpenSSL error
import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('/path/to/server.pem', '/path/to/server.key')
# Throws ssl.SSLError: [SSL] PEM lib (_ssl.c:4045) if file is malformed

# Node.js: same underlying OpenSSL
https.createServer({
  cert: fs.readFileSync('/path/to/server.pem'),  // Will throw if malformed
  key: fs.readFileSync('/path/to/server.key'),
})

Same root cause, same fix. Run openssl x509 -in server.pem -noout -subject on the file first โ€” if OpenSSL rejects it on the command line, your app will reject it too.

Related Error Notes