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.
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 →