DEV Community

Cover image for How To Setup A Node HTTPS/TLS server: Internal systems.
Sk
Sk

Posted on

How To Setup A Node HTTPS/TLS server: Internal systems.

Backend engineering is one thing: getting two or more nodes to talk over the internet.

That’s your job. Everything you do, databases, APIs, protocols; boils down to this: can one machine send a packet to another, and can the other reply? That’s it.

Once that packet leaves your machine, whether from the client:

curl https://domain:443/
Enter fullscreen mode Exit fullscreen mode

Or the server:

res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from HTTPS server!\n');
Enter fullscreen mode Exit fullscreen mode

You’ve lost control. That packet is now at the mercy of ISPs, data centers, and the network until it reaches the other side. A lot can go wrong in that space between.

TLS exists to make sure none of that “wrong” compromises your data.

It encrypts the packet in a way only the sender and receiver can understand. Even if someone intercepts it, they can’t read it. If your packet is raw text, that’s a grenade. TLS turns it into alien gibberish for anyone else.

Custom TLS is the backbone of internal systems(distributed systems), pipelines, IoT and tools in backend(e.g mTLS).

In this guide, we’ll build a simple HTTPS (TLS-enabled) server using Node.js. The concept, though, is universal.


TLS: Secret Language Between Machines

TLS lets two machines on the internet create a private language; one only they can understand.

You’ve probably seen that little “handshake” message in your browser when visiting a secure site. That’s not metaphor; it’s real. The client and server are negotiating a shared language, like:

You (Client)                            Server
-----------                             -------
"Hi! I speak English, Spanish,
and Klingon."     ——→      "Cool, I’m Acme Corp. Here’s my passport 
                            (a certificate from a trusted authority).
                            Let’s talk in Klingon (TLS 1.3)."

"Alright, passport checks out. 
Let’s pick a secret code 
and chat securely."   ←——     "Agreed. Cipher locked in."

From now on, every message is encrypted.
Enter fullscreen mode Exit fullscreen mode

Now; where do you get these certificates?

Usually, when you buy a domain, your registrar resells a certificate. But for local testing or internal services, we can generate our own using things like OpenSSL.


Step 1: Self-Signed Certs with OpenSSL

Let’s roll our own TLS certificates.

Create a working folder:

mkdir certs && touch server.js
Enter fullscreen mode Exit fullscreen mode

Inside certs, we’ll generate both a Certification Authority (CA) and a server certificate signed by it.

Create a Certification Authority (CA)

openssl genrsa -out ca-key.pem 4096

openssl req -x509 -new -nodes -key ca-key.pem -days 3650 -out ca-cert.pem \
  -subj "/C=US/ST=Test/L=Test/O=Test CA/OU=Dev/CN=MyTestCA"
Enter fullscreen mode Exit fullscreen mode

This makes a pseudo-government; our own trusted root called MyTestCA.

Create the Server Certificate

# 1. Generate private key
openssl genrsa -out server-key.pem 4096

# 2. Create a signing request
openssl req -new -key server-key.pem -out server.csr \
  -subj "/C=US/ST=Test/L=Test/O=MyApp/OU=Servers/CN=bunny.local"

# 3. Self-sign with your own CA
openssl x509 -req -in server.csr -CA ca-cert.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-cert.pem -days 365
Enter fullscreen mode Exit fullscreen mode

bunny.local is our mock domain name.


Step 2: Spin Up an HTTPS Server

Now for the fun part serving over TLS.

server.js

import https from "node:https"
import path from "node:path"
import fs from "node:fs"

const PORT = 443
const CERT_DIR = path.resolve(process.cwd(), 'certs')

const tlscert = {
  key: fs.readFileSync(path.join(CERT_DIR, 'server-key.pem')),
  cert: fs.readFileSync(path.join(CERT_DIR, 'server-cert.pem')),
}

const server = https.createServer(tlscert, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello from HTTPS server!\n')
})

server.listen(PORT, () => console.log(`Listening on port ${PORT}`))
Enter fullscreen mode Exit fullscreen mode

Key point: you're using Node’s built-in https module instead of http. Pass in the TLS certs on creation.


Step 3: Test It

Run your server:

node server.js
Enter fullscreen mode Exit fullscreen mode

Then hit it:

curl https://localhost:443/
Enter fullscreen mode Exit fullscreen mode

You’ll get this error:

curl: (60) schannel: SEC_E_UNTRUSTED_ROOT - The certificate chain was issued by an authority that is not trusted.
Enter fullscreen mode Exit fullscreen mode

Which makes sense; your OS doesn’t know who MyTestCA is.

Skip verification with:

curl -k https://localhost:443/
Enter fullscreen mode Exit fullscreen mode

-k tells curl to accept self-signed certs.


Now you’ve got a fully functioning HTTPS server; with a language only you and your clients can speak.

Secure, private, tight.

Welcome to the encrypted internet.

Ever wondered what it really takes to build low-level Node.js tooling or distributed systems from scratch(which this post in an excerpt from)?

  • Learn raw TCP
  • Go over a message broker in pure JavaScript
  • Go from "can code" to "can engineer"

Check out: How to Go from a 6.5 to an 8.5 Developer

Or maybe you're ready to master the dark art of authentication?

  • From salting and peppering to mock auth libraries
  • Understand tokens, sessions, and identity probes
  • Confidently use (or ditch) auth-as-a-service or roll out your own?

Grab: The Authentication Handbook: With Node.js Examples

thanks for reading. 🙏

Top comments (1)

Collapse
 
sfundomhlungu profile image
Sk

Other resources for certificates

let's encrypt

Let's Encrypt

Let's Encrypt is a free, automated, and open Certificate Authority brought to you by the nonprofit Internet Security Research Group (ISRG). Read all about our nonprofit work this year in our 2024 Annual Report.

letsencrypt.org

Make cert(need golang installed)

GitHub logo FiloSottile / mkcert

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

mkcert

mkcert is a simple tool for making locally-trusted development certificates. It requires no configuration.

$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊

$ mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1

Created a new certificate valid for the following names 📜
 - "example.com"
 - "*.example.com"
 - "example.test"
 - "localhost"
 - "127.0.0.1"
 - "::1"

The certificate is at "./example.com+5.pem" and the key at "./example.com+5-key.pem" ✅

Chrome and Firefox screenshot

Using certificates from real certificate authorities (CAs) for development can be dangerous or impossible (for hosts like example.test, localhost or 127.0.0.1), but self-signed certificates cause trust errors. Managing your own CA is the best solution, but usually involves arcane commands, specialized knowledge and manual steps.

mkcert automatically creates and installs a local CA in the system…