Self-signed certificates: what they are, when to use them, and how to create one
A self-signed certificate is a TLS certificate signed by its own private key instead of by a trusted certificate authority. It still encrypts the connection exactly like any other certificate, but because nothing vouches for the identity behind it, browsers and most clients refuse to trust it by default. This guide explains what a self-signed certificate actually is, when it's the right tool, the risks of leaning on one, and exactly how to create, inspect, and trust one with OpenSSL.
What a self-signed certificate is
Every X.509 certificate has an issuer (who signed it) and a subject (who it identifies). In a normal certificate issued by Let's Encrypt or DigiCert, the issuer is the CA and the subject is your domain — two different entities. The CA uses its own private key to sign your certificate, and because your operating system and browser already trust that CA's root, the signature chains up to something trusted.
In a self-signed certificate, the issuer and the subject are the same entity. The certificate is signed by the very key it certifies. There is no third party in the loop and no chain to follow: the certificate points at itself. You can spot one immediately because its issuer and subject fields are identical.
openssl x509 -in cert.pem -noout -issuer -subject
issuer=C=US, O=Example Dev, CN=localhost
subject=C=US, O=Example Dev, CN=localhost
When issuer and subject match like that, no external authority has validated anything. The certificate is cryptographically valid — the signature checks out against its own public key — but it carries no independent proof that the holder is who it claims to be. That distinction is the whole story. For a refresher on how trusted certificates link leaf to intermediate to root, see the certificate chain explained.
Why browsers and clients reject it
A TLS client trusts a server certificate only if it can build a path from that certificate up to a root the client already trusts. A self-signed certificate has no such path — it terminates at itself, and your machine has never heard of it. So the verification fails and the connection is refused.
In a browser, this shows up as a full-page interstitial. Chrome and other Chromium browsers report NET::ERR_CERT_AUTHORITY_INVALID, which means the chain couldn't be anchored to a trusted CA. The browser cannot tell the difference between a harmless local dev cert and an attacker injecting a fake one, so it treats both as untrusted.
On the command line and in server-to-server calls, the same condition surfaces differently. OpenSSL and Node frequently report self signed certificate or self signed certificate in chain, and a partial chain can also produce unable to verify the first certificate. You can reproduce the verification failure directly:
openssl s_client -connect localhost:443 -servername localhost </dev/null 2>&1 | grep -i verify
verify error:num=18:self signed certificate
verify return code: 18 (self signed certificate)
Error 18 is OpenSSL's code for exactly this case: a self-signed certificate that is not in the trust store. None of these errors mean the certificate is broken or that encryption isn't happening — they mean the client correctly refuses to assume identity it cannot verify.
When a self-signed certificate is the right choice
Self-signed certificates have legitimate, everyday uses. They're a problem only when you point them at the public internet and ask strangers to trust them.
- Local development. Running
https://localhostto match production behavior, test secure cookies, or exercise HTTP/2 doesn't need a publicly trusted cert. The only client is you. - Internal-only services. A service that's reachable solely inside a private network or VPC — an admin dashboard, a metrics endpoint, a database proxy — can use a certificate you control without involving a public CA.
- Automated tests and CI. Spinning up a TLS server in a test suite is faster and hermetic with a self-signed cert generated on the fly; there's no external dependency to flake.
- Bootstrapping an internal CA. The root of any private PKI is, by definition, self-signed. You generate a self-signed root, mark it trusted on your fleet, and use it to issue leaf certs for internal hosts.
- Mutual TLS between known peers. When both sides are systems you operate and you pin or pre-distribute the certificates, self-signed certs (or certs from your own CA) are a common, deliberate setup. Trust comes from the pinning, not from a public CA.
The common thread: every client that connects already knows, out of band, which certificate to expect. That's what replaces the missing CA.
The risks of trusting one
The flip side of "no CA involved" is "no CA validation." A self-signed certificate proves only that whoever holds the matching private key signed it — it makes no claim that they own the domain or are who they say. That has real consequences.
- No third-party identity check. A public CA performs domain validation before issuing. A self-signed cert skips that entirely, so the certificate vouches for nothing beyond itself.
- Blind trust invites man-in-the-middle attacks. The dangerous habit is clicking through the warning every time. If you train yourself (or your code, via
rejectUnauthorized: falseorcurl -k) to ignore certificate errors, you lose the ability to detect an actual interception. Someone on the path can present their self-signed cert and you'll accept it just the same. - Trust is hard to revoke and rotate at scale. A public cert can be revoked via CRL/OCSP and is short-lived by design. A self-signed cert you've installed into dozens of machines' trust stores has no revocation infrastructure — pulling it back means touching every host. Multiply that across a fleet and "just trust this cert" becomes an operational liability.
These are exactly the reasons self-signed certs belong in development and private networks, not in front of real users.
How to create a self-signed certificate with OpenSSL
The modern one-liner generates a key and a self-signed certificate together. The flags matter, so here's each one with its job.
openssl req -x509 -newkey rsa:2048 \
-keyout key.pem -out cert.pem \
-days 365 -nodes \
-subj "/C=US/O=Example Dev/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
req -x509produces a self-signed certificate directly instead of a certificate signing request.-newkey rsa:2048generates a fresh 2048-bit RSA key in the same step. Use-newkey ec -pkeyopt ec_paramgen_curve:prime256v1if you prefer an ECDSA key.-keyout/-outwrite the private key and the certificate to separate files.-days 365sets the validity window. Keep dev certs short.-nodes("no DES") leaves the private key unencrypted so your server can load it without a passphrase prompt.-subjfills in the distinguished name non-interactively, so the command doesn't stop to ask questions.-addext "subjectAltName=..."is the line people forget. Modern clients ignore the Common Name and validate against the SAN. Without a SAN, even a manually trusted cert will fail with a name-mismatch error likeERR_TLS_CERT_ALTNAME_INVALID.
Inspect what you produced to confirm the SAN landed and the issuer equals the subject:
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name"
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
For the full set of inspection flags — dates, fingerprints, key details — see check an SSL certificate with OpenSSL.
A small local CA beats a bare self-signed cert
A single self-signed leaf cert is awkward to live with: every new hostname means a brand-new cert you have to trust again on every machine. A better pattern for local development is a tiny local CA. You trust the CA root once, then issue as many leaf certs as you like — each one chains to that root and is accepted without further fuss.
The easiest path is mkcert, which automates the whole thing:
mkcert -install # create a local CA and add it to your trust stores
mkcert localhost 127.0.0.1 ::1 # issue a leaf cert for these names
mkcert -install creates a development CA and installs it into the system and browser trust stores. The second command writes a leaf certificate and key signed by that CA, valid for the names you list. Because the root is trusted, browsers show a normal padlock with no warning, and you never click through an interstitial again. For real internal infrastructure you'd run a longer-lived private CA (with openssl ca or a tool like step-ca), but for laptops, mkcert is the right amount of effort.
How to trust a self-signed certificate
If you do use a bare self-signed cert, you tell each client to trust it by adding it to the relevant trust store. The mechanism differs per platform.
macOS (Keychain): import the certificate and mark it trusted.
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain cert.pem
Windows (certificate store): add it to the machine's Trusted Root authorities from an elevated PowerShell.
Import-Certificate -FilePath cert.pem `
-CertStoreLocation Cert:\LocalMachine\Root
Linux (Debian/Ubuntu): drop the cert into the CA directory and rebuild the bundle.
sudo cp cert.pem /usr/local/share/ca-certificates/my-local.crt
sudo update-ca-certificates
On RHEL/Fedora the equivalent is cp into /etc/pki/ca-trust/source/anchors/ followed by update-ca-trust.
Node.js: Node ships its own CA bundle and ignores the OS store. Point it at your cert with an environment variable instead of disabling verification.
NODE_EXTRA_CA_CERTS=/path/to/cert.pem node server.js
NODE_EXTRA_CA_CERTS adds your certificate to Node's trusted set without weakening anything else — the right move over NODE_TLS_REJECT_UNAUTHORIZED=0, which turns verification off entirely and silently exposes every connection the process makes.
Why not to use one in production
There's no reason to serve a self-signed certificate to the public anymore. The cost that historically justified it — buying a CA-signed cert — is gone. Let's Encrypt issues publicly trusted certificates for free, fully automated, and most platforms renew them for you without intervention.
A self-signed cert on a public site means every visitor hits a scary full-page warning, your API clients fail their handshakes, and you've trained everyone to ignore exactly the signal that protects them. Switching to an automated, trusted certificate removes the warning, restores real identity validation, and gives you painless renewals. If you're moving off a self-signed setup, the mechanics of issuing and rotating a trusted cert are covered in how to renew an SSL certificate.
Self-signed certificates still expire
It's tempting to assume that because you control a self-signed or internal cert, it isn't going to surprise you. It will. A self-signed cert has a notAfter date like any other, and the day it passes, every client that trusted it starts rejecting connections — same as a public cert, but without a CA emailing you a renewal reminder. Internal CAs are especially prone to this because the root and the leaves expire on their own quiet schedules and nobody owns the calendar.
Private and self-signed certificates need monitoring just as much as public ones — arguably more, since the expiry is invisible from the outside. The strategies for watching certs that aren't reachable from the public internet are laid out in how to monitor internal and private SSL certificates.
Monitor it automatically
SSLNudge watches your certificates on a daily schedule and alerts you well before they expire — including the self-signed and internal-CA certs that no public authority will ever warn you about. Add your hosts once and let the daily check track every expiry date, public or private, so a forgotten internal cert becomes a notice instead of an outage. Sign in to start monitoring.
Stop tracking expiry dates by hand
SSLNudge checks your certificates daily and alerts you before they expire.