HTTPS enabled development environments

Have you encountered a situation where some features of your application require HTTPS but your development environment runs on some port on localhost using HTTP? There exists many specific solutions to this problem depending on the framework or tools that you are using but I'll present a general solution that works for many different kinds of situations and without you having to add extra configurations or parameters to your application.

Running your development environment over HTTP is fine. If your application however use HTTPS for some feature then this leads to unwanted differences between your production environment and development environment and it might even cause you to be unable to test certain aspects of your application during the development process.

The situation I ended up in which prompted this article was when I was working on integrating Keycloak authentication in a Next.js application. There was no simple configuration I could use to enable HTTPS and many of my peers seemed to settle for a worse development-experience by disabling Webpack's integrated development-server and instead using a naive HTTPS-enabled file server.

My proposed solution consists of signing your own certificates with your own root CA and reverse-proxying your development server. This means you can leave your development server unchanged and access it by HTTPS by using your reverse-proxy.

If you just want to get this solution up and running as soon as possible you can use my public GitHub repository which has all the files required for a localhost reverse-proxy setup with a Docker Nginx-container.

Certificate Authority basics

Your operating system and browser are both configured with a list of trusted root Certificate Authorities (CA). Unless a certificate is signed by one of the root CAs then the certificate will per-default not be trusted. You might get by without signing your local certificates with a root CA but for some services it can be necessary. Since it's easy and even if you don't have administrator rights on your system you should be able to at least import your own root CA for your browser you might as well do it.

As an example this site is signed by the CA Let's Encrypt. I have Let's Encrypt signs my certificates by sending them a certificate signing request and by proving to them that I am in control of the domain. This is done by passing challenges set by Let's Encrypt that test my ownership. For example by requesting that I create DNS records with their requested content and/or requesting that I serve arbitrary files.

Let's Encrypt is a CA trusted by most systems. Looking at the trust policy store on my Linux system I can find their common-name ISRG Root X1.

trust list
...
> pkcs11:id=%79%B4%59%E6%7B%B6%E5%E4%01%73%80%08%88%C8%1A%58%F6%E9%9B%6E;type=cert
>    type: certificate
>    label: ISRG Root X1
>    trust: anchor
>    category: authority
...

We will take on the role of Let's Encrypt by becoming our own CA.

Creating a root CA

You will need the OpenSSL command-line tool. I'll leave it up to you to make it available.

Run the following command to create the root CA key (devCA.key) and enter a passphrase when prompted.

openssl genrsa -des3 -out devCA.key 2048

Next create the certificate (devCA.pem). You can freely specify for how long it should be valid. I chose a conservative 1 year in the example.

You'll be promted for your previous passphrase and for some optional information. I left everything blank except for the Organization Name and the Common Name to help me identify the certificates.

openssl req -x509 -new -nodes -key devCA.key -sha256 -days 365 -out devCA.pem

...
> Country Name (2 letter code) [AU]:
> State or Province Name (full name) [Some-State]:
> Locality Name (eg, city) []:
> Organization Name (eg, company) []:dev-CA
> Organizational Unit Name (eg, section) []:
> Common Name (e.g. server FQDN or YOUR name) []:dev-CA
> Email Address []:
Creating a certificate extension configuration

We'll create a simple configuration which enables us to add arbitrary subject alternative names which simply put allows us to specify which domains the certificate is valid for.

I'll call this file san.ext

#san.ext
authorityKeyIdentifier = keyid, issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @SAN

[SAN]
DNS.1 = localhost

You can arbitrarily add more domains by using DNS.2, DNS.3 etc.

Creating CA-signed certificates

Begin by creating a key for the domain (localhost.key)

openssl genrsa -out localhost.key 2048

Next create a certificate signing request (localhost.csr). You are again prompted for information. I again left everything blank except for the Organization Name and the Common Name.

openssl req -new -key localhost.key -out localhost.csr

...
> Country Name (2 letter code) [AU]:
> State or Province Name (full name) [Some-State]:
> Locality Name (eg, city) []:
> Organization Name (eg, company) []:localhost
> Organizational Unit Name (eg, section) []:
> Common Name (e.g. server FQDN or YOUR name) []:localhost
> Email Address []:
>
> Please enter the following 'extra' attributes
> to be sent with your certificate request
> A challenge password []:
> An optional company name []:

Finally we create the CA signed certificate (localhost.crt) using the above created files. You'll be promted for the passphrase.

openssl x509 -req -CA devCA.pem -CAkey devCA.key -CAcreateserial -in localhost.csr -out localhost.crt -days 365 -sha256 -extfile san.ext

Now you have the key and certificate necessary to enable SSL.

Become a trusted CA

This is different on each OS. I'll give two examples, the first one adding the CA system-wide on Linux using the trust program with root permission and the second one adding it only for Firefox. If neither of these examples are usable by you information on adding root CA's to Windows, Mac, Chrome and different Linux distributions are readily available online.

Linux using trust

For Linux with the trust utility it's incredibly simple.

sudo trust anchor devCA.pem

This makes the certificate trusted system-wide.

You can inspect your trusted certificates using "trust list".

To remove the certificate run

sudo trust anchor --remove devCA.pem
Firefox

For Firefox you can add the CA by navigating through "Settings > Privacy & Security > Certificates: View Certificates > Import..."

Reverse proxy your development server

Run your preferred reverse proxy such as Nginx or Traefik locally.

I'll share some Nginx server-block configuration that has worked well for me that might help out.

server {
    listen 443 default_server ssl http2;
    listen [::]:443 ssl http2;

    server_name localhost;

    ssl_certificate certs/localhost.crt;
    ssl_certificate_key certs/localhost.key;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_pass http://my-http-dev-server:my-port;
    }
}

Now you should be able to access your development server with a trusted HTTPS connection using https://localhost.