This is a complete tutorial on how to get HTTPS working on localhost or any local domains. The main part is about manual SSL certificate creation. It is worth to now almost low-level details. If you need just a quick and easy setup, go to the end of the article where you will find other solutions.

What is this tutorial about:

  • How to set up local a domain
  • How to create a self-signed SSL certificate (or more accurately, TLS certificate)
  • How to configure Nginx to use an SSL certificate
  • How to run Nginx with HTTPS configured in a Docker container using docker-compose
  • Solutions without manual certificate creation

I am using Linux so this tutorial is for Linux OS. If you use Windows, you can run the same commands using the Git Bash emulator or WSL (Windows Subsystem for Linux). Though I didn’t try it, some commands may not work. Anyway, the setup path is the same. Only commands may differ.

We will use Docker with docker-compose and Nginx at the end to test if HTTPS works.

Why you might need an HTTPS for local domain

There are some reasons you might need to have HTTPS enabled for local domain:

  • You may need it to implement OAuth. For example, if you’re implementing Facebook login button on your website, it requires HTTPS to test it locally
  • If you have a local domain configured in the “hosts” file, and you want to test Service worker locally
  • Check if your web app redirects work as expected

Set up a local domain

You can use “localhost” as a domain name or create a local custom domain for development purposes. If you decide to continue with “localhost”, just skip this part and go to the next.

I will use “myapp.local” domain name as an example throughout the tutorial. You can use any domain you want.

To create a local domain you need to add a new record to the “hosts” file.

Open “/etc/hosts” file using “nano” or “vim” editor. In most cases, you will need to add “sudo” to have permission to edit and save the file. I am using “nano” editor in the command below.

sudo nano /etc/hosts

Add a new line with a domain name you want.

127.0.0.1       myapp.local

This will map “myapp.local” domain (or any domain name you chose) to 127.0.0.1 IP. This IP is mapped to localhost in the same file.

Save and close the editor. Now when you start the Nginx server (i.e. if you use Docker from this tutorial), you can use myapp.local domain in the address bar in the browser.

Create Root self-signed SSL certificate

You can use a root certificate to create any number of local certificates. Place root certificate in the project folder or in any global folder to reuse it for any local project.

I created a new folder “local-root-ca” in the user’s home directory.

mkdir ~/local-root-ca
cd ~/local-root-ca

Run “genrsa” command in that folder to create a private RSA key.

openssl genrsa -des3 -out rootCA.key 2048

Then create self-signed root CA (Certificate authority) certificate with the command below.

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem

The “req” command creates a self-signed certificate for use as root CA.

You will be prompted to enter some certificate information. I used all default so just hit Enter for every prompt.

The output will look like this.

OpenSSL req command output screenshot

The next step is to add a self-signed root CA certificate to the trusted certificates in the browser. You will find tutorials for the Chrome and Firefox browsers below.

Add self-signed SSL certificate to the trusted in Chrome browser

I used version 90 of the Chrome browser.

Go to Settings (click three dots in the top right). Then click on the “Privacy and Security” menu on the left.

Chrome Privacy and Security menu screenshot

Choose the “Security” menu item.

Chrome Security menu screenshot

Then scroll to the bottom and choose the “Manage Certificates” section in the “Advanced” section.

Chrome Manage Certificates menu screenshot

Choose the “Authority” tab and click the “Import” button.

Chrome Authority tab menu screenshot

Then choose your previously created “rootCA.pem” file.

Check at least “Trust this certificate for identifying websites” and click OK.

Chrome Trust this certificate screenshot

That’s it. Now the Chrome browser trusts your certificates we will create lately.

Add self-signed SSL certificate to the trusted in Firefox browser

I used version 88 of the Firefox browser.

Go to Preferences (click on the hamburger menu in the top right first). Then click on the “Privacy & Security” menu item on the left.

Firefox Privacy & Security menu screenshot

Scroll down and find the “Security” section, and the “View Certificates” button here. Click it.

Firefox Privacy & Security menu screenshot

Choose the “Authorities” tab and click the “Import” button. Find your previously created “rootCA.pem” file and choose it.

Firefox Authorities tab screenshot

In the next dialog check at least “Trust this CA to identify websites”. Then click the “Ok” button.

Firefox Downloading certificate dialog screenshot

That’s it. Now Firefox browser trusts your certificates we will create lately.

Create domain SSL certificates common configuration

Prepare config files you will use to create domain SSL certificates. This is a one-time task. You can use it for multiple local domains lately.

Create a certificate config file named “server.csr.cnf” with the content below.

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=US
ST=RandomState
L=RandomCity
O=RandomOrganization
OU=RandomOrganizationUnit
emailAddress=example@example.com

CN = ${ENV::SSL_DOMAIN_TEMP}

Create an extension file named “v3.ext” with the content below.

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${ENV::SSL_DOMAIN_TEMP}

We will use the environment variable “SSL_DOMAIN_TEMP” here. It will be easier to reuse this config for the different domains.

Create a domain SSL certificate

Set the domain name we want to create an SSL certificate to the environment variable “SSL_DOMAIN_TEMP”.

export SSL_DOMAIN_TEMP=myapp.local

Then create “crt” and “key” files. Both are parts of an SSL certificate. The “crt” file is a signed SSL certificate and the “key” file is a private key to it.

Create a “key” file command.

openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config <( cat server.csr.cnf )

Create a “crt” file command.

openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext

Create new folder “certificates”. It might be in the project folder if you will use Docker Compose. Or in any folder, if you use Nginx without Docker.

Then copy domain SSL certificate files (“server.crt” and “server.key”) to the new folder. File “server.csr” can be deleted.

Nginx HTTPS configuration sample

If you’re using Docker from this tutorial, you need to create an Nginx configuration file. Create a new file “default.conf” in the project folder.

Here is the basic Nginx configuration for localhost with HTTPS enabled.

server {
    server_name _;
    
    listen 80 default_server;
    listen 443 ssl;

    ssl_certificate      /var/server.crt;
    ssl_certificate_key  /var/server.key;

    location / {
        root   /usr/share/nginx/html/;
        index  index.html index.htm;
    }
}

If you don’t use Docker, then use this config as a base for your local Nginx configuration.

Run Nginx using Docker Compose

You can skip this part if you don’t use Docker.

Create the “docker-compose.yml” file in the project directory. We already have folder “certificates” here.

Fill docker-compose.yml with the content below.

version: '3'

services:
    nginx:
        image: nginx:alpine
        ports:
            - 80:80
            - 443:443
        volumes:
            - ./default.conf:/etc/nginx/conf.d/default.conf
            - ./certificates/server.crt:/var/server.crt
            - ./certificates/server.key:/var/server.key

Here we are:

  • create a simple Nginx container from the lightweight Alpine image
  • map 80 (HTTP) and 443 (HTTPS) ports from the container
  • mount Nginx config and SSL certificate inside the Docker container

If you have another web server running, Docker can’t map ports 80 and 443. So either stop web server or change mapped ports, like “-81:80”. In this case, you will test the website using a URL with a port, i.e. http://localhost:81.

Test if localhost with HTTPS works

All steps completed. Now we can run the Docker container and check if HTTPS works.

Start Docker container. Run the command below from the project folder.

docker-compose up

Wait for the container to start and go to the browser. Enter URL https://myapp.local or any domain you used in the configuration. You will the website has a secure connection in the browser.

Localhost HTTPS secure connection screenshot

Solutions without manual certificate creation

As I promised, here are simple solutions to use. Choose any.

  • Use mkcert utility to automate the whole process described above
  • Use Caddy web server with automatic HTTPS
  • Use ngrok to expose local server to the public URL with HTTPS