Self-Signed SSL Certificates: When to Use Them and How to Create Them

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:

  • -x509 tells OpenSSL to output a self-signed certificate rather than a CSR
  • -newkey rsa:4096 generates a new 4096-bit RSA private key
  • -nodes outputs the key without encryption (no passphrase required to use it)
  • -days 365 sets validity to 365 days
  • -subj fills 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.

Scroll to Top