10 Feb 2019

How to Use SSL/TLS with Node.js

Introduction

This covers implementing SSL/TLS in Express.js using Let’s Encrypt certificates — both server-side HTTPS setup and hardening the connection further.

Basic HTTPS setup with Node.js

Node.js provides a built-in https module for secure communication:

const https = require("https"),
  fs = require("fs");

const options = {
  key: fs.readFileSync("/opt/ssl/example.com/example.com-key.pem"),
  cert: fs.readFileSync("/opt/ssl/example.com/example.com-cert-chain.pem")
};

const app = express();

app.use((req, res) => {
  res.writeHead(200);
  res.end("hello world\n");
});

app.listen(8000);

https.createServer(options, app).listen(8080);

The certificate and key files need to come from a trusted certificate authority.

Generating certificates

Understanding SSL certificates

A certificate signed by a trusted authority verifies the server’s identity — the green lock icon in the browser indicates a secure, authenticated connection. Self-signed certificates are technically possible, but browsers warn users away from them.

Using Certbot with Let’s Encrypt

Let’s Encrypt provides free, instantly validated certificates, and Certbot is the recommended tool for managing them.

Install (macOS):

brew install certbot

Webroot plugin method

certbot certonly --webroot -w /var/www/example/ -d www.example.com -d example.com

This requires:

  • Running on the production server where the domain resolves.
  • Sudo privileges (it writes to /var/log/letsencrypt).
  • A valid email address for renewal notifications.

Let’s Encrypt certificates expire every three months, but renewal is easy to automate via cron.

Updated config:

const options = {
  key: fs.readFileSync("/var/www/example/sslcert/privkey.pem"),
  cert: fs.readFileSync("/var/www/example/sslcert/fullchain.pem")
};

Security hardening

HSTS (HTTP Strict Transport Security)

HSTS prevents protocol downgrade attacks and cookie hijacking by forcing all traffic through HTTPS. Using the Helmet middleware:

npm install --save helmet
const https = require("https"),
  fs = require("fs"),
  helmet = require("helmet");

const options = {
  key: fs.readFileSync("/srv/www/keys/my-site-key.pem"),
  cert: fs.readFileSync("/srv/www/keys/chain.pem")
};

const app = express();

app.use(helmet());

app.use((req, res) => {
  res.writeHead(200);
  res.end("hello world\n");
});

app.listen(8000);

https.createServer(options, app).listen(8080);

Strong Diffie-Hellman parameters

The default Diffie-Hellman key exchange uses smaller keys than the certificate itself. Generate stronger parameters:

openssl dhparam -out /var/www/example/sslcert/dh-strong.pem 2048
const options = {
  key: fs.readFileSync("/var/www/example/sslcert/privkey.pem"),
  cert: fs.readFileSync("/var/www/example/sslcert/fullchain.pem"),
  dhparam: fs.readFileSync("/var/www/example/sslcert/dh-strong.pem")
};

Conclusion

Modern web development requires HTTPS. Node.js gives you multiple ways to wire up SSL/TLS — serving your own site securely, making encrypted requests to external servers, and managing certificates end to end.