HTTPS & TLS

Serving Encrypted Traffic — and Why You Usually Don't

HTTPS & TLS

Node can serve HTTPS directly, but in production a reverse proxy usually terminates TLS.

3 min read Level 2/5 #nodejs#https#tls
What you'll learn
  • Start an HTTPS server with cert + key
  • Know what "TLS termination at the edge" means
  • Pick the right approach

node:https mirrors node:http exactly — same API, plus a TLS config. Real production usually puts a reverse proxy in front, but you should know how it works.

Direct HTTPS Server

import { createServer } from "node:https";
import { readFileSync } from "node:fs";

const options = {
  key:  readFileSync("./certs/key.pem"),
  cert: readFileSync("./certs/cert.pem"),
};

createServer(options, (req, res) => {
  res.end("secure hello");
}).listen(443);

Generate dev certs with mkcert:

brew install mkcert
mkcert -install
mkcert localhost
# produces localhost.pem (cert) and localhost-key.pem

Why You Usually Don’t

In production, you put Nginx / Caddy / Cloudflare / your cloud load balancer in front of Node:

client ──HTTPS──▶ Nginx (TLS termination) ──HTTP──▶ Node

The proxy handles:

  • TLS (with auto-renewing Let’s Encrypt certs)
  • HTTP/2 & HTTP/3
  • Compression (gzip / brotli)
  • Static files
  • Maybe a CDN cache

Your Node process speaks plain HTTP to the proxy on localhost. Simpler, faster, and you don’t reinvent cert renewal.

”Forwarded” Headers

The proxy sets headers telling you the original request was HTTPS:

req.headers["x-forwarded-proto"];   // 'https'
req.headers["x-forwarded-for"];     // client IP

If your app needs the real protocol or IP (for logging, redirects), read these — but only trust them when you control the proxy.

When To Use Node-Direct HTTPS

  • Local dev with https://localhost (needed for some APIs)
  • Edge cases — IoT devices, embedded scenarios
  • Quick prototypes

For production web traffic: terminate TLS at the edge.

Serving Static Files →