DEV Community

Cover image for How to Set Up Nginx Reverse Proxy with SSL (Let's Encrypt)
Raizan
Raizan

Posted on • Originally published at chasebot.online

How to Set Up Nginx Reverse Proxy with SSL (Let's Encrypt)

What You'll Need

  • n8n Cloud or self-hosted n8n (optional, for API monitoring)
  • Hetzner VPS or Contabo VPS for your server
  • Namecheap for domain registration
  • DigitalOcean as an alternative hosting option
  • SSH access to your Linux server (Ubuntu 20.04 or later preferred)
  • A domain name pointing to your server's IP address
  • Basic command-line knowledge

Table of Contents

  1. Understanding Nginx Reverse Proxy Architecture
  2. Installing Nginx and Certbot
  3. Configuring Your First Reverse Proxy
  4. Setting Up SSL with Let's Encrypt
  5. Securing Your Configuration
  6. Getting Started

Understanding Nginx Reverse Proxy Architecture

I've been managing production servers for years, and one thing I learned early: a reverse proxy is your infrastructure's best friend. Unlike a forward proxy (which shields clients), a reverse proxy sits between your internet-facing traffic and your internal applications. This setup gives you load balancing, SSL termination, security hardening, and the ability to run multiple services behind a single domain.

Here's why this matters: if you're running multiple backend services—say, a Node.js API on port 3000 and a Python Flask app on port 5000—a reverse proxy lets clients connect to both via api.example.com and app.example.com without exposing internal ports. Add Let's Encrypt SSL, and you've got enterprise-grade encryption for free.

This is especially useful if you're building automation workflows. For example, if you're running a self-hosted n8n Cloud instance or managing automation workflows that replace expensive SaaS tools, you'll want that traffic encrypted end-to-end.


Installing Nginx and Certbot

First, SSH into your server. I'm assuming you've already provisioned a Hetzner VPS or Contabo VPS with Ubuntu 20.04 or later. Your domain should already be pointing to your server's IP address (if not, update your DNS records at Namecheap).

Update your package manager and install Nginx:

sudo apt update
sudo apt upgrade -y
sudo apt install -y nginx
Enter fullscreen mode Exit fullscreen mode

Start Nginx and enable it to run on boot:

sudo systemctl start nginx
sudo systemctl enable nginx
Enter fullscreen mode Exit fullscreen mode

Check that Nginx is running:

sudo systemctl status nginx
Enter fullscreen mode Exit fullscreen mode

You should see active (running) in the output. Now install Certbot, the Let's Encrypt client, and the Nginx plugin:

sudo apt install -y certbot python3-certbot-nginx
Enter fullscreen mode Exit fullscreen mode

Verify the installation:

certbot --version
Enter fullscreen mode Exit fullscreen mode

Great. Now we're ready to configure.


Configuring Your First Reverse Proxy

Let's say you have a backend service running on localhost:3000 (maybe a Node.js app, a Python service, or anything HTTP). You want to expose it securely at api.example.com.

Open the Nginx configuration directory:

sudo nano /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

Replace the entire file with this configuration:

upstream backend_service {
    server localhost:3000;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name api.example.com;

    location / {
        proxy_pass http://backend_service;
        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_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let me break this down:

  • upstream backend_service: Defines where traffic goes. Point this to your actual backend port.
  • listen 80: Listen on HTTP (Certbot will upgrade this to HTTPS).
  • server_name api.example.com: Your domain. Change this to your actual domain.
  • proxy_pass: Routes requests to your backend service.
  • proxy_set_header directives: Preserve client information (IP, protocol, domain) so your backend app sees the real client.

Test the configuration syntax:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

If you see syntax is ok, reload Nginx:

sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

If you visit http://api.example.com in your browser right now, it should proxy to your backend service on port 3000. The connection is unencrypted, though—that's next.

💡 Fast-Track Your Project: Don't want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-DEVTO.


Setting Up SSL with Let's Encrypt

This is where the magic happens. Certbot automates the entire SSL setup, including renewal. Run Certbot with the Nginx plugin:

sudo certbot --nginx -d api.example.com
Enter fullscreen mode Exit fullscreen mode

Certbot will ask a few questions:

  1. Enter your email address (for renewal reminders).
  2. Accept the Let's Encrypt terms of service.
  3. Optionally share your email with the EFF (their choice).

Choose "2" when asked about redirecting HTTP to HTTPS:

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, unless you have a specific reason otherwise.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Enter fullscreen mode Exit fullscreen mode

Certbot automatically modifies your Nginx config to enable HTTPS and redirect HTTP traffic. Here's what your config now looks like:

sudo cat /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

You'll see something like this (Certbot added the HTTPS server block and modified the HTTP block):

upstream backend_service {
    server localhost:3000;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://backend_service;
        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_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}
Enter fullscreen mode Exit fullscreen mode

Test the syntax:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

Reload Nginx:

sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

Now visit https://api.example.com. Your connection is encrypted with a valid SSL certificate—and it's completely free. Your certificate is valid for 90 days. Certbot automatically renews it before expiration via a system timer.

Verify the renewal timer is active:

sudo systemctl status certbot.timer
Enter fullscreen mode Exit fullscreen mode

You should see active in the output.


Securing Your Configuration

A reverse proxy without proper security is like leaving your front door unlocked. Let's harden the setup.

1. Add Security Headers

Edit your Nginx config:

sudo nano /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

Add these directives inside the HTTPS server block (after ssl_dhparam):

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Enter fullscreen mode Exit fullscreen mode

These headers prevent clickjacking, MIME sniffing, and enforce HTTPS everywhere.

2. Limit Request Rate

If you're concerned about DDoS or brute-force attacks, add rate limiting. Insert this outside any server block (near the top of the file):

limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
Enter fullscreen mode Exit fullscreen mode

Then add this inside the HTTPS server block:

limit_req zone=general burst=20 nodelay;
Enter fullscreen mode Exit fullscreen mode

3. Restrict File Access

Add this inside the HTTPS server block to block sensitive files:

location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

location ~ ~$ {
    deny all;
    access_log off;
    log_not_found off;
}
Enter fullscreen mode Exit fullscreen mode

4. Enable Gzip Compression

Outside any server block, add:

gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
gzip_disable "MSIE [1-6]\.";
Enter fullscreen mode Exit fullscreen mode

Test and reload:

sudo nginx -t
sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

Advanced: Multiple Backend Services

If you're running multiple services (say, an API and a web dashboard), create separate upstream blocks and location directives. Here's an example:

sudo nano /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode
upstream api_backend {
    server localhost:3000;
    keepalive 32;
}

upstream app_backend {
    server localhost:5000;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name api.example.com app.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live
Enter fullscreen mode Exit fullscreen mode

Originally published on Automation Insider.

Top comments (0)