---
title: 'How to create an HTTPS server in localhost?'
date: '2022-03-05'
tags: ['https', 'localhost', 'nodejs', 'open-ssl', '443', 'mac-os', 'koajs']
summary: 'The simplest way to set up an HTTPS server in Nodejs application when developing locally'
images:
  [
    '/static/images/banners/black-iphone-5-beside-brown-framed-eyeglasses-and-black-iphone-5-c__danny144__ah-HeguOe9k.jpeg',
  ]
authors: ['default']
---

> When do we need **HTTPS** in **local**? <Twemoji emoji="thinking-face" />

## Problem

In most cases, we don't need **HTTPS** while developing app in **local**. However, if your app has features that require **authentication** with a third parties,
or listen to **webhooks** from another app... And those third parties require your app must adopt **HTTPS** in order to receive their request, then how would you do?

Of course, it will be **HTTPS** in **production**, but how to have a secure connection while developing your app locally?

We have several options like using [PageKite](https://pagekite.net/) or [ngrok](https://ngrok.com/) to create an **HTTPS tunnel** that points to your **localhost**,
but these services have a few drawbacks:

- Limited number of tunnels/requests to use at a time on free plan.

- Unable to use a specific **domain**, it will be different and we have to re-config the app's environment variable every time developing the app.

- And they're ~freaking~ **slowww** <Twemoji emoji="face-with-symbols-on-mouth" /> which wastes lots of time developing/testing our app.

If you're facing similar issues, here is the solution <Twemoji emoji="backhand-index-pointing-down" />

## Solution

The solution here is to use [OpenSSL](https://www.openssl.org/) to generate **SSL certificates** and create an HTTPS server with those **certificates**.

### <Twemoji emoji="keycap-1" /> Generate Root SSL Certificate

First, we need to create a [Root SSL Certificate](https://support.dnsimple.com/articles/what-is-ssl-root-certificate/) to sign any certificate that we will use for **localhost**.

Use the following command to create a `key` to generate **Root SSL Certificate** in the next step:

```bash
openssl genrsa -des3 -out rootCA.key 2048
```

![root-ca-key](/static/images/root-ca-key.png)

You will be asked to enter a **pass phrase** to generate the key, just enter a random string and verify it.

<Twemoji emoji="warning" /> Remember this **pass phrase** as you will need it in the next steps!

The generated key will be saved in `rootCA.key` file, use it to generate **Root SSL Certificate** with the following command:

```bash
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 7300 -out rootCA.pem
```

![root-ca-pem](/static/images/root-ca-pem.png)

Fill in the required information (the **pass phrase** must match the one that was used to generate the `rootCA.key` in the first step!)

This certificate will be saved in `rootCA.pem` file.

### <Twemoji emoji="keycap-2" /> Trust the Root SSL Certificate

To use the certificates generated by the **Root SSL Certificate** we need to tell the Operating System to trust the **Root Certificate**.

Open the **Keychain Access** app (MacOS), select **Certificate** tab:

![keychain-access](/static/images/keychain-access.png)

Import `rootCA.pem` by dragging in or navigating to **File / Import items...**.

Then **Right click / Get Info** (or **Double click**) on the imported certificate

![cert-trust-setting](/static/images/cert-trust-setting.png)

Expand the **Trust** tab, select **Always Trust** in the first setting, then save it.

The **Keychain Access** will then show a message like **"This certificate is marked as trusted for this account"** which means you have successfully trusted the **Root SSL Certificate** <Twemoji emoji="party-popper" />.

### <Twemoji emoji="keycap-3" /> Create local SSL Certificate

Now, we will use the trusted **Root SSL Certificate** to generate an **SSL Certificate** to use in **localhost**.

#### Step 1

Create a config file named `server.csr.cnf` with the following cotent. **OpenSSL** will use this file to generate the **Certificate key**:

```bash:server.csr.cnf showLineNumbers
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

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

Create a **Certificate key** with the above configs and save it to the `server.key` file:

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

![server-key](/static/images/server-key.png)

#### Step 2

Create a `v3.ext` file with the configurations below to generate the certificate:

```bash:v3.ext showLineNumbers
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
```

Generate certificate with **Root SSL Certificate** and `v3.ext` config file then save to `server.crt` with this command:

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

<Twemoji emoji="warning" /> Remember to use the same pass phrase in the steps above!

![server-cert](/static/images/server-cert.png)

Done <Twemoji emoji="party-popper" /><Twemoji emoji="party-popper" /><Twemoji emoji="party-popper" />

Now in the folder where you just created certificate will contain 2 files: `server.key` and `server.crt`. We will use these files to create an **HTTPS** server in the next step.

![cert-folder](/static/images/cert-folder.png)

### <Twemoji emoji="keycap-4" /> Create HTTPS server

The most important work of generating `server.key` and `server.crt` is done, now let's use that certificate to create a Nodejs **HTTPS** server in localhost using [Koa.js](https://koajs.com/)
(It's almost the same in **Express** cause **Koa** is created by the team behind **Express** <Twemoji emoji="beaming-face-with-smiling-eyes" />).

Create a simple **web server** with **Koa**:

```js:server.js showLineNumbers
let Koa = require('koa');
let app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

module.exports = app
```

Create a directory called `certs/` in your app, then move `server.key` and `server.crt` there.

Load the certificate files and create **HTTPS** server with `https` module:

```js:index.js showLineNumbers
let app = require('./server')
let https = require('https')
let fs = require('fs')
let path = require('path')

let certOptions = null
try {
  certOptions = {
    key: fs.readFileSync(path.resolve('certs/server.key')),
    cert: fs.readFileSync(path.resolve('certs/server.crt'))
  }
} catch(err) {
  console.log('No certificate files found!')
}

let host = process.env.APP_URL || 'localhost'
let isLocal = host === 'localhost'
let enableHTTPSInLocal = Boolean(isLocal && certOptions)

let port = enableHTTPSInLocal ? 443 : process.env.PORT || 3434
let protocol = (isLocal && !certOptions) ? "http" : "https"

let url = `${protocol}://${host}${isLocal ? `:${port}` : ''}`

let callback = () => {
  console.log(`App start successfully at ${url}`)
}

if (enableHTTPSInLocal) {
  https
    .createServer(certOptions || {}, app.callback())
    .listen(port, callback)
} else {
  app.listen(port, callback)
}
```

The **project structure** should looks like this:

![https-koa-project](/static/images/https-koa-project.png)

Start your app with `npm start` (<Twemoji emoji="warning" /> Notice that it's `node index.js`, not `node server.js`!)

![http-koa](/static/images/http-koa.png)

If no **certificate** file exists, the app will start at normal `http`.

![https-koa](/static/images/https-koa.png)

And after moving the **certificate** files in, the app will start with **https** on port **443**.

![localhost-443](/static/images/localhost-443.png)

Open `https://localhost:443` in your browser and you will see your app running with **HTTPS** <Twemoji emoji="party-popper" /> <Twemoji emoji="party-popper" />

The source code can be found in this [repo](https://github.com/hta218/local-https-with-koajs).

## Conclusion

If you're not a **Node.js** developer, then you can google how to create an **HTTPS** server with certificate files in your preferred technology <Twemoji emoji="grinning-face-with-sweat" />

I hope this guide can help you while developing your app with **HTTPS** locally!

Happy sharing <Twemoji emoji="clinking-beer-mugs" />

## References

- [OpenSSL - TLS/SSL and crypto library](https://github.com/openssl/openssl)
- [What is a Root SSL Certificate?](https://support.dnsimple.com/articles/what-is-ssl-root-certificate/)
- [Apple's Requirements for trusted certificates](https://support.apple.com/en-us/HT210176)
- [X509 v3 certificate](https://en.wikipedia.org/wiki/X.509)
- [Port 443 is used to secure communication travels between the client and the server](https://www.clickssl.net/blog/port-443)
