PEM vs DER is not a choice of different data: a certificate, key, CSR or CRL is always defined as DER-encoded ASN.1, and PEM is a text wrapper around those exact bytes. The question is which representation a tool, protocol or platform expects, and how to recognize and convert each one.
At a glance
| DER | PEM | |
|---|---|---|
| What it is | Distinguished Encoding Rules of ASN.1 (ITU-T X.690): binary tag-length-value | RFC 7468 text: base64 of DER between labels |
| Starts with | Byte 0x30 (SEQUENCE) for certificates and keys | -----BEGIN CERTIFICATE-----, -----BEGIN PRIVATE KEY-----, etc. |
| Size (P-256 certificate, measured) | 389 bytes | 583 bytes (about 4/3 plus line breaks and labels) |
| Objects per file | One | Any number of blocks (chains, key plus certificate) |
| Self-describing | No label; type known from context | Label names the structure |
| Common extensions | .der, .cer, .crt (ambiguous) | .pem, .crt, .cer, .key, .csr (ambiguous) |
| Fingerprint | SHA-256 of these bytes | Same value: fingerprints are computed over the DER inside |
| Safe in email, YAML, environment variables | No | Yes |
What each one is
DER is the canonical binary encoding of ASN.1 data: every value is a tag, a length and contents, with exactly one valid encoding per value. That canonical property is why signatures work: a certificate's signature covers the DER bytes of its tbsCertificate, and verifiers re-encode nothing.
PEM (named after the 1993 Privacy-Enhanced Mail standard and specified for PKIX objects in RFC 7468) wraps those bytes in standard base64, conventionally 64 characters per line, between a BEGIN and an END line that name the structure: CERTIFICATE, X509 CRL, CERTIFICATE REQUEST, PUBLIC KEY, PRIVATE KEY, ENCRYPTED PRIVATE KEY, plus OpenSSL's legacy RSA PRIVATE KEY and EC PRIVATE KEY. Text before, between or after blocks is ignored, which is how bundles with comments and openssl x509 -text output work.
Converting and checking
The script below, run with OpenSSL 3.6.5, creates a throwaway certificate and shows that both encodings carry the same object.
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl req -new -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout key.pem \
-subj "/CN=example.com" -days 30 -out cert.pem 2>/dev/null
openssl x509 -in cert.pem -outform DER -out cert.der
printf 'cert.pem %s bytes, cert.der %s bytes\n' "$(wc -c < cert.pem | tr -d ' ')" "$(wc -c < cert.der | tr -d ' ')"
head -1 cert.pem
openssl x509 -in cert.der -inform DER -noout -fingerprint -sha256 | cut -c1-40
openssl x509 -in cert.pem -noout -fingerprint -sha256 | cut -c1-40
cd / && rm -rf "$dir"
cert.pem 583 bytes, cert.der 389 bytes
-----BEGIN CERTIFICATE-----
sha256 Fingerprint=FE:8B:DC:19:30:EE:A8:
sha256 Fingerprint=FE:8B:DC:19:30:EE:A8:
The same pattern applies to keys (openssl pkey -in key.pem -outform DER), CSRs (openssl req -outform DER) and CRLs (openssl crl -outform DER). To tell an unknown file apart: if it starts with -----BEGIN, it is PEM; if xxd file | head -1 shows 3082 near the start, it is almost certainly DER.
Typical errors
no start line/Expecting: TRUSTED CERTIFICATEfrom OpenSSL: the tool expected PEM and got DER, or a PEM block with a different label. Add-inform DERor check the label.header too long/wrong tag: the tool expected DER and got PEM text, or a truncated file.- Windows line endings or stray spaces inside a PEM block pasted into a web form or YAML; strict parsers reject them. Re-encode rather than editing by hand.
- A PEM bundle where one certificate was expected: many tools read only the first block, so the order matters (leaf first for servers).
- Base64url instead of base64: PEM uses the standard alphabet with
+,/and padding; JOSE and WebAuthn use base64url. A certificate copied out of a JWKx5cmember is standard base64 DER, and wrapping it in PEM lines makes it a valid PEM file.
The ASN.1 explorer accepts either encoding and shows the structure, and the PEM bundle splitter separates multi-certificate files.
Bundles and chains
Because DER holds exactly one object, anything that needs several objects in one file uses PEM concatenation or a container format. A server's fullchain.pem is the leaf certificate followed by intermediates; a CA bundle such as /etc/ssl/certs/ca-certificates.crt is hundreds of root certificates in one PEM file. The DER-based alternatives are PKCS #7 certificate bundles (.p7b, a CMS SignedData with no content and only certificates, common on Windows) and PKCS #12 (.pfx) when a private key travels with the chain. Converting a .p7b to PEM is openssl pkcs7 -print_certs -in bundle.p7b, which writes each certificate as its own PEM block.
Order and completeness matter more than encoding: a server must send its leaf first and every intermediate needed to reach a root that clients trust, and a missing intermediate breaks clients that do not fetch it themselves, whatever the file format.
When to choose which
- Web servers, reverse proxies, ACME clients: PEM (
fullchain.pem,privkey.pem). - Java (
keytool -importcert,CertificateFactory): either, but DER is the canonical input; keystores use PKCS #12 for key plus chain. - Windows certificate import: DER
.ceror PEM-encoded.cer; key plus certificate goes into.pfx. - Embedding in source code, environment variables, Kubernetes secrets: PEM text (or base64 DER, if a field demands it).
- Wire protocols and binary containers: DER, always; TLS, CMS, OCSP and X.509 extensions never carry PEM.
- Archival and audit: DER for byte-exact storage; PEM if humans need to inspect files.
Migration notes
There is nothing to migrate in the cryptographic sense; switching encodings is a deployment detail.
- Standardize file naming:
.pemfor PEM,.derfor DER, and avoid ambiguous.crtand.cerin your own tooling. - Convert at deployment time, keeping one source of truth (usually PEM) in your repository or secret store.
- Check fingerprints after conversion with
openssl x509 -noout -fingerprint -sha256; they must match. - For private keys, keep encryption on:
openssl pkey -outform DERwrites an unencrypted key unless you pass a cipher, and DER keys are often handled by tools with fewer warnings. - Document expected formats in configuration (
ssl_certificateexpects PEM; Java truststores expect DER or PKCS #12), since most format errors come from guessing.