Chapter 23: Node.js HTTPS Module
1. What is the https module and how is it different from http?
node:https is almost identical to node:http, but it adds TLS/SSL encryption.
| Feature | node:http | node:https |
|---|---|---|
| Protocol | HTTP (unencrypted) | HTTPS (encrypted via TLS/SSL) |
| Default port | 80 | 443 |
| Security | No encryption – data is readable | Encrypted – protects data in transit |
| Certificate required | No | Yes (server certificate + private key) |
| Client certificate auth | Not applicable | Possible (mutual TLS) |
| Performance overhead | Lower | Slightly higher (TLS handshake) |
| Modern usage | Development, internal APIs | Almost all production APIs |
Key takeaway 2026: You almost never run a public-facing server with plain http anymore — HTTPS is the default expectation (browsers show warnings, SEO penalty, security compliance).
2. Creating the most basic HTTPS server
You need at least two files:
- Private key (.key)
- Certificate (.crt / .pem)
For real learning & development, the easiest way is to use self-signed certificates.
|
0 1 2 3 4 5 6 7 8 9 10 11 |
# 1. Generate self-signed certificate (run once) openssl req -x509 -newkey rsa:4096 -nodes \ -keyout server.key \ -out server.crt \ -days 365 \ -subj "/CN=localhost" |
This creates:
- server.key → private key
- server.crt → public certificate
Now the code:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// server-https-basic.js import { createServer } from 'node:https'; import { readFileSync } from 'node:fs'; const options = { key: readFileSync('server.key'), cert: readFileSync('server.crt') }; const server = createServer(options, (req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`Hello from secure HTTPS server!\n`); }); const PORT = 3443; // common choice for local HTTPS server.listen(PORT, () => { console.log(`HTTPS server running → https://localhost:${PORT}`); console.log('→ Warning: self-signed certificate → browser will show warning'); }); |
Run:
|
0 1 2 3 4 5 6 |
node server-https-basic.js |
Open browser → https://localhost:3443
You will see a security warning because the certificate is self-signed.
Click “Advanced” → “Proceed to localhost (unsafe)” to continue (only for learning).
3. Using a real certificate in development (recommended way 2026)
Instead of self-signed certificates that trigger warnings, most developers use mkcert — a zero-config tool that creates locally trusted certificates.
Install mkcert (one-time):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# macOS brew install mkcert # Ubuntu / Debian sudo apt install libnss3-tools # Windows (chocolatey) choco install mkcert # Initialize mkcert (one-time) mkcert -install |
Then generate certificate for localhost:
|
0 1 2 3 4 5 6 |
mkcert localhost 127.0.0.1 ::1 |
→ creates localhost+2.pem and localhost+2-key.pem
Now update the code:
|
0 1 2 3 4 5 6 7 8 9 |
const options = { key: readFileSync('localhost+2-key.pem'), cert: readFileSync('localhost+2.pem') }; |
→ Browser will show green padlock → no warnings
4. Realistic example – JSON API over HTTPS
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// server-https-api.js import { createServer } from 'node:https'; import { readFileSync } from 'node:fs'; const options = { key: readFileSync('localhost+2-key.pem'), cert: readFileSync('localhost+2.pem') }; const server = createServer(options, (req, res) => { const url = req.url; const method = req.method; res.setHeader('Content-Type', 'application/json'); if (url === '/' && method === 'GET') { res.statusCode = 200; res.end(JSON.stringify({ message: 'Secure HTTPS API is running', secure: req.socket.encrypted === true, timestamp: new Date().toISOString() })); return; } if (url === '/users' && method === 'GET') { res.statusCode = 200; res.end(JSON.stringify([ { id: 1, name: 'Aman', role: 'admin' }, { id: 2, name: 'Priya', role: 'user' } ])); return; } // Handle POST with body (manual parsing) if (url === '/users' && method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { try { const data = JSON.parse(body); res.statusCode = 201; res.end(JSON.stringify({ success: true, message: 'User created', received: data })); } catch (err) { res.statusCode = 400; res.end(JSON.stringify({ error: 'Invalid JSON' })); } }); return; } res.statusCode = 404; res.end(JSON.stringify({ error: 'Not found' })); }); server.listen(3443, () => { console.log('Secure API → https://localhost:3443'); }); |
Try with curl (note the -k flag for self-signed, or remove if using mkcert):
|
0 1 2 3 4 5 6 7 8 9 10 |
curl -k https://localhost:3443/users curl -k -X POST https://localhost:3443/users \ -H "Content-Type: application/json" \ -d '{"name":"Rahul","email":"rahul@example.com"}' |
5. Making outgoing HTTPS requests (client side)
Modern way (Node.js 18+):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function fetchSecureData() { try { const response = await fetch('https://api.github.com/users/webliance'); const data = await response.json(); console.log('GitHub user:', data.login, data.public_repos, 'repos'); } catch (err) { console.error('Fetch failed:', err.message); } } fetchSecureData(); |
Classic way (still useful for more control):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import https from 'node:https'; const options = { hostname: 'api.github.com', path: '/users/webliance', method: 'GET', headers: { 'User-Agent': 'Node.js Learning' } }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log('Status:', res.statusCode); console.log('Response:', JSON.parse(data)); }); }); req.on('error', (err) => { console.error('Request error:', err.message); }); req.end(); |
6. Important security & production considerations
| Topic | Recommendation 2025–2026 | Why it matters |
|---|---|---|
| Self-signed certificates | Only for local development | Browsers & clients reject them |
| Real certificates | Use Let’s Encrypt (free), ZeroSSL, or paid (Cloudflare, AWS ACM) | Required for public HTTPS |
| HTTP → HTTPS redirect | Always redirect 80 → 443 | Security & SEO |
| HSTS header | Add Strict-Transport-Security header | Forces browser to always use HTTPS |
| Minimum TLS version | TLS 1.3 (or at least 1.2) | Old versions are insecure |
| Cipher suites | Use secure defaults or Mozilla’s intermediate config | Protects against known attacks |
| Client certificate auth | Use when needed (mutual TLS) | Strong authentication |
Very common production setup (not raw https):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Redirect HTTP → HTTPS import { createServer as createHttpServer } from 'node:http'; import { createServer as createHttpsServer } from 'node:https'; // ... https server code ... // Simple HTTP redirect server createHttpServer((req, res) => { res.writeHead(301, { Location: `https://{req.headers.host}${req.url}` }); res.end(); }).listen(80); |
Summary – Quick comparison & decision guide
| Situation | Use this in 2026 | Why? |
|---|---|---|
| Local development / learning | https + mkcert | Trusted locally, no warnings |
| Public API / website | Never raw https — use Fastify / Express / Hono | Middleware, routing, error handling, security |
| Very high-performance microservice | Raw https + careful tuning | Minimal overhead |
| Outgoing requests | fetch (built-in) | Clean, modern, promise-based |
| Need full control (headers, agents) | https.request | Fine-grained control |
Which direction would you like to go deeper next?
- Mutual TLS (client certificate authentication)
- Streaming large files over HTTPS
- HTTP/2 vs HTTP/1.1 in Node.js
- TLS configuration for maximum security (ciphers, protocols)
- Full production setup with redirect + HSTS + secure headers
Just tell me what interests you most — I’ll continue with detailed, copy-paste-ready examples. 😊
