DEV Community

Cover image for Configuring Nginx Reverse Proxy with Certbot SSL
Raizan
Raizan

Posted on • Originally published at chasebot.online

Configuring Nginx Reverse Proxy with Certbot SSL

What You'll Need

Table of Contents

Understanding Reverse Proxies and SSL Termination

When I build backend infrastructure, I rarely expose application servers directly to the public internet. Running Node.js, Python, Go, or Java applications directly on public ports like 3000, 5000, or 8080 leaves systems vulnerable to slowloris attacks, unoptimized static file delivery, and messy SSL certificate management.

A reverse proxy sits between the public internet and your internal network services. It receives incoming HTTP and HTTPS requests from clients, evaluates the incoming headers, decrypts the TLS layer, and passes sanitized requests to your underlying microservices over local loopback interfaces or private networks.

                  +-------------------------------------------------+
                  |                   VPS Host                      |
                  |                                                 |
[ Client ] ---->  | [ Nginx (Port 80/443) ]                         |
 (HTTPS)          |         |                                       |
                  |         v (Internal HTTP / Unix Socket)         |
                  | [ App Backend (Port 3000 / 5000 / Socket) ]      |
                  +-------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

SSL termination is the process where the proxy server decrypts incoming HTTPS traffic before sending it to local backend services. This offloads cryptographic calculations from application workers, centralizing certificate renewal and cipher maintenance.

When managing server automation or scheduling Python scripts for automated tasks, running individual application runtimes on exposed ports creates security vulnerabilities. Furthermore, when handling data-heavy enterprise stacks like setting up PostgreSQL connection pooling with PgBouncer, placing an Nginx proxy in front of administrative web dashboards or REST endpoints isolates database management behind HTTPS encryption and authentication boundaries.

Installing and Configuring Nginx as a Reverse Proxy

To begin, provision a virtual private server on Hetzner VPS or DigitalOcean. SSH into your newly created Linux server running Ubuntu 22.04 or 24.04 LTS as a user with root access.

First, update the package repository index and install Nginx:

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

Verify that Nginx is active and running on your system:

sudo systemctl status nginx
Enter fullscreen mode Exit fullscreen mode

Ensure your domain name (for instance, app.example.com) points to your server IP address. You can update your A record through your domain registrar, such as Namecheap.

Next, create an application server running locally. For this guide, assume an application service is listening on local port 3000 (127.0.0.1:3000).

Now, remove the default Nginx configuration file to clean up your web server environment:

sudo rm /etc/nginx/sites-enabled/default
Enter fullscreen mode Exit fullscreen mode

Create a dedicated site configuration file for your application domain inside /etc/nginx/sites-available/app.example.com:

sudo nano /etc/nginx/sites-available/app.example.com
Enter fullscreen mode Exit fullscreen mode

Paste the following full configuration into the file. Replace app.example.com with your exact domain name:

upstream backend_app {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

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

    access_log /var/log/nginx/app.example.com.access.log;
    error_log /var/log/nginx/app.example.com.error.log;

    location / {
        proxy_pass http://backend_app;
        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_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffering off;
    }
}
Enter fullscreen mode Exit fullscreen mode

Enable this site configuration by linking it from sites-available into sites-enabled:

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
Enter fullscreen mode Exit fullscreen mode

Validate your Nginx configuration syntax to ensure there are no missing semicolons or structural errors:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

If the syntax test passes, reload Nginx to apply the new proxy rule:

sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

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

Obtaining Free Let's Encrypt SSL Certificates with Certbot

With Nginx routing traffic on HTTP port 80, the next step is securing communications using TLS/SSL certificates issued by Let's Encrypt. Certbot handles certificate issuance, verification challenges, and Nginx configuration updating automatically.

Install Certbot along with the official Nginx integration plugin using the standard apt package manager:

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

Before running Certbot, verify that your firewall allows traffic on both HTTP (port 80) and HTTPS (port 443). If you are using UFW (Uncomplicated Firewall), allow the Nginx Full profile:

sudo ufw allow 'Nginx Full'
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

Execute Certbot with the Nginx plugin targeting your domain name:

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

Certbot will prompt you for an administrator email address for emergency renewal and security notices. Agree to the Let's Encrypt Terms of Service. Certbot will then initiate an ACME HTTP-01 challenge.

During the HTTP-01 challenge process, Let's Encrypt issues a cryptographically signed token. Certbot creates a temporary file in your server filesystem containing that token. Let's Encrypt validation servers make an automated HTTP request to http://app.example.com/.well-known/acme-challenge/<TOKEN>. Once confirmed, Let's Encrypt issues your TLS certificate files to /etc/letsencrypt/live/app.example.com/.

Certbot automatically modifies your /etc/nginx/sites-available/app.example.com file to inject SSL directives and build an automatic HTTP to HTTPS redirect rule.

Let's Encrypt certificates remain valid for 90 days. Certbot configures a systemd timer to renew certificates before expiration automatically. Verify that the renewal systemd timer is active:

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

Test the automated certificate renewal process using a dry run execution:

sudo certbot renew --dry-run
Enter fullscreen mode Exit fullscreen mode

If the dry run finishes without errors, your automated certificate renewal pipeline is fully functional.

Securing and Hardening Your SSL Nginx Setup

The default configuration generated by Certbot provides baseline security, but production systems require explicit hardening. Advanced architectures, such as systems created when learning how to build distributed web scraping pipelines, generate high volumes of concurrent network traffic. Hardening Nginx minimizes memory consumption, prevents protocol downgrade vulnerabilities, and protects header metadata.

First, generate a custom 2048-bit Diffie-Hellman parameter group to secure Key Exchange mechanisms:

sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Enter fullscreen mode Exit fullscreen mode

Now open your site configuration file to rewrite it into a hardened, production-ready reverse proxy configuration:

sudo nano /etc/nginx/sites-available/app.example.com
Enter fullscreen mode Exit fullscreen mode

Replace the complete contents of the file with the optimized script below:

upstream backend_app {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

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

    return 301 https://$host$request_uri;
}

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

    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/nginx/dhparam.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" 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 "no-referrer-when-downgrade" always;

    access_log /var/log/nginx/app.example.com.access.log;
    error_log /var/log/nginx/app.example.com.error.log;

    client_max_body_size 16M;

    location / {
        proxy_pass http://backend_app;
        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_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        proxy_buffers 8 16k;
        proxy_buffer_size 16k;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is what each hardening directive achieves:

  • http2: Enables the HTTP/2 protocol, reducing latency via stream multiplexing.
  • ssl_protocols TLSv1.2 TLSv1.3: Explicitly disables legacy, vulnerable TLS 1.0 and 1.1 protocol versions.
  • Strict-Transport-Security: Forces web browsers to interact with the host strictly through HTTPS for two years (max-age=63072000).
  • X-Frame-Options SAMEORIGIN: Disallows third-party websites from embedding your web application within standard HTML frames or iframes, mitigating clickjacking attacks.
  • proxy_buffers: Allocates response buffering parameters so large response payloads do not choke backend worker threads.

Verify the configuration syntax and reload Nginx:

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

Troubleshooting Common Nginx and Certbot Errors

Even straightforward proxy configurations can throw errors due to permission conflicts, blocked ports, or application runtime issues. Below are diagnostic workflows for solving common setup problems.

1. Fixing 502 Bad Gateway Errors

A 502 Bad Gateway error indicates Nginx received traffic successfully on ports 80/443, but could not connect to the upstream backend service mapped in the proxy_pass directive.

Check if your internal backend application service is listening on port 3000:

sudo ss -tulpn | grep 3000
Enter fullscreen mode Exit fullscreen mode

If the command output is blank, your backend application is offline or listening on a different port. Start your application runtime or check its service status.

If your application listens on a local Unix domain socket instead of a TCP port, inspect file system ownership permissions:

ls -la /run/backend_app.sock
Enter fullscreen mode Exit fullscreen mode

The Nginx worker process runs under the www-data user account in Ubuntu environments. Ensure www-data has full read/write access to your upstream socket file.

2. Resolving Certbot Challenge Failures

If Certbot fails during the HTTP-01 challenge process with an Unauthorized or Connection refused error message, check the following issues:

  1. DNS Propagation Failures: Ensure your DNS A record matches your host public IP address precisely. Run dig app.example.com +short to confirm DNS lookup resolution.
  2. Firewall Port Blocking: Verify port 80 is accessible publicly. Test connection response using curl:
curl -I http://app.example.com/.well-known/acme-challenge/test
Enter fullscreen mode Exit fullscreen mode
  1. Conflicting Global Nginx Server Blocks: Ensure no other site config in /etc/nginx/sites-enabled/ captures generic port 80 traffic using the default_server directive.

3. Eliminating Mixed Content Warnings

When application backends render HTML pages containing asset links pointing to static resources via http:// instead of https://, browsers block those assets.

Fix this by ensuring your upstream headers pass the scheme to your backend application code properly:

proxy_set_header X-Forwarded-Proto $scheme;
Enter fullscreen mode Exit fullscreen mode

In your application code, configure framework settings (such as Express app.set('trust proxy', true) in Node.js or Django SECURE_PROXY_SSL_HEADER) to instruct the framework to acknowledge the reverse proxy scheme.

Inspect the live Nginx log files to debug runtime failures as they occur:

sudo tail -f /var/log/nginx/app.example.com.error.log
Enter fullscreen mode Exit fullscreen mode

Getting Started

By setting up Nginx as a reverse proxy coupled with Certbot SSL certificates, you isolate internal microservices behind an encrypted server boundary.

To build out your production deployment:

  1. Deploy a Linux server on host providers like Hetzner VPS, Contabo VPS, or DigitalOcean.
  2. Point your primary domain or subdomains through your DNS registrar on Namecheap.
  3. Connect your reverse proxy setup to self-hosted orchestration runtimes or external webhooks using n8n Cloud.

Outsource Your Automation

Don't have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-DEVTO for priority. Or DM at chasebot.online.


Originally published on Automation Insider.

Top comments (0)