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
scpor a secrets manager โ never copy-paste through a terminal or text editor. - Touched a cert on a Windows machine? Run
dos2unixthe 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 verifybefore 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.

