Skip to main content

Generate self-signed SSL/TLS certificate for local IP address or local domain

In real life, when we build our website and make it public, some paid or free Certificate Authority (CA) will help us sign a certificate for our website domain (IP address is not acceptable!) and enable SSL/TLS connections from user browser to our server.

Given the secure reasons, the browser will only admit those servers's certificates signed from the authorized CA, and the CA certificate is kept in our host system trust store. In Linux, you can view the CA certificate file like /etc/ssl/certs/ca-certificates.crt.

note

One of the most popular Certificate Authorities is Let's Encrypt, which is a free and non-profit CA.

However, in many internal networks and development environments, we often need self-signed certificate more frequently.

Here is an example, we will generate a local server certificate that is signed by a local CA. Finally, let Chrome can visit our local website without security warning.

In brief, these steps we need to sign local sever certificate actually simulate how those CA sign certificates for public servers, as following:

  1. Create a local Root CA.
  2. Create a CSR(Certificate Signing Request) file for local server 127.0.0.1.
  3. The local Root CA use the CSR to generate a certificate for local server 127.0.0.1.
  4. Install the local Root CA into our system(Windows, Ubuntu or macOS) trust store.
  5. Run a simple https server to test local server certificate.

For those official CA, they have to validate the domain is owned by the server before the step 3, and we can ignore step 4 as they are already installed into the system or the browser trust store.

And there is nice picture from How to create your own self-signed root Certificate Authority(CA) to show the relationship between CA, server and browser.

1. Create a local CA

Generate a file RootCA.key and a file RootCA.crt of our local root CA:

openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.crt -subj "/C=US/CN=Example-Root-CA"

You can change Example-Root-CA to others or add more fields to CA.

[Optional] Create a CA with a configuration file,

openssl req -x509 -nodes -new -keyout RootCA.key -out RootCA.crt -config <(cat <<EOF
[ req ]
default_bits = 2048
default_md = sha256
default_days = 3650
prompt = no
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
C = US
ST = California
L = San Francisco
O = Example Corp
OU = IT Department
CN = www.example.com
emailAddress = admin@example.com
EOF
)
note

process substitution does not work in bash scripts!

2. Create a signed certificate for the local server

Next, we should apply the local CA to sign a certificate for our local server using the server's Certificate Signing Request (CSR) , which will be accessed through the localhost or 127.0.0.1 from our local machine.

2.1. Generate the server's CSR

When generating the CSR file with OpenSSL, we can either specify certain details directly in the command line or use a configuration file. While you can provide some information via command-line arguments, complex configurations like specifying [v3_req] and [alt_names] in belowing are typically done through a configuration file.

subjectAltName in [v3_req] will let you specify more than domain/IP addresses as Subject Alternative Names (SANs). And now modern browser require the server certificate to include SANs. Usage of the Common Name (CN) alone is not considered secure enough, and omitting SANs may result a certificate validation error!

Here is an example for using pure arguments to specify -subj:

openssl req -new -newkey rsa:2048 -nodes -keyout privkey.pem -out csr.pem -subj "/C=US/ST=California/L=San Francisco/O=Example Corp/OU=IT Department/CN=www.example.com/emailAddress=admin@example.com"

Here is an example to use the configuration file as -config.

  1. [Optional] Customize DNS by editing /etc/hosts (Other machines also have to do this if they would like to visit the server),
/etc/hosts
127.0.0.1   localhost
127.0.0.1 fake1.local
127.0.0.1 fake2.local
  1. Create a configure file for CSR including typical sections as:
localhost.conf
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
countryName = XX
stateOrProvinceName = N/A
localityName = N/A
organizationName = Self-signed certificate
commonName = 127.0.0.1: Self-signed certificate

[v3_req]
subjectAltName = @alt_names

[alt_names]
IP.1 = 127.0.0.1
DNS.1 = localhost
DNS.2 = fake1.local
DNS.3 = fake2.local
  1. Generates the CSR

To generate a CSR using the configuration file with OpenSSL, you can use the following command:

openssl req -new -nodes -keyout localhost.key -out localhost.csr -config localhost.conf

[Optional] mix -subj and -config, to be short like:

SAN_LIST="[SAN]\nsubjectAltName=DNS:localhost, DNS:*.localhost, IP:127.0.0.1"
openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example CORP/CN=localhost.local" -reqexts SAN -config <(echo -e "$SAN_LIST")

Here, localhost.conf is the configuration file and the two outputs are:

  • localhost.key is the private key file for the local server to communicate with the clients securely.
  • localhost.csr is the CSR file that the local server will use in the next step to sign its certificate from the local CA.
  1. Verify the CSR file localhost.csr:
openssl req -text -noout -verify -in localhost.csr

2.2. Sign the CSR with the local CA

Now, it's time for local CA to sign a certificate for our local server by using the server's CSR localhost.csr file, thereby issuing a signed certificate.

  • Generates localhost.crt by using CSR localhost.csr,
openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.crt -CAkey RootCA.key -CAcreateserial -extensions v3_req -extfile localhost.conf -out localhost.crt
info

-extensions v3_req -extfile localhost.conf: openssl x509 will contain subjectAltName extension in the certificate.

warning

If X509 extensions(subjectAltName) are missing from the certificate, the browser will still report security issues, such as its security certificate does not specify Subject Alternative Names.

Or after OpenSSL v3, you can copy the extensions specified in the CSR to the certificate by openssl x509 as this:

openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.crt -CAkey RootCA.key -CAcreateserial -copy_extensions copyall -out localhost.crt
  • View the generated localhost.crt:
openssl x509 -text -noout -in localhost.crt
  • Verify the generatedlocalhost.crt:
openssl verify -verbose -CAfile RootCA.crt localhost.crt
note

If the CA and Subject are the same one, the step of creating the local CA can be skipped.

openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf

3. Use the signed certificate in the local server

Run up a node https server to use the signed certificate for the local sever.

npx http-server -p 8082 --ssl --cert localhost.crt --key localhost.key

Then visit:

The browser will give you security warning as the local root CA is not trusted in default.

4. Install the local CA

TO trust the root local CA, we must install the local CA certificate RootCA.crt into each system trust store or each browser.

  • Windows system trust store
  • Ubuntu system trust store
  • macOS system trust store

Then visit again, the browser will show green!

[Optional] Sign the CSR with openssl ca

For complicated configuration for CA to sign a certificate, you can use openssl ca and the configuration file is like:

cd /path/to/your/ca/
mkdir -p newcerts
touch index.txt
echo 1000 > serial
cat <<EOF > /path/to/your/ca/openssl.cnf
[ ca ]
default_ca = CA_default

[ CA_default ]
dir = /path/to/your/ca
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/RootCA.crt
serial = $dir/serial
private_key = $dir/RootCA.key
default_days = 365
default_md = sha256
policy = policy_any
x509_extensions = usr_cert
copy_extensions = copy

[ policy_any ]
countryName = supplied
stateOrProvinceName = supplied
organizationName = supplied
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ usr_cert ]
basicConstraints=CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
EOF
openssl ca -config /path/to/your/ca/openssl.cnf -in localhost.csr -out localhost.crt -batch

Troubleshooting

Chrome red security warning

  1. Go to Developer Tools.
  2. Click Security tab.
  3. Check Security overview issues.

Resources

How to create an HTTPS certificate for localhost domains · GitHub

How to add X.509 extensions to certificate OpenSSL | GoLinuxCloud

GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd like.