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/
Or the server:
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from HTTPS server!\n');
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.
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
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"
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
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}`))
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
Then hit it:
curl https://localhost:443/
You’ll get this error:
curl: (60) schannel: SEC_E_UNTRUSTED_ROOT - The certificate chain was issued by an authority that is not trusted.
Which makes sense; your OS doesn’t know who MyTestCA
is.
Skip verification with:
curl -k https://localhost:443/
-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)
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.
Make cert(need golang installed)
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.
Using certificates from real certificate authorities (CAs) for development can be dangerous or impossible (for hosts like
example.test
,localhost
or127.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…