Self-signed certificates get a bad reputation because browsers show scary warning pages when they encounter them. But that reputation comes from misuse — using self-signed certs where a trusted certificate is needed. In the right context, a self-signed certificate is the correct tool.
Here’s where I actually use them: internal APIs that communicate server-to-server, development environments on localhost, internal admin interfaces on private networks, and testing new server configurations before adding real certificates.
What Makes a Certificate “Self-Signed”
A regular certificate is signed by a Certificate Authority — a trusted third party vouched for you and used their private key to sign your cert. Browsers trust the CA, so they trust your cert by extension.
A self-signed certificate has no CA. You generate a key pair and use your own private key to sign your own certificate. The signature is cryptographically valid — the math is the same — but there’s no chain of trust to a recognized authority.
This means:
- Encryption is just as strong as a CA-signed cert
- The handshake and TLS protocol are identical
- There’s no independent verification that you are who you claim to be
For encryption alone (protecting data in transit between two systems you control), self-signed certificates work perfectly.
Creating a Basic Self-Signed Certificate with OpenSSL
The simplest approach — generates a certificate valid for one year:
openssl req -x509 -newkey rsa:4096 -keyout private.key -out certificate.crt \
-sha256 -days 365 -nodes \
-subj "/CN=example.com/O=My Organization/C=US"
Breaking down the flags:
-x509tells OpenSSL to output a self-signed certificate rather than a CSR-newkey rsa:4096generates a new 4096-bit RSA private key-nodesoutputs the key without encryption (no passphrase required to use it)-days 365sets validity to 365 days-subjfills in the certificate fields without interactive prompts
The resulting certificate.crt and private.key files work just like any other PEM certificate and key.
Creating a Certificate with Subject Alternative Names
Modern browsers and many clients require the domain name to appear in the SAN field, not just the CN (Common Name). If you create a self-signed cert with only a CN and try to use it with Chrome, you’ll get a NET::ERR_CERT_COMMON_NAME_INVALID error.
Create an OpenSSL config file first:
# /tmp/openssl.cnf
[req]
default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn x509_extensions = v3_req
[dn]
CN = example.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com DNS.2 = www.example.com DNS.3 = *.example.com IP.1 = 192.168.1.100
Generate the certificate:
openssl req -x509 -newkey rsa:2048 -keyout private.key -out certificate.crt \
-sha256 -days 365 -nodes \
-config /tmp/openssl.cnf
You can include IP addresses (for certificates used with IP-based access), multiple DNS names, and wildcard patterns — just like a CA-signed certificate.
Localhost Certificate for Development
Development on localhost is a common use case. Having HTTPS in development avoids behavior differences between dev (HTTP) and production (HTTPS), and some browser APIs require HTTPS even on localhost.
# /tmp/localhost.cnf
[req]
default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn x509_extensions = v3_req
[dn]
CN = localhost
[v3_req]
keyUsage = keyEncipherment, dataEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost DNS.2 = *.localhost IP.1 = 127.0.0.1 IP.2 = ::1
openssl req -x509 -newkey rsa:2048 -keyout localhost.key -out localhost.crt \
-sha256 -days 825 -nodes \
-config /tmp/localhost.cnf
Note: 825 days is the max validity modern browsers accept (a Chrome restriction). Longer than that and Chrome will reject the certificate.
Creating a Local Certificate Authority
For teams or environments where multiple self-signed certs need to be trusted, creating a local CA makes sense. Instead of trusting each cert individually, you trust the CA once and it signs all the certs.
Step 1: Create the CA key and certificate:
# Generate CA private key (keep this extremely secure)
openssl genrsa -out ca.key 4096
# Generate CA certificate (10 year validity is typical for a CA cert)
openssl req -new -x509 -key ca.key -out ca.crt -days 3650 \
-subj "/CN=My Local CA/O=My Organization/C=US"
Step 2: Create and sign a server certificate:
# Generate server key
openssl genrsa -out server.key 2048
# Create a CSR
openssl req -new -key server.key -out server.csr \
-subj "/CN=internal.example.com"
# Create extensions file
cat > /tmp/server_ext.cnf << EOF
subjectAltName=DNS:internal.example.com,DNS:*.internal.example.com
EOF
# Sign the CSR with your CA
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 365 \
-extfile /tmp/server_ext.cnf -sha256
Step 3: Distribute the CA certificate:
On Linux (Debian/Ubuntu):
sudo cp ca.crt /usr/local/share/ca-certificates/my-local-ca.crt
sudo update-ca-certificates
On RHEL/CentOS/AlmaLinux:
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/my-local-ca.crt
sudo update-ca-trust extract
On macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt
On Windows (PowerShell):
Import-Certificate -FilePath "ca.crt" -CertStoreLocation "Cert:\LocalMachine\Root"
Once the CA certificate is trusted, any certificate signed by that CA will be trusted too — no more browser warnings.
mkcert: The Easy Way for Development
If manually managing OpenSSL commands isn’t your thing, mkcert is a tool designed specifically for creating locally-trusted development certificates. It creates a local CA, installs it in your system trust store, and generates certificates signed by that CA.
# Install (on macOS with Homebrew)
brew install mkcert
# Install the local CA in system trust stores
mkcert -install
# Create a certificate for localhost and local IP
mkcert localhost 127.0.0.1 ::1
# Create a certificate for a local domain
mkcert example.local "*.example.local"
mkcert handles the system trust store installation across macOS, Linux, and Windows, including Firefox’s certificate store (which is separate from the OS store). It’s what I use for development environments.
Self-Signed Certs in Production: The Risks
Using self-signed certificates on public-facing services creates problems:
- Browsers show warnings that damage user trust and cause people to leave
- Automated clients (monitoring, APIs, mobile apps) reject them or require insecure workarounds
- Compliance frameworks (PCI-DSS, HIPAA) typically require CA-signed certificates
- Certificate pinning setups become complicated
For server-to-server communication on internal networks where you control both ends, the risks are minimal and the operational simplicity is a genuine benefit. For anything a user’s browser touches, use a CA-signed certificate — Let’s Encrypt makes this free and easy.
The line I draw: if a human browser ever connects to it, use a real cert. If it’s strictly machine-to-machine, self-signed with a proper internal CA is fine.