NodeJSCreating an HTTPS Server

Creating an HTTPS Server

HTTPS is HTTP over TLS — the encryption layer that makes the web secure. It guarantees three things: confidentiality (eavesdroppers can't read the traffic), integrity (it can't be tampered with in transit), and authentication (the client verifies it's really talking to your server). Node's https module mirrors http almost exactly; the only real addition is supplying a certificate and private key. This page covers running TLS in Node and when you should — and shouldn't.

The minimal HTTPS server

JS
import { createServer } from 'node:https'
import { readFileSync } from 'node:fs'

const options = {
  key:  readFileSync('./certs/key.pem'),    // private key
  cert: readFileSync('./certs/cert.pem'),   // certificate
}

createServer(options, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Secure hello over TLS\n')
}).listen(443, () => console.log('https://localhost'))
`https` is `http` plus a key/cert — the request handling is identical
Everything you learned about [`req`/`res`](/nodejs/request-response), [routing](/nodejs/routing-basics), headers, and status codes applies unchanged. The *only* difference from `http.createServer` is passing the `options` object with `key` and `cert` as its first argument. Under the hood, Node performs the TLS handshake, then hands you the same decrypted HTTP stream.
Certificates and keys, briefly

Piece

What it is

Private key

Secret key that decrypts traffic — never share it

Certificate

Public document binding your domain to a public key

CA (Certificate Authority)

Trusted issuer whose signature browsers trust

Self-signed cert

You sign your own — fine for local dev, untrusted by browsers

CA-signed cert

Signed by a trusted CA — required for production

The private key is a secret — protect it like a password
If your private key leaks, an attacker can impersonate your server and decrypt intercepted traffic. Never commit it to git, never log it, restrict file permissions (`chmod 600`), and load it from a secrets manager or protected file in production — not from the repo. Rotate it if you suspect exposure.
Local development certificates

For local testing you can generate a self-signed certificate. Browsers will warn (it's not CA-signed), which is expected on localhost:

Bash
# Quick self-signed cert (OpenSSL):
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365

# Better DX: mkcert creates a locally-trusted cert (no browser warning)
mkcert -install
mkcert localhost
Use `mkcert` for a friction-free local HTTPS experience
Plain self-signed certs trigger browser warnings and break tools that verify TLS. `mkcert` installs a local CA into your system trust store and issues certs signed by it — so `localhost` HTTPS "just works" without warnings, only on your machine. It's the standard tool for local TLS development.
Production reality: terminate TLS upstream
In most deployments, Node should NOT handle TLS directly
Running TLS in your Node process means managing certificates, renewals, cipher configuration, and OCSP stapling in application code — and it costs CPU. The common production pattern is **TLS termination** at a reverse proxy (nginx, Caddy), a load balancer, or a cloud ingress: it handles HTTPS to the world and forwards plain `http` to your Node app on a private network. Caddy and managed platforms even auto-provision and renew Let's Encrypt certs. Reach for Node's `https` module only when you specifically need end-to-end TLS to the process.
Get certificates free with Let's Encrypt

For real domains, Let's Encrypt issues trusted CA-signed certificates at no cost, automated via the ACME protocol. Tools like certbot or a proxy with built-in ACME (Caddy, Traefik) obtain and auto-renew them — important because Let's Encrypt certs are valid for only 90 days.

Automate renewal — expired certificates take your site down
A certificate that expires makes every browser reject your site with a scary security error until you renew. Manual renewal *will* eventually be forgotten. Use automated renewal (certbot timers, or a proxy that renews on its own) and monitor expiry. An expired cert is one of the most common — and entirely avoidable — production outages.
Redirect HTTP to HTTPS

Run a tiny plain-HTTP server alongside HTTPS that 301-redirects everything to the secure URL, so visitors on http:// land on https://:

JS
import http from 'node:http'

http.createServer((req, res) => {
  const host = req.headers.host?.split(':')[0]
  res.writeHead(301, { Location: `https://${host}${req.url}` })
  res.end()
}).listen(80)
Add HSTS to enforce HTTPS on future visits
Pair the redirect with the `Strict-Transport-Security` header on your HTTPS responses (e.g. `max-age=31536000; includeSubDomains`). It instructs browsers to use HTTPS automatically for your domain going forward — even if a user types `http://` — closing the brief window where the initial plaintext request could be intercepted.
Key takeaways
  • https.createServer(options, handler) = http + a key/cert — handling is otherwise identical.

  • Protect the private key like a credential; never commit it.

  • Local dev: use mkcert for trusted, warning-free certs.

  • Production: usually terminate TLS at a proxy/load balancer; use Let's Encrypt with automated renewal.

  • Redirect HTTP→HTTPS and send HSTS to enforce secure connections.

Next
Move from raw Node to the framework that streamlines all of this: [Introduction to Express.js](/nodejs/express-intro).