# Certificate formats explained: PEM, DER, PFX, P7B and JKS

> PEM, DER, PKCS#7, PKCS#12 and JKS explained: what each file can hold, how to identify an unknown file, and which format each server expects.

Source: https://ssl.sh/learn/certificate-formats/
Last updated: 2026-11-10

Certificate files differ in two ways: the encoding (binary DER or Base64 PEM text) and the container (one certificate, a PKCS#7 bundle, a password-protected PKCS#12 archive with the key, or a Java keystore). Nginx, Apache and most Linux software want PEM; Windows and IIS want PKCS#12 (.pfx); Java accepts PKCS#12 or JKS. File extensions are only a hint.

## Encoding versus container

The certificate itself is always the same X.509 structure defined in RFC 5280; the formats only change how it is written down and what travels with it. The following table covers the five formats you will meet in practice.

| Format | Encoding | Can contain | Private key? | Usual extensions | Specification |
|---|---|---|---|---|---|
| DER | Binary ASN.1 | One certificate, key or CRL | Only as a separate file | .der, .cer, .crt | ITU-T X.690, RFC 5280 |
| PEM | Base64 text with header lines | Any number of certificates, keys, CSRs or CRLs | Yes, as its own block | .pem, .crt, .cer, .key | RFC 7468 |
| PKCS#7 | DER or PEM | Certificates and CRLs | No | .p7b, .p7c | RFC 2315 |
| PKCS#12 | Binary | Key, certificate and chain, encrypted with a password | Yes | .pfx, .p12 | RFC 7292 |
| JKS | Binary, Java-specific | Keys and certificates in a keystore | Yes | .jks, .keystore | Java documentation |

The relationship is easier to see as layers:

```text
X.509 certificate (ASN.1 structure, RFC 5280)
 |
 +-- written as binary DER .............................. .der / .cer
      |
      +-- Base64 + "-----BEGIN CERTIFICATE-----" ........ PEM (.pem / .crt)
      +-- several certificates in one envelope .......... PKCS#7 (.p7b)
      +-- certificates + private key, password-protected  PKCS#12 (.pfx / .p12)
```

## PEM: the text format most servers use

PEM is DER encoded in Base64, wrapped at 64 characters per line, between a `BEGIN` line and an `END` line that name the content. RFC 7468 standardized the labels that had been in use for decades:

| Label | Content |
|---|---|
| `CERTIFICATE` | An X.509 certificate |
| `PRIVATE KEY` | An unencrypted PKCS#8 private key (any algorithm) |
| `ENCRYPTED PRIVATE KEY` | A password-protected PKCS#8 private key |
| `RSA PRIVATE KEY` / `EC PRIVATE KEY` | Older algorithm-specific key formats (PKCS#1, SEC 1) |
| `CERTIFICATE REQUEST` | A [CSR](https://ssl.sh/glossary/csr/) |
| `X509 CRL` | A certificate revocation list |
| `PKCS7` | A PKCS#7 bundle in PEM form |

Since OpenSSL 3.0, `openssl genrsa` writes `-----BEGIN PRIVATE KEY-----` (PKCS#8) by default instead of the older `RSA PRIVATE KEY`; both work in Nginx and Apache.

Because PEM is text, you can concatenate blocks. A `fullchain.pem` is the leaf certificate followed by its intermediates, and HAProxy expects the leaf, the intermediates and the key in one file. Common PEM problems are Windows line endings, a byte-order mark added by an editor, missing line breaks between blocks after a copy-paste, and an `-----END` line cut off. Text outside the blocks, such as the `Bag Attributes` lines that `openssl pkcs12` writes, is ignored by OpenSSL-based servers.

## DER: the binary form

DER is the canonical binary encoding of the ASN.1 structure. A DER certificate almost always starts with the bytes `30 82`, an ASN.1 SEQUENCE with a two-byte length. Windows certificate exports offer both "DER encoded binary X.509 (.CER)" and "Base-64 encoded X.509 (.CER)", so two files with the same `.cer` extension can be in different formats. Java's `keytool -exportcert` also writes DER unless you add `-rfc`.

```bash
# DER to PEM, and back
openssl x509 -inform der -in certificate.cer -out certificate.pem
openssl x509 -in certificate.pem -outform der -out certificate.der
```

## PKCS#7 (.p7b): certificates without the key

A PKCS#7 bundle holds certificates (and optionally CRLs) inside a signed-data envelope with no signature and no key. Some CAs deliver the intermediate chain this way, and Windows and Java can import it in one step. Because a .p7b never contains a private key, it cannot be used on its own to configure a server.

```bash
# Extract all certificates from a P7B as PEM
openssl pkcs7 -in chain.p7b -print_certs -out chain.pem
# Build a P7B from a PEM chain
openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b
```

## PKCS#12 (.pfx/.p12): key and chain in one encrypted file

PKCS#12 is the format for moving a private key together with its certificate and chain. It stores each item in a "bag", encrypts the key (and usually the certificates) with a password-derived key, and protects the whole file with a MAC. Windows, IIS, Azure and many Java and macOS tools import it directly. The name PFX comes from Microsoft's earlier Personal Information Exchange format, which PKCS#12 replaced.

### The OpenSSL 3.0 default change

OpenSSL 3.0 changed how `openssl pkcs12 -export` protects new files. Older versions used RC2-40 or 3DES with SHA-1, algorithms that OpenSSL 3.0 moved into its legacy provider. With OpenSSL 3.x, the default looks like this:

```bash
openssl pkcs12 -export -inkey example.com.key -in example.com.crt \
  -certfile intermediate.pem -out example.com.pfx
openssl pkcs12 -info -in example.com.pfx -noout
```

```text
MAC: sha256, Iteration 2048
MAC length: 32, salt length: 16
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
```

This causes problems in two directions:

- **Old importers cannot read new files.** Older Windows Server releases, older Java 8 updates and older macOS Keychain versions may reject an AES-256 PKCS#12 file, often with a misleading "wrong password" error. Create the file with `-legacy`, which uses 3DES for the key, RC2 for the certificates and a SHA-1 MAC.
- **OpenSSL 3 cannot read old files without help.** Opening a PFX exported years ago by Windows or OpenSSL 1.x fails with an `unsupported` error naming `RC2-40-CBC`. Add `-legacy` to the command to load the legacy provider.

```text
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...
Algorithm (RC2-40-CBC : 0), Properties ()
```

`-nodes` is deprecated since OpenSSL 3.0; the new spelling for "do not encrypt the extracted key" is `-noenc`. The [PKCS#12 cookbook](https://ssl.sh/cookbook/pkcs12/) has ready-to-use commands for both directions.

## JKS and Java keystores

JKS is a proprietary Java keystore format with weak integrity protection, and modern Java no longer creates it by default. Since JDK 9 (JEP 229) the default keystore type is PKCS12, which means a `.p12` file made with OpenSSL can be used as a Java keystore directly. To convert an existing JKS:

```bash
keytool -importkeystore -srckeystore server.jks -srcstoretype jks \
  -destkeystore server.p12 -deststoretype pkcs12
keytool -list -v -keystore server.p12
```

`keytool` prints a warning recommending migration to PKCS12 whenever it opens a JKS file. Older servlet containers and some appliances still require JKS; for everything else, prefer PKCS12.

## Private key formats

The private key has its own format choices, and a key in the wrong one is a common reason a server refuses to start. Three PEM variants are in everyday use:

| Header | Structure | Notes |
|---|---|---|
| `BEGIN PRIVATE KEY` | PKCS#8, unencrypted | Default output of OpenSSL 3.x for all key types |
| `BEGIN ENCRYPTED PRIVATE KEY` | PKCS#8, password-protected | The server needs the password at startup |
| `BEGIN RSA PRIVATE KEY` / `BEGIN EC PRIVATE KEY` | PKCS#1 / SEC 1 | Older OpenSSL output; still widely accepted |

`openssl pkey` converts between them without changing the key itself:

```bash
# Any supported key to unencrypted PKCS#8
openssl pkey -in old.key -out new.key
# Show the algorithm and size
openssl pkey -in new.key -noout -text | head -1
```

Web servers such as Nginx can read an encrypted key only if you provide the password through `ssl_password_file`, so most deployments keep the key unencrypted and protect it with file permissions (`chmod 600`, owned by root). Treat a PKCS#12 file the same way: the password protects it in transit, but anyone with the file and the password has your key. Send the password through a different channel from the file, and do not upload private keys or PFX files to websites that process them on a server.

## How to identify an unknown file

Look at the first bytes, then ask OpenSSL to parse the file in the format you suspect. Extensions are not reliable.

| What you see | Format | Try |
|---|---|---|
| `-----BEGIN CERTIFICATE-----` | PEM certificate | `openssl x509 -in f -noout -text` |
| `-----BEGIN PKCS7-----` | PEM PKCS#7 | `openssl pkcs7 -in f -print_certs` |
| `-----BEGIN PRIVATE KEY-----` | PEM PKCS#8 key | `openssl pkey -in f -noout -text` |
| `-----BEGIN CERTIFICATE REQUEST-----` | PEM CSR | `openssl req -in f -noout -text` |
| Binary, starts with `30 82` | DER certificate, PKCS#12 or DER PKCS#7 | try `x509 -inform der`, then `pkcs12 -info`, then `pkcs7 -inform der` |

The `file` command on Linux and macOS recognizes some of these:

```bash
file leaf.der site.pfx chain.p7b leaf.pem
```

```text
leaf.der:  Certificate, Version=3
site.pfx:  data
chain.p7b: ASCII text
leaf.pem:  PEM certificate
```

A PKCS#12 file shows up as plain `data`, so if a binary file is not a DER certificate, try `openssl pkcs12 -info -in file -noout` next. The [certificate decoder](https://ssl.sh/tools/certificate-decoder/) does the same detection in the browser for public certificates.

## Converting between formats

Most conversions are one OpenSSL command, and the private key never needs to leave your machine. A quick reference:

| From | To | Command |
|---|---|---|
| DER | PEM | `openssl x509 -inform der -in cert.der -out cert.pem` |
| PEM | DER | `openssl x509 -in cert.pem -outform der -out cert.der` |
| P7B | PEM | `openssl pkcs7 -in chain.p7b -print_certs -out chain.pem` |
| PEM | P7B | `openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b` |
| PEM + key | PFX | `openssl pkcs12 -export -inkey key.pem -in fullchain.pem -out site.pfx` |
| PFX | PEM | `openssl pkcs12 -in site.pfx -out all.pem -noenc` |
| JKS | PKCS12 | `keytool -importkeystore -srckeystore a.jks -destkeystore a.p12 -deststoretype pkcs12` |

The [format conversion cookbook](https://ssl.sh/cookbook/convert-formats/) adds output examples and edge cases, and the [SSL converter](https://ssl.sh/tools/ssl-converter/) handles common conversions in the browser. After any conversion, confirm that the key still matches the certificate with the [key matcher](https://ssl.sh/tools/key-matcher/); a mismatch is what Nginx reports as [key values mismatch](https://ssl.sh/errors/nginx-key-values-mismatch/).

## What each server expects

Choose the format by the software that will load it. The table lists common targets.

| Platform | Certificate | Private key | Chain |
|---|---|---|---|
| Nginx | PEM (`ssl_certificate`) | PEM (`ssl_certificate_key`) | In the certificate file, after the leaf |
| Apache 2.4.8+ | PEM (`SSLCertificateFile`) | PEM (`SSLCertificateKeyFile`) | In the certificate file; `SSLCertificateChainFile` is deprecated |
| HAProxy | One PEM file | Same file | Same file |
| IIS / Windows | PKCS#12 (.pfx) import | Inside the PFX | Inside the PFX |
| Tomcat | PKCS#12 or JKS keystore; recent versions also accept PEM files | In the keystore or PEM | In the keystore or PEM |
| Node.js | PEM strings (`cert`, `key`) or a PFX (`pfx`) | PEM or PFX | Appended to `cert` |
| Cloud load balancers | Usually PEM body, chain and key pasted separately | PEM | Separate field |

When a CA or hosting panel lets you download a ZIP "for Nginx", "for IIS" or "for Tomcat", it is giving you the same certificate in these different containers. Whatever you download, the chain must be complete: see [certificate chains](https://ssl.sh/learn/certificate-chains/) for why the intermediates matter.
