DEV Community

brent alexis
brent alexis

Posted on AI-assisted

Nginx reverse proxy + HTTPS in 5 minutes (video + every command)

I recorded a short walkthrough of putting Nginx in front of an app and getting a free Let's Encrypt certificate onto it. Everything in the video runs on a real Ubuntu server, nothing is staged.

Below are the exact commands used, so you can copy them instead of pausing the video.

What you need

  • A Linux server. I used Ubuntu 24.04, which ships Nginx 1.24.
  • A domain or subdomain whose A record points at the server. If it's on Cloudflare, keep the record DNS only (grey cloud) while you get the certificate.
  • An app listening locally, for example on 127.0.0.1:3000.

1. Install Nginx and Certbot. Open the firewall to allow Nginx

sudo apt update
sudo apt install -y nginx certbot
sudo ufw allow 'Nginx Full'
Enter fullscreen mode Exit fullscreen mode

No app installed? Use this to start a test one on port 3000:

mkdir -p ~/app && echo '<h1>Hello from port 3000</h1>' > ~/app/index.html
cd ~/app && python3 -m http.server 3000
Enter fullscreen mode Exit fullscreen mode

Leave it running. Open a second SSH window for the rest of the tutorial.

2. Check your Nginx version

nginx -v
Enter fullscreen mode Exit fullscreen mode

This matters for HTTPS. Nginx 1.25.1 and newer use a separate http2 on; line. Older versions (like 1.24 on Ubuntu 24.04, or 22.04 and Debian 11 packages) need HTTP/2 on the listen line instead:

listen 443 ssl http2;
Enter fullscreen mode Exit fullscreen mode

Get this wrong and nginx -t fails with unknown directive "http2".

3. Generate the config

I used ProxyForge, the free generator I built. Pick Reverse proxy, enter your domain and 127.0.0.1:3000, and tick Redirect HTTP to HTTPS, WebSocket, and Older Nginx if step 2 showed a version before 1.25.1. Any config that does the same job works too. The parts that matter are:

  • a port 80 block that answers Let's Encrypt's check (/.well-known/acme-challenge/) and redirects everything else to HTTPS (more on this in step 7)
  • a port 443 block with ssl_certificate / ssl_certificate_key pointing at /etc/letsencrypt/live/<your-domain>/
  • proxy_pass http://127.0.0.1:3000; plus the usual proxy_set_header lines

Copy the output and keep it handy (I paste it into Notepad++), but I don't install it yet. The certificate files it points to don't exist, so nginx -t would fail.

4. Get the certificate

sudo certbot certonly --webroot -w /var/www/html \
  -d app.example.com -m you@example.com --agree-tos --no-eff-email
Enter fullscreen mode Exit fullscreen mode

The webroot method only drops a file in /var/www/html, which Ubuntu's default site already serves on port 80. Certbot never edits your Nginx config. You should see:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/app.example.com/fullchain.pem
Enter fullscreen mode Exit fullscreen mode

5. Install the config, test, then reload

sudo nano /etc/nginx/sites-available/app.example.com     # paste, Ctrl+O, Enter, Ctrl+X
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

&& means Nginx only reloads if the test passes, so a typo can't take a running site down.

One gotcha that cost me a few minutes. If the paste into nano silently fails, you end up saving an empty file. nginx -t still passes, but nothing listens on 443. Check with grep -c . /etc/nginx/sites-available/app.example.com. If it prints 0, paste again.

6. Check it

curl -I http://app.example.com     # expect: 301 Moved Permanently -> https://
curl -I https://app.example.com    # expect: HTTP/2 200
Enter fullscreen mode Exit fullscreen mode

Then open it in a browser and click the padlock to see the Let's Encrypt certificate.

7. Make sure renewal works

Certbot renews automatically, but a common trap is the HTTPS redirect itself. If port 80 redirects everything to HTTPS, Let's Encrypt's renewal check gets passed through to your app instead of /var/www/html, and renewal quietly fails about 60 days later.

I recorded the video before I caught this, so the config on screen redirects everything. ProxyForge's output now includes the exception (tested against a real nginx -t before it went live), so regenerate the config if you copied it from the video. If you're using another config, the port 80 block should look like this:

location /.well-known/acme-challenge/ {
    root /var/www/html;
}
location / {
    return 301 https://$host$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

The return has to sit inside location /. A return placed directly in the server block runs before Nginx looks at any location, so the check would still be redirected.

Not sure which folder you gave Certbot? This version serves the file if it's there and otherwise falls back to the redirect, so it can't make things worse. ProxyForge's Redirect Generator uses this form:

location /.well-known/acme-challenge/ {
    root /var/www/html;
    try_files $uri @redirect;
}
location @redirect {
    return 301 https://$host$request_uri;
}
location / {
    return 301 https://$host$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

Either way, test renewal now instead of finding out in two months:

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

Congratulations, all simulated renewals succeeded means you're done.


If you hit an error I didn't cover, paste it in the comments and I'll take a look. The generator is free with no sign-up, and the how-to guide explains every option.

Top comments (0)