Moniruzzaman Saikat

Posted Sep 29, 2026 · 5 min read · 1 view

Report

Set Up Nginx and Free SSL with Let's Encrypt on Ubuntu

You have deployed your app to a VPS and it runs on port 3000. Now you need a proper domain, HTTPS, and a setup you can trust in production. That is exactly what Nginx and Let's Encrypt are for.

In this tutorial you will install Nginx on Ubuntu, configure it as a reverse proxy for an app, and secure it with a free SSL certificate that renews itself.

What you need

  • An Ubuntu 22.04 or 24.04 server with a sudo user
  • A domain name with an A record pointing to your server's IP address
  • An app already running locally on the server (we will assume port 3000)

Point both example.com and www.example.com to the server before you start, since the certificate step needs DNS to be working. You can check with:

dig +short example.com

Step 1: Install Nginx

sudo apt update
sudo apt install nginx -y

Check that it is running:

sudo systemctl status nginx

Then visit your server's IP address in a browser. You should see the default "Welcome to nginx" page.

Step 2: Open the firewall

If you use UFW, allow web traffic and keep SSH open so you do not lock yourself out:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status

Nginx Full opens both port 80 (HTTP) and port 443 (HTTPS).

Step 3: Create a server block

Nginx uses server blocks, which work like virtual hosts. Create a config file for your site:

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

Paste this configuration:

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

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        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 Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Here is what the important parts do:

  • server_name tells Nginx which domains this block handles.
  • proxy_pass forwards requests to your app running on port 3000.
  • The X-Forwarded headers pass the visitor's real IP and original protocol to your app. Without them, your app sees every request as coming from 127.0.0.1.
  • The Upgrade headers allow WebSocket connections to work through the proxy.

If your app is a PHP site instead, you would use a root directive and fastcgi_pass to PHP-FPM, but the certificate steps below stay the same.

Step 4: Enable the site and test the config

Nginx only serves sites that are linked into sites-enabled:

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

nginx -t checks your syntax. Never skip it, because a bad config can take your site down on reload. If the test passes, reload:

sudo systemctl reload nginx

If the default site conflicts with yours, remove its link with sudo rm /etc/nginx/sites-enabled/default.

Now http://example.com should show your app.

Step 5: Get a free SSL certificate

Let's Encrypt provides free certificates, and Certbot automates the whole process. Install it with snap:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Request and install the certificate:

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

Certbot will ask for your email address and to accept the terms. It then verifies that you control the domain, obtains the certificate, edits your Nginx config to use it, and offers to redirect all HTTP traffic to HTTPS. Choose the redirect option.

Reload the page and you should see the padlock in your browser.

Step 6: Confirm automatic renewal

Let's Encrypt certificates last 90 days. The Certbot package sets up a timer that renews them for you. Test the renewal process without changing anything:

sudo certbot renew --dry-run

If it finishes without errors, renewal is working. You can check the scheduled timer with:

systemctl list-timers | grep certbot

Step 7: Add sensible hardening

Open your server block again and add a few improvements inside the HTTPS server block that Certbot created.

Compress responses:

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

Add security headers:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Hide the Nginx version number. Add this to the http block in /etc/nginx/nginx.conf:

server_tokens off;

Limit upload size if your app accepts files:

client_max_body_size 10M;

Consider adding an HSTS header only after you are sure HTTPS works everywhere on your domain, because browsers will refuse to use plain HTTP for it afterward:

add_header Strict-Transport-Security "max-age=31536000" always;

Always run sudo nginx -t and reload after making changes.

Troubleshooting

ProblemLikely causeFix
502 Bad GatewayYour app is not running on the proxied portStart the app and check proxy_pass
Certbot fails validationDNS not pointing to the server yetWait for DNS, verify with dig
Certbot cannot reach port 80Firewall or provider blocking itOpen port 80 in UFW and the cloud panel
Config test failsTypo or missing semicolonRead the line number in the nginx -t output
Changes have no effectForgot to reloadRun sudo systemctl reload nginx

When you are stuck, the error log is your best friend:

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

Final thoughts

Nginx plus Let's Encrypt gives you a production grade front door for any app, and it costs nothing. Remember the workflow: create the server block, test with nginx -t, reload, then let Certbot handle HTTPS and renewals. Once you have done it a couple of times, it takes about ten minutes.

Which stack do you run behind Nginx: Node, PHP, or Python? Tell us in the comments.

Be the first to react
0

Written by

Moniruzzaman Saikat

Software Engineer at TheSoftking Ltd

Software engineer who loves building useful things, solving hard problems, and turning ideas into scalable products. Always learning, shipping, and experimenting with new tech.

Founding MemberNew MemberProlific Writer

14 articles · Dhaka Bangladesh · Joined Sep 2026

Discussion (0)

Sign in to join the discussion.