Help us fix this page
If you found a broken link, missing page, or incorrect redirect, please let us know. Your report helps us improve the website for everyone.

A self‑signed SSL/TLS certificate is a certificate that is signed by its own private key rather than by a trusted Certificate Authority (CA). It serves a valuable purpose in development, testing, and internal environments where the cost and administrative overhead of a publicly trusted certificate are not justified.
When a service is ready to ship in development, the reverse proxy is listening on HTTPS, and the browser still throws a warning. API clients complain. Secure cookies do not behave the way they should. Internal callbacks fail because the certificate does not validate. That is the point where many developers search for how to create a self‑signed certificate with OpenSSL, copy a one‑liner, and then discover that generating a certificate and generating a certificate that clients trust are two different jobs.
NET::ERR_CERT_COMMON_NAME_INVALID error regardless of the Common Name field.chmod 600) and should never be committed to version control. Use RSA 4096 or ECDSA for cryptographic strength.This guide is for developers, system administrators, and DevOps engineers who need to set up HTTPS for local development, internal services, containerised workloads, or test environments. By the end of this guide, you will be able to generate self‑signed certificates with modern extensions, configure them on major web servers and development tools, and apply security best practices to keep your TLS setup robust.
SSL (Secure Sockets Layer) is a cryptographic protocol developed by Netscape in the mid‑1990s to provide secure communication over the internet. SSL 1.0 was never publicly released. SSL 2.0 (1995) had serious flaws and was quickly replaced. SSL 3.0 (1996) remained in use for many years but was officially deprecated in 2015 due to the POODLE attack.
TLS (Transport Layer Security) is the successor to SSL. TLS 1.0 (1999) was essentially SSL 3.1. TLS 1.1 (2006) addressed several vulnerabilities. TLS 1.2 (2008) is still widely used today. TLS 1.3 (2018) is the current standard, removing obsolete cryptographic primitives and reducing handshake latency.
The term “SSL” has become a genericised trademark. Most people say “SSL certificate” even when they mean TLS. In practice, all modern certificates are used with TLS, and the term is understood to mean the same thing.
HTTPS is HTTP over TLS. The connection establishment follows these steps:

TLS handshake diagram:

The server sends its certificate containing its public key. The client verifies the certificate against its trust store. If the certificate is self‑signed, the client has no trust anchor and shows a warning.
| Term | Explanation |
|---|---|
| Certificate | An X.509 document binding a public key to an identity (domain name, organisation). |
| Public key | The key used to encrypt data or verify signatures; it is safe to distribute. |
| Private key | The key used to decrypt data or create signatures; it must be kept secret. |
| CSR (Certificate Signing Request) | A formatted request sent to a CA to obtain a signed certificate. |
| CA (Certificate Authority) | A trusted entity that issues and signs certificates. |
| Root certificate | The self‑signed certificate at the top of a trust chain. |
| Intermediate certificate | A certificate signed by a root CA, used to sign end‑entity certificates. |
| SAN (Subject Alternative Name) | An X.509 extension that allows a certificate to be associated with multiple DNS names, IP addresses, or other identifiers. |
| CN (Common Name) | A deprecated field that historically identified the certificate’s primary domain. Modern browsers require SAN. |
| Wildcard certificate | A certificate matching *.example.com, valid for any subdomain. |
| EV certificate | Extended Validation certificate, requiring extensive validation, displayed with a green bar in some browsers. |
| DV certificate | Domain‑Validated certificate, only confirming domain control. |
| OV certificate | Organisation‑Validated certificate, confirming both domain control and organisation existence. |
| PEM | Privacy‑Enhanced Mail, a base64‑encoded ASCII format with -----BEGIN ...----- headers. |
| CRT / CER | Certificate files; can be PEM or DER. |
| DER | Binary format containing only the certificate. |
| PFX / PKCS#12 | A password‑protected binary container for certificates and private keys. |
| PKCS#8 | A standard for encoding private keys, often used with PEM. |
| JKS | Java KeyStore, a format used by Java applications. |
| Type | Browser Trust | Cost | Renewal | Validation | Security | Typical Usage |
|---|---|---|---|---|---|---|
| Self‑signed | no (warning) | Free | Manual | None | Full encryption | Development, testing, internal services |
| Let’s Encrypt | yes | Free | 90 days (auto) | Domain control | Full | Production, personal sites |
| Commercial DV | yes | €10–€50/yr | 1–2 years | Domain control | Full | Production, small business |
| OV | yes | €50–€200/yr | 1–2 years | Domain + organisation | Full | Business websites |
| EV | yes | €200–€500+/yr | 1–2 years | Extended validation | Full | Financial, e‑commerce |
| Internal enterprise CA | yes (internally) | Internal cost | Custom | Internal policy | Full | Internal corporate PKI |
Self‑signed certificates are appropriate in these scenarios:
For a solo laptop workflow, a direct self‑signed leaf certificate is fine. For shared development or staging where several people and systems need the same internal trust model, a local CA is the right answer.
Never use a self‑signed certificate in production environments that serve external users. The browser warnings erode user trust and can lead to MITM attacks. Examples include:
Production‑grade services must use certificates from a publicly trusted CA such as Let’s Encrypt, DigiCert, or Sectigo.
| Method | Description |
|---|---|
| Official OpenSSL installer | OpenSSL 4.0.1 or 3.5.7 LTS installers available |
| Chocolatey | choco install openssl |
| Scoop | scoop install openssl |
| Git Bash | Includes OpenSSL by default |
| WSL | Run Linux OpenSSL from Windows |
OpenSSL is available via the package manager on all major distributions.
Ubuntu / Debian (OpenSSL 3.5.5 on Ubuntu 26.04):
sudo apt update
sudo apt install opensslFedora / RHEL / Rocky / AlmaLinux:
sudo dnf install opensslArch Linux:
sudo pacman -S opensslVerify installation:
openssl versionMethod 1: Official installer (recommended)
The OpenSSL Project now ships its own Windows installers-MSI and plain executable, with a lightweight option for command‑line tools only. Download from the official OpenSSL website or FireDaemon.
Method 2: Chocolatey
choco install opensslMethod 3: Scoop
scoop install opensslAfter installation, add the OpenSSL bin directory to your system PATH and verify:
openssl version# Ubuntu / Debian
sudo apt update && sudo apt install openssl
# Fedora / RHEL / Rocky / AlmaLinux
sudo dnf install openssl
# Arch Linux
sudo pacman -S openssl| Format | Extension | Content | Readable |
|---|---|---|---|
| PEM | .pem, .crt, .cer, .key | Base64‑encoded, ASCII | Yes |
| DER | .der, .cer | Binary | No |
| PKCS#12 | .pfx, .p12 | Binary container (cert + key) | No |
| JKS | .jks | Java KeyStore | No |
Conversion examples:
Convert DER to PEM:
openssl x509 -inform der -in certificate.cer -out certificate.pemConvert PKCS#12 to PEM:
openssl pkcs12 -in certkey.pfx -out certkey.pem -nodesConvert PEM certificate + key to PKCS#12:
openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfxopenssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodesWarning: This command does not include Subject Alternative Names (SAN), which are required by modern browsers. The certificate will generate a NET::ERR_CERT_COMMON_NAME_INVALID error.
Step 1: Create an OpenSSL configuration file (san.cnf) :
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[dn]
C = SK
ST = Bratislavský kraj
L = Bratislava
O = My Company
OU = Engineering
CN = localhost
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
DNS.3 = myapp.local
IP.1 = 127.0.0.1
IP.2 = ::1Step 2: Generate the certificate
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -sha256 -config san.cnf -extensions req_extopenssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -sha256 -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1"Note: The -addext flag requires OpenSSL 1.1.1 or later.
| Algorithm | Key size | Performance | Compatibility |
|---|---|---|---|
| RSA 2048 | 2048 bits | Fast | Universal |
| RSA 4096 | 4096 bits | Slower | Universal |
| ECDSA P‑256 | 256 bits | Very fast | Modern browsers |
| ECDSA P‑384 | 384 bits | Fast | Modern browsers |
Recommendation: Use RSA 4096 for general‑purpose certificates. Use ECDSA for high‑performance environments.
Historically, the Common Name (CN) field was used to identify the domain. Modern browsers (since Chrome 58, Firefox 48) ignore the CN field for domain validation and require SAN. A certificate without SAN will fail validation.
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
DNS.4 = *.example.com
IP.1 = 192.168.1.10
IP.2 = 10.0.0.5For local development, always include:
DNS.1 = localhostDNS.2 = *.localhost (if using .localhost domains)IP.1 = 127.0.0.1IP.2 = ::1.crt fileDebian / Ubuntu:
sudo cp server.crt /usr/local/share/ca-certificates/
sudo update-ca-certificatesFedora / RHEL / CentOS:
sudo cp server.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trustsudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain server.crtFirefox uses its own certificate store. Import via:
Enable SSL module:
sudo a2enmod ssl
sudo a2enmod headers # for HSTS
sudo systemctl restart apache2<VirtualHost *:443>
ServerName localhost
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
# Modern TLS settings
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache shmcb:/var/run/ocsp(128000)
</VirtualHost><VirtualHost *:80>
ServerName localhost
Redirect permanent / https://localhost/
</VirtualHost>sudo a2enmod http2Add to VirtualHost:
Protocols h2 http/1.1server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name localhost;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
root /var/www/html;
index index.html;
}server {
listen 80;
listen [::]:80;
server_name localhost;
return 301 https://$server_name$request_uri;
}Nginx supports HTTP/3 with the QUIC module. Add:
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';Caddy automatically obtains and renews certificates from Let’s Encrypt. For self‑signed certificates in development:
localhost {
tls internal
root * /var/www/html
file_server
}The tls internal directive tells Caddy to use its internal CA for local certificates.
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello HTTPS!');
}).listen(443);const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
};
https.createServer(options, app).listen(443);Laravel Valet and Herd automatically serve sites over HTTPS using locally trusted certificates. For custom setups:
php artisan serve --port=443 --host=localhost --ssl-key=server.key --ssl-cert=server.crtsymfony server:start --port=443 --ssl-key=server.key --ssl-cert=server.crtphp -S localhost:443 -t . --ssl-key=server.key --ssl-cert=server.crtFROM alpine:latest
RUN apk add --no-cache openssl
RUN openssl req -x509 -newkey rsa:4096 -keyout /certs/server.key -out /certs/server.crt -days 365 -nodes -subj "/CN=localhost"version: '3'
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/ssl/certs
ports:
- "443:443"Traefik can generate self‑signed certificates for local development using its resolvers configuration.
kubectl create secret tls tls-secret --key server.key --cert server.crtyaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
tls:
- hosts:
- example.local
secretName: tls-secret
rules:
- host: example.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80cert‑manager can use a self‑signed Issuer for local development:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}| Tool | Advantages | Disadvantages | Recommended Usage |
|---|---|---|---|
| mkcert | Zero config, local CA, browser trust | Requires CA installation | Default for local dev |
| OpenSSL | Universal, fine‑grained control | Complex, manual trust management | Custom certificate needs |
| step‑ca | Full PKI, ACME support | Heavier setup | Internal CA, team use |
| devcert | Node.js integration | Limited to Node | JavaScript projects |
mkcert quick start:
# Install mkcert
brew install mkcert # macOS
choco install mkcert # Windows
sudo apt install mkcert # Linux
# Install local CA
mkcert -install
# Generate certificate
mkcert localhost 127.0.0.1 ::1 *.localhost myapp.localmkcert creates locally‑trusted certificates without browser warnings.
# View certificate details
openssl x509 -in server.crt -text -noout
# Verify certificate against CA
openssl verify -CAfile ca.crt server.crt
# Check SSL connection
openssl s_client -connect localhost:443 -servername localhostcurl -v https://localhost --cacert server.crt./testssl.sh https://localhosttestssl.sh checks TLS/SSL ciphers, protocols, and cryptographic flaws.
Visit https://www.ssllabs.com/ssltest/ to analyse a publicly accessible server.
chmod 600 server.key-sha256 or -sha384TLSv1.2
TLSv1.3For TLS 1.3 (OpenSSL names):
TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256For TLS 1.2 (OpenSSL names):
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384| Factor | Impact |
|---|---|
| RSA vs ECC | ECC is faster, especially on mobile devices |
| TLS session reuse | Reduces handshake overhead |
| TLS 1.3 | One‑round‑trip handshake, faster than TLS 1.2 |
| HTTP/2 | Multiplexing, header compression |
| HTTP/3 (QUIC) | Even faster, uses UDP |
| Certificate size | RSA 4096 certificates are larger, slower |
| Error | Cause | Solution |
|---|---|---|
NET::ERR_CERT_AUTHORITY_INVALID | Self‑signed, not trusted | Install CA or use mkcert |
ERR_CERT_COMMON_NAME_INVALID | Missing SAN | Regenerate with SAN |
SSL_ERROR_BAD_CERT_DOMAIN | Domain not in certificate | Add domain to SAN |
ERR_SSL_PROTOCOL_ERROR | TLS version mismatch | Update client or server |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH | No common cipher | Adjust cipher suites |
| Apache startup failure | Invalid certificate path | Check file paths and permissions |
| Nginx startup failure | Invalid certificate format | Convert to PEM |
| Permission denied | Private key not readable | chmod 600 server.key |
| Expired certificate | Beyond validity period | Regenerate |
| Tool | Purpose |
|---|---|
| OpenSSL | Certificate inspection, connection testing |
| curl | Test HTTPS connections |
| testssl.sh | Comprehensive TLS scan |
| SSL Labs | Public server analysis |
| Wireshark | Deep packet inspection |
| Browser DevTools | Certificate viewer, security tab |
bash
#!/bin/bash
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -config san.cnf -extensions req_ext
sudo systemctl reload nginx0 2 1 * * /usr/local/bin/renew-cert.shCreate /etc/systemd/system/renew-cert.timer:
[Timer]
OnCalendar=monthly
Persistent=trueUse OpenSSL or mkcert in CI pipelines to generate ephemeral certificates for test environments.
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com| Tool | Windows | Linux | GUI | Free | Best For |
|---|---|---|---|---|---|
| OpenSSL | yes | yes | no | yes | Standard certificate generation |
| mkcert | yes | yes | no | yes | Local development |
| Smallstep (step‑ca) | yes | yes | partial | yes | Internal PKI |
| CFSSL | yes | yes | no | yes | Enterprise CA |
| XCA | yes | yes | yes | yes | GUI certificate management |
At Playful Sparkle, our development stack for certificate and TLS work includes:
Self‑signed SSL/TLS certificates are an essential tool for development, testing, and internal environments. They provide full encryption without the cost and administrative overhead of publicly trusted certificates.
Need help securing your development and production environments? Playful Sparkle has been engineering digital products since 2004, offering Web Development, App Development, and Security & DevOps services. Our team can help you set up TLS certificates, configure web servers, and establish secure development workflows. Contact us to discuss how we can help you build secure, production‑ready systems.