Emmanuel Corels
← Blog
Cybersecurity

Create a self-signed localhost certificate with correct SANs

Generate a short-lived local TLS certificate, include every DNS/IP identity, protect the private key, establish trust deliberately and verify hostname plus chain.

By Emmanuel Corels

A self-signed certificate is useful for isolated development and controlled laboratories. It provides encryption and proves possession of its private key, but clients will not trust it automatically. Modern hostname verification also depends on Subject Alternative Names (SANs), not merely a convenient CN=localhost.

Decide the exact identities before generating anything

If clients connect as localhost, api.test, 127.0.0.1 and ::1, each identity needs the correct SAN type. DNS names use DNS:; numeric addresses use IP:. A certificate for localhost does not match 127.0.0.1 unless both appear.

Generate a short-lived key and certificate

umask 077
openssl req -x509 -newkey rsa:3072 -sha256   -days 30 -noenc   -keyout localhost.key   -out localhost.crt   -subj '/CN=localhost'   -addext 'subjectAltName=DNS:localhost,DNS:api.test,IP:127.0.0.1,IP:::1'   -addext 'basicConstraints=critical,CA:FALSE'   -addext 'keyUsage=critical,digitalSignature,keyEncipherment'   -addext 'extendedKeyUsage=serverAuth'

Current OpenSSL uses -noenc; older releases commonly show -nodes. An unencrypted service key permits unattended startup, so filesystem access becomes the primary protection. If the server supports an encrypted key through a secret manager or agent, use that operational model instead.

Inspect the artifact rather than trusting the command

openssl x509 -in localhost.crt -noout   -subject -issuer -serial -dates -fingerprint -sha256

openssl x509 -in localhost.crt -noout   -ext subjectAltName,basicConstraints,keyUsage,extendedKeyUsage

openssl pkey -in localhost.key -check -noout

A self-signed leaf has the same subject and issuer. Confirm the SANs, validity window, key usage and CA false constraint. Then confirm the public keys match:

openssl x509 -in localhost.crt -pubkey -noout |
  openssl pkey -pubin -outform DER |
  openssl dgst -sha256

openssl pkey -in localhost.key -pubout -outform DER |
  openssl dgst -sha256

The two digests must be identical.

Protect the private key

chmod 0600 localhost.key
chmod 0644 localhost.crt
ls -l localhost.key localhost.crt

Keep the key out of source control, container images, tickets and chat. Add filename patterns to ignore rules, but remember that ignore rules do not remove a key already committed. If exposure is suspected, replace it and remove trust for the old certificate.

Configure the local server with separate certificate and key files

server {
    listen 8443 ssl;
    server_name localhost api.test;

    ssl_certificate     /etc/local-tls/localhost.crt;
    ssl_certificate_key /etc/local-tls/localhost.key;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}
sudo nginx -t
sudo systemctl reload nginx

Restrict the key so the service account can read it but unrelated users cannot. The exact group and deployment path should follow the service manager’s security model.

Test encryption without pretending trust

openssl s_client   -connect 127.0.0.1:8443   -servername localhost   -showcerts </dev/null

This shows the handshake and presented chain. A verification error is expected until the client is explicitly configured to trust the certificate or its issuing development CA.

Verify trust and hostname together

openssl s_client   -connect 127.0.0.1:8443   -servername localhost   -verify_hostname localhost   -verify_return_error   -CAfile localhost.crt </dev/null

curl --cacert localhost.crt   --resolve localhost:8443:127.0.0.1   https://localhost:8443/health

curl -k disables verification and can hide the exact defect you are trying to fix. Use a scoped CA file or controlled local trust store instead.

Prefer a development CA for teams and multiple services

Trusting each self-signed leaf does not scale. A private development CA can be installed once on managed test devices and used to sign short-lived leaf certificates. Protect the CA key more strongly than any server key, keep it out of application hosts, mark CA constraints correctly, and never install a development root on production systems.

Diagnose the common failures

ErrorCauseRepair
Certificate not trustedNo trust anchor configuredUse scoped CA file or managed local trust
Hostname mismatchConnected name absent or wrong SAN typeRegenerate with correct DNS/IP SAN
Key values mismatchCertificate paired with another private keyCompare public-key digests and deploy pair
Permission denied reading keyService account lacks accessCorrect owner/group and restrictive mode
Works only with -kVerification was disabled, not fixedTest chain and hostname explicitly

Know where self-signed stops being appropriate

For public services, use a certificate issued through the organization’s approved public or private PKI, with automated renewal and monitoring. A self-signed localhost certificate is not a shortcut around domain control, revocation, rotation or trust distribution.

TLS can encrypt a connection to the wrong peer. Verification—trust chain plus hostname—is what turns encryption into authenticated transport.

Sources: the solved Stack Overflow question (question by michelemarcon; accepted answer by Diego Woitasen, CC BY-SA), OpenSSL’s official req, x509, and X.509 extension documentation.

Primary source: Review the official reference ↗