Self signed certificates: when they are fine and when they are not
A self signed certificate is one where the subject and the issuer are the same entity: you generated a key pair and signed the certificate with your own private key instead of sending a request to a certificate authority. Cryptographically it is identical to any other certificate, and the encryption it provides is exactly as strong. What it lacks is the one thing a CA sells, an existing trust relationship with the machine on the other end.
That single gap explains every self signed certificate story, both the ones where it works fine and the ones where it wastes a day.
Why does the browser refuse it?
Certificate validation asks two separate questions: does this certificate cryptographically chain up to a root the client already trusts, and does it match the hostname being visited. A self signed certificate chains to itself, and nothing in the operating system or browser trust store vouches for it, so validation fails at the first question with NET::ERR_CERT_AUTHORITY_INVALID or the equivalent. The connection would still be encrypted; the browser cannot tell whether it is encrypted to your server or to whoever is between you, which is precisely what a certificate exists to answer. That failure mode is covered in detail in fix NET::ERR_CERT_AUTHORITY_INVALID.
Two consequences worth being explicit about:
- Clicking through the warning is not the same as being secure. It disables the authentication guarantee and keeps only the encryption, which is a meaningful downgrade on any untrusted network.
- Non browser clients often refuse outright rather than warn.
curlfails with certificate verification errors, most HTTP libraries throw, and getting past it means--insecureorverify=False, flags that then tend to survive into production code where they are genuinely dangerous.
Generating one correctly
Most self signed certificates fail for a second, avoidable reason: no subject alternative name. Modern clients ignore the common name entirely and match the hostname only against the SAN extension. A certificate without one is rejected even after you trust it.
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout server.key -out server.crt -days 365 \
-subj "/CN=dev.internal" \
-addext "subjectAltName=DNS:dev.internal,DNS:*.dev.internal,IP:127.0.0.1"
The pieces: -x509 means self signed rather than a signing request, -nodes leaves the private key unencrypted so a service can start without a passphrase, and -addext supplies the SAN list, which is the line people omit. Include every name and address you will actually use, including localhost and 127.0.0.1 if you connect that way.
To confirm the result before deploying it, paste the certificate into the SSL Decoder: it shows the SAN list, validity dates, key size, and signature algorithm as a client would read them. If you already have a key and want to check they belong together, the key matcher compares the public key in the certificate against the private key. The reasoning behind each field is in how to read an SSL certificate.
When is self signed the right call?
- Local development. You need HTTPS for service workers, secure cookies, or clipboard APIs, and no external party is involved. A trusted local CA (
mkcertand similar) is the more comfortable version of this. - Between services you control. Two backends on a private network with the certificate pinned or the CA explicitly trusted at both ends. This is mutual TLS territory, and here the public CA system adds nothing.
- A device or appliance with no name. Hardware management interfaces on private addresses cannot get a public certificate, because no CA will validate a name that does not resolve publicly.
- A short lived internal tool where distributing a root to a handful of machines is cheaper than provisioning a public certificate.
When is it the wrong call?
Anything the public reaches. A self signed certificate on a public site trains users to click through warnings, breaks API clients, blocks mobile apps whose platforms forbid ignoring validation errors, and gives you none of the transparency that public certificates carry in CT logs.
The usual objection, that certificates cost money, has not been true since ACME made public certificates free and automatic. If a name resolves publicly and points at your server, getting a trusted certificate is a one command operation with automatic renewal. Self signed on a public host in 2026 is a configuration choice, not a budget constraint.
The better option for internal services: a private CA
Trusting one self signed certificate per host does not scale. Every new service means another manual trust decision on every machine, and rotating a certificate means repeating it. A private CA inverts that: you distribute one root certificate to your machines, then issue as many leaf certificates from it as you like, and each one validates automatically.
# 1. Create the root, once
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
-keyout ca.key -out ca.crt -subj "/CN=Example Internal Root CA"
# 2. Per service: key and CSR
openssl req -newkey rsa:2048 -nodes \
-keyout service.key -out service.csr -subj "/CN=service.internal" \
-addext "subjectAltName=DNS:service.internal"
# 3. Sign it with the root
openssl x509 -req -in service.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out service.crt -days 365 -copy_extensions copyall
-copy_extensions copyall is required, otherwise the SAN in the request is silently dropped and you get an unusable certificate for the same reason as before. The CSR generator produces correctly formed requests with SANs already in place, and the CA matcher confirms which certificate signed which.
Distribute ca.crt to the system trust store on each machine (/usr/local/share/ca-certificates/ plus update-ca-certificates on Debian, Keychain Access on macOS), and keep ca.key off shared machines: anyone holding it can mint a certificate for any name your systems will trust.
Renewal is still your problem
Self signed and privately signed certificates expire like any other, and nothing reminds you. Expiry is the single most common cause of internal outages that look like network faults. Track your internal hosts with the bulk SSL checker and set the alert before the expiry, not after, as described in monitor SSL certificate expiry.