Skip to content
Ravi Agheda
← All writing

— 1 min read

Nginx and SSL

Put Nginx in front of a Node.js app as a reverse proxy and terminate SSL with a free Let's Encrypt certificate.

  • Nginx
  • Node.js
  • DevOps

A Node app can serve HTTPS directly, but in practice it's almost always better to let Nginx sit in front of it: terminate SSL, handle gzip and static files, and reverse-proxy everything else to your app process.

1. Install Nginx and Certbot

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

2. Reverse-proxy config

Create /etc/nginx/sites-available/app.conf:

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
    }
}

The Upgrade/Connection headers matter if your app uses WebSockets — without them, Nginx won't proxy the upgrade handshake correctly.

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

3. Get a certificate

sudo certbot --nginx -d example.com -d www.example.com

Certbot edits the same config file to add the listen 443 ssl block, the certificate paths, and an HTTP → HTTPS redirect — no manual cert wiring needed. It also sets up a renewal timer, so certificates (valid 90 days) renew themselves; you can confirm with:

sudo certbot renew --dry-run

Notes

  • Keep your Node app bound to 127.0.0.1, not 0.0.0.0 — it should only be reachable through Nginx, not directly on its port.
  • If you're running multiple apps on one box, give each its own server {} block keyed by server_name, and a distinct upstream port.
  • Add client_max_body_size in the server block if your app accepts file uploads larger than Nginx's 1MB default.