AI Chat

How to Install Nginx as a Reverse Proxy with Let's Encrypt SSL on Ubuntu 24.04

Install Nginx Reverse Proxy Lets Encrypt Ubuntu 24.04
Ask AI to extract steps & commands from this tutorial:

If you're running an app on a backend port — Node.js on 3000, a Python app on 8000, a Docker container on some internal port — you don't want people hitting that port directly. You want one clean, secure front door. That's what a reverse proxy does: Nginx sits in front of your app, handles the public traffic, and forwards requests to the app running behind it.

Pair that with a free Let's Encrypt SSL certificate, and you get a properly secured HTTPS setup without paying for a certificate or managing one by hand. This guide walks through the full setup on Ubuntu 24.04 — from installing Nginx to getting HTTPS working and auto-renewing.

Who This Guide Is For

This is for anyone running an application on a Ubuntu server — a Node.js API, a Python app, a Docker container, or anything listening on a local port — who wants to put it behind Nginx with real HTTPS, using a real domain name.

Prerequisites

Before you start, make sure you have:

  • An Ubuntu 24.04 server with root or sudo access

  • A registered domain name, with its A record pointing to your server's public IP address (this is required — Let's Encrypt can't issue a certificate for a domain that doesn't resolve to your server)

  • An application already running on the server, listening on a local port (this guide uses localhost:3000 as an example — swap it for your actual app's port)

  • Ports 80 and 443 open on your firewall

You can confirm your domain is pointing to the right place with:

bash

dig +short yourdomain.com
                            

The output should match your server's public IP. If it doesn't, fix your DNS first — nothing past this point will work until it does.

Step 1: Install Nginx

Update your package list and install Nginx from Ubuntu's repository:

bash

sudo apt update
sudo apt install -y nginx
                            

Confirm it's running:

bash

sudo systemctl status nginx
                            

You should see active (running). If you visit your server's IP address in a browser now, you'll see the default Nginx welcome page — that confirms the web server itself is working before you touch any configuration.

Step 2: Open the Firewall

If you're using UFW, allow both HTTP and HTTPS traffic. Nginx registers its own firewall profiles when installed, so you can use the named profile instead of raw port numbers:

bash

sudo ufw allow 'Nginx Full'
sudo ufw reload
                            

This opens both port 80 (HTTP) and port 443 (HTTPS). If you only had the HTTP profile allowed from a previous setup, this replaces it with both.

Step 3: Create the Reverse Proxy Server Block

Nginx server configurations live in /etc/nginx/sites-available/. Create a new file for your domain:

bash

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

Paste in a basic reverse proxy configuration, replacing yourdomain.com and the port number with your actual domain and app port:

nginx

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

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
                            

A quick breakdown of what those proxy headers actually do, since it's easy to copy this blindly:

  • proxy_pass — sends the request to your app running on that local port.

  • proxy_set_header Host — passes the original domain name through to your app, instead of it seeing localhost.

  • proxy_set_header X-Real-IP and X-Forwarded-For — preserve the visitor's real IP address, since without these your app would only ever see Nginx's own IP.

  • X-Forwarded-Proto — tells your app whether the original request was HTTP or HTTPS, which matters once SSL is in place and your app needs to know the connection was actually secure.

Enable the site by linking it into sites-enabled:

bash

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

Test the configuration for syntax errors before reloading:

bash

sudo nginx -t
                            

If it reports syntax is ok and test is successful, reload Nginx to apply the change:

bash

sudo systemctl reload nginx
                            

At this point, visiting http://yourdomain.com should show your app, proxied through Nginx — but still over plain HTTP.

Step 4: Install Certbot

Certbot is the standard tool for requesting and renewing Let's Encrypt certificates, and its Nginx plugin can edit your server block automatically. Install both from Ubuntu's repository:

bash

sudo apt install -y certbot python3-certbot-nginx
                            

Step 5: Request the SSL Certificate

Run Certbot with the Nginx plugin, pointing it at your domain:

bash

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

Certbot will ask for an email address (used for renewal and expiry notices) and ask you to agree to the Let's Encrypt terms of service. Once it validates that your domain actually points to this server, it issues the certificate and — this is the useful part — automatically rewrites your Nginx server block to add the listen 443 ssl directive, the certificate paths, and an HTTP-to-HTTPS redirect.

Your server block now effectively includes something like this, without you having to write it by hand:

nginx

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

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

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
                            

Step 6: Verify HTTPS Is Working

Visit https://yourdomain.com in a browser. You should see your app load with a padlock icon and no certificate warnings. You can also check from the command line:

bash

curl -I https://yourdomain.com
                            

A 200 OK (or your app's normal response code) with no SSL errors confirms the certificate is correctly installed and being served.

Also confirm the plain HTTP version now redirects to HTTPS instead of just serving the site insecurely:

bash

curl -I http://yourdomain.com
                            

You should see a 301 redirect pointing to the https:// version of the same URL.

Step 7: Confirm Auto-Renewal Is Set Up

Let's Encrypt certificates are valid for 90 days, which is intentionally short to encourage automation rather than manual renewal. The Certbot package installs a systemd timer that handles this for you automatically — you don't need to set up a cron job yourself.

Check that the timer is active:

bash

sudo systemctl status certbot.timer
                            

You can also do a dry run to confirm renewal would actually succeed, without making any real changes:

bash

sudo certbot renew --dry-run
                            

If this completes without errors, renewal is correctly configured and will keep running in the background for as long as the server is up.

Common Errors and How to Fix Them

Error Symptom How to Fix It
Certbot fails with a domain validation error This almost always means your DNS A record isn't actually pointing at this server yet, or hasn't propagated. Re-check with dig +short yourdomain.com and wait for it to match your server's IP before retrying.
502 Bad Gateway after setting up the proxy This means Nginx is running, but the app it's trying to reach on localhost:3000 isn't actually running, or is listening on a different port. Confirm your app is up with sudo ss -tlnp | grep 3000.
Certificate obtained, but the site still shows "not secure" Usually a browser cache issue after the redirect was added — try a hard refresh, or test in a private/incognito window. If it persists, re-run sudo nginx -t to confirm there isn't a duplicate or conflicting server block for the same domain.
"Nginx Full" profile not found in UFW This means the Nginx package didn't register its UFW application profile, which can happen on minimal installs. Allow ports 80 and 443 directly instead: sudo ufw allow 80/tcp and sudo ufw allow 443/tcp.

Next Steps

Once your reverse proxy and SSL are working, it's worth adding basic hardening — restricting direct access to your app's backend port from outside the server, and reviewing your Nginx security headers. If you're running multiple sites or containerized apps behind this same server, look into using separate server blocks per domain so each one manages its own certificate independently.

Frequently Asked Questions

Do I need a separate certificate for each subdomain? +

No — a single certificate can cover multiple names (like yourdomain.com and www.yourdomain.com) if you list them all with -d flags when requesting it. For unrelated domains or many subdomains, separate certificates are usually safer, since one certificate failing renewal won't take every other domain down with it.

What happens if my app changes ports later? +

Just edit the proxy_pass line in your server block to the new port, run sudo nginx -t to check the syntax, then sudo systemctl reload nginx. Your SSL certificate is unaffected, since it's tied to the domain, not the backend port.

Is Let's Encrypt as secure as a paid SSL certificate? +

Yes, in terms of encryption strength — a Let's Encrypt certificate provides the same TLS encryption as any other Domain Validated certificate. The difference from paid certificates is mainly around validation level (some paid certificates verify organization identity, not just domain control) and the 90-day renewal window instead of a longer one.

Can I run multiple apps behind the same Nginx server on the same domain? +

Yes — use different location blocks for different paths (like /api going to one port and / going to another), or separate server blocks with different subdomains, each pointing to a different proxy_pass target.

Our Bandwidth providers

We are Partners with 15 +

At eServers , we proudly partner with 15+ leading global tech providers to deliver secure, high-performance hosting solutions. These trusted alliances with top hardware, software, and network innovators ensure our clients benefit from modern technology and enterprise-grade reliability.

Hosting Solutions