Making Self-signed Certificates Work

Colin J. Ihrig

Sometimes you need to configure HTTPS locally using a self-signed certificate. Here is a quick step by step guide. Note: this guide does not rely on "insecure" options such as curl's --insecure flag.

  1. Ensure that OpenSSL is installed. The following commands will rely on OpenSSL.

  2. We are going to act as a Certificate Authority (CA). Create a private key for the CA.

openssl genrsa -out ca.key.pem 2048
  1. Create the root certificate for the CA. This cert will be valid for roughly ten years (3,650 days). You will be prompted to provide some values. Feel free to just press Return/Enter until the prompting ends.
openssl req -x509 -new -nodes -key ca.key.pem -sha256 -days 3650 -out ca.cert.pem
  1. Create a key for our server.
openssl genrsa -out server.key.pem 2048
  1. Create a certificate signing request (CSR) for our server. Again, you will be prompted to provide some values. Again, feel free to press Return/Enter until the prompting ends.
openssl req -new -key server.key.pem -out server.csr
  1. Create an X509 v3 certificate extension. Create a file named server.cert.ext with the following contents. The alt_names section can be configured as needed.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = example.com
DNS.2 = localhost
  1. Create a certificate for the server.
openssl x509 -req -in server.csr \
  -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial \
  -days 3650 -sha256 -extfile server.cert.ext \
  -out server.cert.pem
  1. Use the server certificate and key somehow. The following example creates a Node.js server, which can be run via the command node server.js:
'use strict';
const https = require('node:https');
const fs = require('node:fs');
const options = {
  key: fs.readFileSync('server.key.pem'),
  cert: fs.readFileSync('server.cert.pem'),
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(4000);
  1. Make a request to the server. You should see "hello world" printed. The --cacert flag tells curl to use our CA. Our CA is not expected to be used by anything else such as a web browser, so you will still see an insecure warning if your navigate your browser to the same URL.
curl https://localhost:4000 --cacert ca.cert.pem