Generating a Self-Signed SSL Certificate on Windows and Linux: Complete Setup, Security, and Best Practices

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.

TL;DR

  • A self-signed SSL certificate is signed by its own private key, not by a trusted Certificate Authority. It provides full encryption but triggers browser trust warnings because there is no external validation of identity.
  • Modern browsers require Subject Alternative Names (SAN) . A certificate without SAN will generate a NET::ERR_CERT_COMMON_NAME_INVALID error regardless of the Common Name field.
  • mkcert is the recommended tool for local development, creating locally-trusted certificates without browser warnings. OpenSSL remains the standard for custom or automated certificate generation.
  • Private keys must be protected with strict permissions (chmod 600) and should never be committed to version control. Use RSA 4096 or ECDSA for cryptographic strength.
  • Production environments must use publicly trusted certificates from Let’s Encrypt or a commercial CA. Self-signed certificates are appropriate only for development, testing, and internal services.

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.

Understanding SSL and TLS

What is SSL?

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.

What is TLS?

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.

Why Everyone Still Says “SSL”

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.

How HTTPS Works

HTTPS is HTTP over TLS. The connection establishment follows these steps:

HTTPS connection establishment showing the sequence of TCP and TLS handshake messages between a client and server, including the TCP handshake, TLS Client Hello, Server Hello, certificate exchange, key exchange, finished messages, and the start of encrypted application data transmission.

TLS handshake diagram:

TLS handshake sequence illustrating how a client and server negotiate protocol settings, exchange cryptographic keys and certificates, establish a shared encrypted session, and begin secure data transmission over the encrypted connection.

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.

Certificate Terminology

TermExplanation
CertificateAn X.509 document binding a public key to an identity (domain name, organisation).
Public keyThe key used to encrypt data or verify signatures; it is safe to distribute.
Private keyThe 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 certificateThe self‑signed certificate at the top of a trust chain.
Intermediate certificateA 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 certificateA certificate matching *.example.com, valid for any subdomain.
EV certificateExtended Validation certificate, requiring extensive validation, displayed with a green bar in some browsers.
DV certificateDomain‑Validated certificate, only confirming domain control.
OV certificateOrganisation‑Validated certificate, confirming both domain control and organisation existence.
PEMPrivacy‑Enhanced Mail, a base64‑encoded ASCII format with -----BEGIN ...----- headers.
CRT / CERCertificate files; can be PEM or DER.
DERBinary format containing only the certificate.
PFX / PKCS#12A password‑protected binary container for certificates and private keys.
PKCS#8A standard for encoding private keys, often used with PEM.
JKSJava KeyStore, a format used by Java applications.

Types of SSL Certificates

TypeBrowser TrustCostRenewalValidationSecurityTypical Usage
Self‑signedno (warning)FreeManualNoneFull encryptionDevelopment, testing, internal services
Let’s EncryptyesFree90 days (auto)Domain controlFullProduction, personal sites
Commercial DVyes€10–€50/yr1–2 yearsDomain controlFullProduction, small business
OVyes€50–€200/yr1–2 yearsDomain + organisationFullBusiness websites
EVyes€200–€500+/yr1–2 yearsExtended validationFullFinancial, e‑commerce
Internal enterprise CAyes (internally)Internal costCustomInternal policyFullInternal corporate PKI

When to Use a Self‑Signed Certificate

Self‑signed certificates are appropriate in these scenarios:

  • Local development environments (localhost, 127.0.0.1)
  • Docker containers and container orchestration (Kubernetes, Docker Compose)
  • Internal APIs that are not exposed to the public internet
  • Home labs and personal projects
  • Development microservices
  • IoT devices (Raspberry Pi, embedded systems)
  • Internal dashboards and monitoring tools
  • Staging environments where public trust is not required

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.

When NOT to Use a Self‑Signed Certificate

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:

  • Public e‑commerce websites
  • Customer‑facing portals
  • Any service that handles sensitive user data over the public internet
  • API endpoints consumed by third parties

Production‑grade services must use certificates from a publicly trusted CA such as Let’s Encrypt, DigiCert, or Sectigo.

Prerequisites

Windows

MethodDescription
Official OpenSSL installerOpenSSL 4.0.1 or 3.5.7 LTS installers available
Chocolateychoco install openssl
Scoopscoop install openssl
Git BashIncludes OpenSSL by default
WSLRun Linux OpenSSL from Windows

Linux

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 openssl

Fedora / RHEL / Rocky / AlmaLinux:

sudo dnf install openssl

Arch Linux:

sudo pacman -S openssl

Verify installation:

openssl version

Installing OpenSSL

Windows Installation

Method 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 openssl

Method 3: Scoop

scoop install openssl

After installation, add the OpenSSL bin directory to your system PATH and verify:

openssl version

Linux Installation

# Ubuntu / Debian
sudo apt update && sudo apt install openssl
# Fedora / RHEL / Rocky / AlmaLinux
sudo dnf install openssl
# Arch Linux
sudo pacman -S openssl

Understanding Certificate File Formats

FormatExtensionContentReadable
PEM.pem, .crt, .cer, .keyBase64‑encoded, ASCIIYes
DER.der, .cerBinaryNo
PKCS#12.pfx, .p12Binary container (cert + key)No
JKS.jksJava KeyStoreNo

Conversion examples:

Convert DER to PEM:

openssl x509 -inform der -in certificate.cer -out certificate.pem

Convert PKCS#12 to PEM:

openssl pkcs12 -in certkey.pfx -out certkey.pem -nodes

Convert PEM certificate + key to PKCS#12:

openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx

Generate a Self‑Signed Certificate

openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes

Warning: 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 = ::1

Step 2: Generate the certificate

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -sha256 -config san.cnf -extensions req_ext

One‑Liner with SAN (Linux / bash)

openssl 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.

RSA 2048 vs RSA 4096 vs ECDSA

AlgorithmKey sizePerformanceCompatibility
RSA 20482048 bitsFastUniversal
RSA 40964096 bitsSlowerUniversal
ECDSA P‑256256 bitsVery fastModern browsers
ECDSA P‑384384 bitsFastModern browsers

Recommendation: Use RSA 4096 for general‑purpose certificates. Use ECDSA for high‑performance environments.

Generate SAN Certificates

Why CN Is Deprecated

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.

11.2 Configure SAN for Multiple Domains

[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.5

localhost and 127.0.0.1

For local development, always include:

  • DNS.1 = localhost
  • DNS.2 = *.localhost (if using .localhost domains)
  • IP.1 = 127.0.0.1
  • IP.2 = ::1

Installing the Certificate into the Operating System

Windows Certificate Store

  1. Double‑click the .crt file
  2. Click Install Certificate
  3. Select Local Machine
  4. Choose Place all certificates in the following store
  5. Browse and select Trusted Root Certification Authorities
  6. Click OKFinish

Linux Trust Store

Debian / Ubuntu:

sudo cp server.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

Fedora / RHEL / CentOS:

sudo cp server.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

macOS

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain server.crt

Firefox

Firefox uses its own certificate store. Import via:

  • SettingsPrivacy & SecurityCertificatesView CertificatesImport

Apache Configuration

Prerequisites

Enable SSL module:

sudo a2enmod ssl
sudo a2enmod headers # for HSTS
sudo systemctl restart apache2

VirtualHost Example

<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>

13.3 Redirect HTTP to HTTPS

<VirtualHost *:80>
 ServerName localhost
 Redirect permanent / https://localhost/
</VirtualHost>

Enable HTTP/2

sudo a2enmod http2

Add to VirtualHost:

Protocols h2 http/1.1

Nginx Configuration

HTTPS Server Block

server {
 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;
}

Redirect HTTP

server {
 listen 80;
 listen [::]:80;
 server_name localhost;
 return 301 https://$server_name$request_uri;
}

HTTP/3 (QUIC)

Nginx supports HTTP/3 with the QUIC module. Add:

listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';

Microsoft IIS Configuration

  1. Open IIS Manager
  2. Select the server node → Server Certificates
  3. Click Import and select the PFX file
  4. Bind the certificate to the website:
    • Select the site → BindingsAddhttps
    • Select the certificate from the dropdown
    • Enable Require Server Name Indication (SNI)

Caddy Configuration

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.

Node.js HTTPS Server

Native HTTPS

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);

Express

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);

PHP Development Servers

Laravel (Valet / Herd)

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.crt

Symfony

symfony server:start --port=443 --ssl-key=server.key --ssl-cert=server.crt

PHP Built‑in Server

php -S localhost:443 -t . --ssl-key=server.key --ssl-cert=server.crt

Docker

Generate Certificates in Docker

FROM 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"

Docker Compose with Volume Mounting

version: '3'
services:
 nginx:
 image: nginx:alpine
 volumes:
 - ./nginx.conf:/etc/nginx/conf.d/default.conf
 - ./certs:/etc/ssl/certs
 ports:
 - "443:443"

Traefik

Traefik can generate self‑signed certificates for local development using its resolvers configuration.

Kubernetes

Create a Secret

kubectl create secret tls tls-secret --key server.key --cert server.crt

Ingress with NGINX

yaml

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: 80

cert‑manager (for development)

cert‑manager can use a self‑signed Issuer for local development:

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
 name: selfsigned-issuer
spec:
 selfSigned: {}

Local Development Tools

ToolAdvantagesDisadvantagesRecommended Usage
mkcertZero config, local CA, browser trustRequires CA installationDefault for local dev
OpenSSLUniversal, fine‑grained controlComplex, manual trust managementCustom certificate needs
step‑caFull PKI, ACME supportHeavier setupInternal CA, team use
devcertNode.js integrationLimited to NodeJavaScript 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.local

mkcert creates locally‑trusted certificates without browser warnings.

Certificate Validation

Browser

  • Chrome: Click the padlock → Connection is secureCertificate is valid
  • Firefox: Click the padlock → Connection secureMore informationView Certificate

OpenSSL

# 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 localhost

curl

curl -v https://localhost --cacert server.crt

testssl.sh

./testssl.sh https://localhost

testssl.sh checks TLS/SSL ciphers, protocols, and cryptographic flaws.

SSL Labs

Visit https://www.ssllabs.com/ssltest/ to analyse a publicly accessible server.

Security Best Practices

Private Key Protection

  • File permissions: chmod 600 server.key
  • Never commit private keys to version control
  • Use password‑protected keys where appropriate: bashopenssl genrsa -aes256 -out server.key 4096

Certificate Rotation

  • Issue certificates with a validity period of 365 days or less
  • Automate rotation using scripts or cron jobs
  • For production, use Let’s Encrypt with 90‑day certificates

Use Strong Algorithms

  • Avoid SHA‑1: Use -sha256 or -sha384
  • Avoid RSA < 2048 bits
  • Prefer ECDSA over RSA where possible

Disable Obsolete TLS Versions

  • Disable TLS 1.0 and TLS 1.1
  • Enable TLS 1.2 and TLS 1.3 only

TLS Configuration Best Practices

TLSv1.2
TLSv1.3

For TLS 1.3 (OpenSSL names):

TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256

For 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

Additional Recommendations

  • Enable Perfect Forward Secrecy (PFS) – all modern ciphers support it
  • Enable OCSP Stapling for certificate revocation checking
  • Enable HSTS to enforce HTTPS
  • Disable compression (CRIME attack)
  • Enable session resumption for performance

Performance Considerations

FactorImpact
RSA vs ECCECC is faster, especially on mobile devices
TLS session reuseReduces handshake overhead
TLS 1.3One‑round‑trip handshake, faster than TLS 1.2
HTTP/2Multiplexing, header compression
HTTP/3 (QUIC)Even faster, uses UDP
Certificate sizeRSA 4096 certificates are larger, slower

Common Errors and Troubleshooting

ErrorCauseSolution
NET::ERR_CERT_AUTHORITY_INVALIDSelf‑signed, not trustedInstall CA or use mkcert
ERR_CERT_COMMON_NAME_INVALIDMissing SANRegenerate with SAN
SSL_ERROR_BAD_CERT_DOMAINDomain not in certificateAdd domain to SAN
ERR_SSL_PROTOCOL_ERRORTLS version mismatchUpdate client or server
ERR_SSL_VERSION_OR_CIPHER_MISMATCHNo common cipherAdjust cipher suites
Apache startup failureInvalid certificate pathCheck file paths and permissions
Nginx startup failureInvalid certificate formatConvert to PEM
Permission deniedPrivate key not readablechmod 600 server.key
Expired certificateBeyond validity periodRegenerate

Debugging Tools

ToolPurpose
OpenSSLCertificate inspection, connection testing
curlTest HTTPS connections
testssl.shComprehensive TLS scan
SSL LabsPublic server analysis
WiresharkDeep packet inspection
Browser DevToolsCertificate viewer, security tab

Automation

Renewal Script (Bash)

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 nginx

cron (Linux)

0 2 1 * * /usr/local/bin/renew-cert.sh

systemd Timer

Create /etc/systemd/system/renew-cert.timer:

[Timer]
OnCalendar=monthly
Persistent=true

GitHub Actions / CI/CD

Use OpenSSL or mkcert in CI pipelines to generate ephemeral certificates for test environments.

Migrating to a Trusted Certificate

Replace Self‑signed with Let’s Encrypt

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Replacement Checklist

  • Obtain certificate from trusted CA
  • Deploy certificate with zero downtime (use staging environment)
  • Update load balancers and CDN configurations
  • Update internal services and API clients
  • Update monitoring and alerting
  • Test thoroughly before switching production traffic
  • Set up automated renewal
ToolWindowsLinuxGUIFreeBest For
OpenSSLyesyesnoyesStandard certificate generation
mkcertyesyesnoyesLocal development
Smallstep (step‑ca)yesyespartialyesInternal PKI
CFSSLyesyesnoyesEnterprise CA
XCAyesyesyesyesGUI certificate management

Software We Use

At Playful Sparkle, our development stack for certificate and TLS work includes:

Certificate Generation

  • OpenSSL – standard tool for custom certificates
  • mkcert – default for local development to avoid browser warnings

Web Servers

  • Apache HTTP Server – for legacy and PHP applications
  • Nginx – for modern web applications and reverse proxying
  • Caddy – for automatic HTTPS and zero‑config setups

Development

  • Docker & Docker Compose – containerised development environments
  • WSL – seamless Linux development on Windows
  • Git Bash – OpenSSL on Windows without installation

Testing

  • curl – basic connection testing
  • OpenSSL s_client – detailed TLS inspection
  • testssl.sh – comprehensive TLS/SSL scanning

Security Analysis

  • SSL Labs – public server security grading
  • testssl.sh – internal server security auditing

Conclusion

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.

Appropriate use cases

  • Local development and testing
  • Internal services and APIs
  • Containerised and orchestrated environments
  • IoT and embedded systems

Security recommendations

  • Always include Subject Alternative Names
  • Use RSA 4096 or ECDSA
  • Apply modern TLS settings (TLS 1.2/1.3 only)
  • Protect private keys with strict permissions
  • Rotate certificates regularly
  • Use mkcert for local development to avoid browser warnings

When to migrate to a publicly trusted CA

  • Any production service exposed to external users
  • Services handling sensitive user data
  • Public e‑commerce or business websites

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.

Was this helpful - Post
Zsolt Oroszlány

Zsolt Oroszlány

Founder & Chief Creative Officer of Playful Sparkle since 2004, combining business leadership, digital strategy, design, and software engineering to help organizations build effective digital solutions. Regularly publishes insights on web development, SEO, design, and emerging technologies.