How to Create a Self-Signed Certificate with OpenSSL
For local development, internal services, and testing TLS, you don't need a certificate authority — you need a self-signed certificate: one your own key signs for itself. OpenSSL generates one in a single command, but modern browsers reject certificates without a Subject Alternative Name, so the naive command from old tutorials no longer works.
This guide gives you the command that actually works in 2026, explains each flag, and covers when a self-signed cert is the right tool.
What "Self-Signed" Means
A normal certificate is signed by a Certificate Authority that your system already trusts. A self-signed certificate is signed by its own private key — there's no CA vouching for it. That's why browsers show a warning: they can't verify the identity through a trusted chain. For local/internal use where you control both ends, that's fine. For anything public-facing, use a real CA (Let's Encrypt is free) — see What Is a CSR File? for the CA-signed path.
The One-Command Version
This generates a 2048-bit RSA key and a self-signed certificate valid for 365 days, including the SAN that modern browsers require:
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout key.pem -out cert.pem -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Flag by flag:
req -x509— create a self-signed certificate (not a CSR).-newkey rsa:2048— generate a fresh 2048-bit RSA key at the same time.-nodes— "no DES": don't encrypt the private key with a passphrase (so a dev server can start unattended).-keyout key.pem/-out cert.pem— where to write the key and cert.-days 365— validity period.-subj "/CN=localhost"— the subject, non-interactively (skips the prompts).-addext "subjectAltName=..."— the critical part. Lists the names/IPs the cert is valid for.
Why the SAN Matters
Since Chrome 58 (2017), browsers ignore the Common Name entirely and validate the hostname only against the Subject Alternative Name field. A certificate with CN=localhost but no SAN produces NET::ERR_CERT_COMMON_NAME_INVALID even though the CN looks right. The -addext "subjectAltName=..." is what makes the cert actually usable.
List every name and IP you'll access the service by:
subjectAltName=DNS:localhost,DNS:dev.local,IP:127.0.0.1,IP:192.168.1.50
Generating Without OpenSSL
If you'd rather not wrangle OpenSSL flags, the Self-Signed Cert Generator builds the key and certificate in your browser — fill in the subject, validity, key size, and SANs in a form, and download the PEM files. The key is generated locally and never sent anywhere. The CSR Generator covers the other path when you need a CA-signed cert instead.
ECDSA Instead of RSA
For a smaller, faster modern key, use an elliptic-curve key instead of RSA:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout key.pem -out cert.pem -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
prime256v1 (P-256) is the widely supported default. ECDSA certs are smaller and the handshake is faster — fine for dev and increasingly common in production.
Verify What You Made
Confirm the certificate has the fields you expect — especially that the SAN is present:
- Paste
cert.peminto the SSL Certificate Decoder to see the subject, validity dates, and the Subject Alternative Names list. If the SAN is missing, browsers will reject it — regenerate with-addext. - Confirm the key and cert pair correctly with the Certificate Key Matcher before wiring them into a server.
You can also check from the command line:
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name"
Trusting It Locally (Optional)
To stop the browser warning on your own machine, add the certificate to your OS trust store:
- macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem - Linux (Debian/Ubuntu): copy to
/usr/local/share/ca-certificates/and runsudo update-ca-certificates. - Windows: import into "Trusted Root Certification Authorities" via
certmgr.msc.
Only do this for certs you generated yourself on machines you control.
When Not to Use a Self-Signed Cert
- Anything public-facing — visitors get a scary warning and many won't proceed. Use Let's Encrypt.
- Mobile apps / strict clients — many refuse self-signed certs outright with no override.
- Production internal services at scale — run a small internal CA instead, so you trust one root rather than installing dozens of individual certs.
Quick Reference
- One command:
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1". - The SAN is mandatory — browsers ignore CN; without
-addext subjectAltNamethe cert is rejected. -nodesleaves the key unencrypted for unattended dev servers; secure the file withchmod 600.- No-OpenSSL option: the Self-Signed Cert Generator; verify output with the SSL Decoder.
- Self-signed is for local/internal use only — use a real CA for anything public.