Back to tutorials
Tutorial

How to Setup Nginx SSL Reverse Proxy on Ubuntu 22.04: Complete Configuration Guide with Let's Encrypt

Complete guide to setup Nginx SSL reverse proxy on Ubuntu 22.04 with Let's Encrypt certificates. Secure backend services with HTTPS termination.

By Anurag Singh
Updated on Apr 22, 2026
Category: Tutorial
Share article
How to Setup Nginx SSL Reverse Proxy on Ubuntu 22.04: Complete Configuration Guide with Let's Encrypt

Understanding Nginx SSL Reverse Proxy Architecture

An Nginx SSL reverse proxy sits between clients and your backend services. It terminates SSL connections and forwards requests to upstream servers on different ports or machines.

This setup handles certificate management while your applications focus on core logic.

The benefits are substantial. You centralize SSL certificates in one location instead of managing them across multiple services.

Backend applications run without SSL overhead, reducing resource usage. Load balancing becomes straightforward across multiple instances.

Your Node.js, Python, or PHP applications can focus entirely on business logic. Nginx handles SSL handshakes and connection management.

Prerequisites and Server Preparation

Start with a fresh Ubuntu 22.04 server and root access. Point your domain to the server's public IP using DNS A records.

Update packages and install required tools:

sudo apt update && sudo apt upgrade -y
sudo apt install nginx certbot python3-certbot-nginx curl -y

Start and enable Nginx:

sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx

The status should show "active (running)". Test by visiting your server's IP address in a browser.

Configure firewall rules:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

Backend Application Setup

We'll create a simple Node.js application on port 3000 for demonstration.

Set up the test app:

mkdir /opt/testapp && cd /opt/testapp
npm init -y
npm install express

Create app.js with basic server code:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.json({ 
    message: 'Hello from backend!', 
    headers: req.headers,
    ip: req.ip 
  });
});

app.listen(port, '127.0.0.1', () => {
  console.log(`Server running on port ${port}`);
});

Start the application and test locally:

node app.js &
curl http://127.0.0.1:3000

You should see the JSON response. The backend binds only to localhost—external traffic reaches it through Nginx.

SSL Certificate Generation with Let's Encrypt

Obtain SSL certificates before configuring the reverse proxy. Replace yourdomain.com with your actual domain:

sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com

Certbot prompts for your email and terms agreement. Choose option 2 to get certificates without modifying Nginx configuration.

Certificates are stored in /etc/letsencrypt/live/yourdomain.com/:

  • fullchain.pem - Complete certificate chain
  • privkey.pem - Private key file

Verify certificate creation:

sudo ls -la /etc/letsencrypt/live/yourdomain.com/

Set up automatic renewal by editing crontab:

sudo crontab -e

Add this line for twice-daily renewal checks:

0 12,0 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx

Nginx SSL Reverse Proxy Configuration

Create a new server block configuration:

sudo nano /etc/nginx/sites-available/yourdomain.com

Add this complete configuration:

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozTLS:10m;
    ssl_session_tickets off;

    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";

    # Proxy Configuration
    location / {
        proxy_pass http://backend;
        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_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

This configuration handles several critical functions.

The upstream block defines backend servers with connection pooling. HTTP requests automatically redirect to HTTPS.

SSL settings follow Mozilla's modern recommendations for security. Security headers protect against common attacks.

Proxy headers preserve client information for backend applications.

Enabling and Testing the Configuration

Test Nginx syntax first:

sudo nginx -t

You should see "syntax is ok" and "test is successful".

Enable the site with a symbolic link:

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/

Remove the default site to prevent conflicts:

sudo rm /etc/nginx/sites-enabled/default

Reload Nginx:

sudo systemctl reload nginx

Test the SSL configuration:

curl -I https://yourdomain.com

You should see HTTP/2 200 responses with security headers.

Check SSL certificate details:

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com

These SSL configurations work seamlessly with HostMyCode VPS optimized Ubuntu images and network infrastructure.

Advanced Security and Performance Optimization

Add rate limiting to prevent abuse attacks.

Create a rate limiting configuration:

sudo nano /etc/nginx/conf.d/rate-limiting.conf

Add these directives:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

Update your server block to apply rate limiting:

location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://backend;
    # ... other proxy settings
}

location /login {
    limit_req zone=login burst=5;
    proxy_pass http://backend;
    # ... other proxy settings
}

Configure Gzip compression for better performance:

sudo nano /etc/nginx/conf.d/gzip.conf

Add compression settings:

gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

SSL Certificate Monitoring and Maintenance

Create a certificate expiration monitoring script:

sudo nano /usr/local/bin/ssl-check.sh

Add this monitoring script:

#!/bin/bash
DOMAIN="yourdomain.com"
EXPIRY=$(openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates | grep notAfter | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))

if [ $DAYS_LEFT -lt 7 ]; then
    echo "WARNING: SSL certificate for $DOMAIN expires in $DAYS_LEFT days"
    # Add notification logic here
else
    echo "SSL certificate for $DOMAIN is valid for $DAYS_LEFT more days"
fi

Make it executable and schedule daily checks:

sudo chmod +x /usr/local/bin/ssl-check.sh
sudo crontab -e

Add daily certificate monitoring:

0 8 * * * /usr/local/bin/ssl-check.sh

For comprehensive monitoring approaches, see our Linux VPS performance monitoring guide for production environments.

Multiple Backend Configuration

Host multiple applications by configuring additional upstream blocks:

upstream app1 {
    server 127.0.0.1:3000;
}

upstream app2 {
    server 127.0.0.1:3001;
}

upstream app3 {
    server 127.0.0.1:3002;
}

Route traffic using location blocks:

location /app1/ {
    proxy_pass http://app1/;
    # ... proxy settings
}

location /app2/ {
    proxy_pass http://app2/;
    # ... proxy settings
}

location /api/ {
    proxy_pass http://app3/;
    # ... proxy settings
}

This approach works particularly well with HostMyCode Node.js hosting for running multiple applications behind a single SSL endpoint.

Troubleshooting Common Issues

Check Nginx error logs for configuration problems:

sudo tail -f /var/log/nginx/error.log

Common issues and solutions:

502 Bad Gateway - Backend service isn't running or uses wrong upstream port.

Verify your application is listening:

sudo netstat -tlnp | grep :3000

SSL certificate errors - Check certificate paths and permissions:

sudo ls -la /etc/letsencrypt/live/yourdomain.com/
sudo nginx -t

WebSocket connection failures - Ensure Upgrade headers are properly configured in the proxy block.

Test backend connectivity directly:

curl -H "Host: yourdomain.com" http://127.0.0.1:3000

Monitor real-time access logs:

sudo tail -f /var/log/nginx/access.log

Load Balancing Multiple Backend Servers

For high-availability setups, configure multiple backend servers:

upstream backend {
    server 127.0.0.1:3000 weight=3;
    server 127.0.0.1:3001 weight=2;
    server 192.168.1.100:3000 backup;
    keepalive 32;
}

This distributes traffic based on weights. The backup server only receives traffic when primary servers fail.

Add health checking with custom error pages:

location /health {
    access_log off;
    return 200 "healthy";
    add_header Content-Type text/plain;
}

Check our detailed VPS performance tuning guide for optimizing high-traffic configurations with multiple backends.

Ready to deploy your SSL reverse proxy setup? HostMyCode VPS hosting provides optimized Ubuntu 22.04 images with pre-configured security settings. Our managed VPS plans include SSL certificate management and Nginx optimization right out of the box.

Frequently Asked Questions

Can I use multiple SSL certificates with one Nginx reverse proxy?

Yes, configure separate server blocks for each domain with their respective SSL certificates. Nginx automatically selects the correct certificate based on the requested hostname through SNI (Server Name Indication).

How do I handle WebSocket connections through the SSL proxy?

The configuration shown includes the necessary Upgrade and Connection headers. Ensure your location block includes proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection 'upgrade'; for WebSocket support.

What's the difference between proxy_pass with and without trailing slashes?

proxy_pass http://backend; preserves the full URI path, while proxy_pass http://backend/; removes the location prefix. Choose based on how your backend expects to receive requests.

How can I monitor SSL certificate expiration automatically?

Use the monitoring script provided above, or integrate with external monitoring tools. Certbot's automatic renewal typically handles this, but monitoring provides early warning for any renewal failures.

Can I terminate SSL at the reverse proxy while using HTTPS between proxy and backend?

Yes, change proxy_pass http://backend; to proxy_pass https://backend; and add proxy_ssl_verify off; if using self-signed certificates on backend servers.

How to Setup Nginx SSL Reverse Proxy on Ubuntu 22.04: Complete Configuration Guide with Let's Encrypt | HostMyCode