Back to tutorials
Tutorial

SSL Setup Guide Tutorial (2026): Install Let’s Encrypt on a VPS with Nginx, Auto-Renewal, and Safe Redirects

SSL setup guide tutorial for 2026: install Let’s Encrypt on a VPS with Nginx, auto-renewal, redirects, and verification steps.

By Anurag Singh
Updated on Jun 29, 2026
Category: Tutorial
Share article
SSL Setup Guide Tutorial (2026): Install Let’s Encrypt on a VPS with Nginx, Auto-Renewal, and Safe Redirects

Most “SSL issues” aren’t cryptography problems. They’re config mistakes.

The certificate lands on the wrong server block. HTTP still serves an old vhost. Renewal runs, but Nginx never reloads.

This SSL setup guide tutorial walks through a clean Let’s Encrypt setup on an Ubuntu VPS running Nginx. You’ll add auto-renewal, safe HTTP→HTTPS redirects, and quick checks you can reuse for every domain.

If you run production sites, set this up once. Write it down. Stop treating SSL like a recurring emergency.

What you’ll build (and what you need)

  • Ubuntu Server 24.04 LTS (or 22.04 LTS) VPS with root or sudo access
  • Nginx 1.24+ (Ubuntu packages are fine)
  • A domain name pointing to your VPS public IP (A/AAAA records)
  • A working Nginx site (even a basic placeholder)
  • Let’s Encrypt certificate issued via Certbot using the Nginx plugin
  • Automatic renewal with reload verification

If you’re still choosing a server, a HostMyCode VPS works well for Nginx + Let’s Encrypt. You control DNS, firewall rules, and the web stack.

If you don’t want to manage OS updates and baseline hardening yourself, managed VPS hosting is the lower-maintenance option.

Step 1: Confirm DNS points to your VPS (A/AAAA)

Before you request a certificate, Let’s Encrypt must be able to reach your server for the domain you’re validating. First, confirm what IP your domain actually resolves to.

dig +short A example.com
dig +short AAAA example.com

The A record should match your VPS IPv4. If you use IPv6, the AAAA record should match your VPS IPv6.

Pitfall: During propagation, issuance can fail unpredictably. For planned moves, drop TTL ahead of time. Then cut over deliberately.

Our DNS cutover tutorial covers a low-downtime workflow with rollback checks.

Step 2: Install Nginx and open the firewall

If Nginx isn’t installed yet:

sudo apt update
sudo apt install -y nginx

Next, allow HTTP and HTTPS. On Ubuntu, UFW is the common choice:

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

Want a tighter rule set (without locking yourself out of SSH)? Follow our UFW firewall configuration tutorial.

Step 3: Create a clean Nginx server block for your domain

Keep site configs in /etc/nginx/sites-available/. Then symlink into sites-enabled.

Start with an HTTP-only vhost for example.com and www.example.com.

sudo mkdir -p /var/www/example.com/public_html
sudo chown -R www-data:www-data /var/www/example.com

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

Paste:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    root /var/www/example.com/public_html;
    index index.html;

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

    location / {
        try_files $uri $uri/ =404;
    }
}

Enable the site and reload:

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

Create a quick test page:

echo 'OK: example.com over HTTP' | sudo tee /var/www/example.com/public_html/index.html

Now confirm the domain reaches your VPS and hits Nginx:

curl -I http://example.com

You should see HTTP/1.1 200 OK and Server: nginx.

Step 4: Install Certbot and request a Let’s Encrypt certificate

On Ubuntu, install Certbot and the Nginx plugin:

sudo apt install -y certbot python3-certbot-nginx

Issue the certificate and let Certbot update your Nginx config:

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

During the prompts:

  • Use a real email address (renewal and security notices matter in 2026).
  • Pick the redirect option only after you’ve confirmed the HTTPS vhost will point at the right root/upstream.

If it succeeds, Certbot stores your files here:

  • /etc/letsencrypt/live/example.com/fullchain.pem
  • /etc/letsencrypt/live/example.com/privkey.pem

Step 5: Make redirects safe (avoid loops, keep ACME validation working)

Certbot usually adds an HTTP→HTTPS redirect automatically. That’s fine—until it isn’t.

Two failures show up a lot:

  • Redirect loops (often caused by an upstream proxy also forcing HTTPS)
  • Future validation breakage if you change challenge methods or introduce a different proxy layout

A practical pattern is simple and repeatable:

  • Keep an HTTP server block that serves the ACME challenge path, then redirects everything else.
  • Keep HTTPS as the only “real” application/site vhost.

Edit your site config (Certbot may have created or modified files).

A clean HTTP block looks like this:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com/public_html;
        default_type "text/plain";
    }

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

Test and reload:

sudo nginx -t
sudo systemctl reload nginx

Step 6: Verify the certificate and full chain from the command line

Browser checks help. CLI checks are faster. They’re also easier to troubleshoot.

1) Confirm HTTPS response and redirect behavior

curl -I http://example.com
curl -I https://example.com

You want:

  • HTTP returns 301 to the same host on https://
  • HTTPS returns 200 (or your app’s expected status)

2) Inspect the certificate and issuer

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject -dates

Check that:

  • subject matches your domain
  • notAfter is in the future (Let’s Encrypt certificates are short-lived by design)
  • The issuer looks like Let’s Encrypt (commonly “R” series intermediates)

Step 7: Confirm auto-renewal and make reload predictable

On Ubuntu, Certbot typically installs a systemd timer. Verify it:

systemctl list-timers | grep certbot

Then run a dry-run renewal to confirm everything still validates:

sudo certbot renew --dry-run

Sometimes renewal succeeds, but Nginx never reloads. When that happens, browsers can warn about an “expired certificate” even though the files on disk are updated.

Certbot often reloads Nginx for you with the Nginx plugin. If you want it to be explicit, add a deploy hook:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
set -eu
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Run another dry run to confirm the hook behaves:

sudo certbot renew --dry-run

Step 8: Add modern TLS settings (without getting fancy)

Nginx defaults are generally fine. You can still tighten TLS settings without breaking common clients.

Create a small snippet file:

sudo nano /etc/nginx/snippets/tls.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# OCSP stapling (depends on resolver access)
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

Then include it inside your HTTPS server { } block:

include /etc/nginx/snippets/tls.conf;

Reload:

sudo nginx -t
sudo systemctl reload nginx

If you also want hardened web security headers (CSP, HSTS, Permissions-Policy) with practical examples for both Nginx and Apache, use our HTTP security headers tutorial.

Don’t wing CSP. One bad directive can take out checkout flows and admin screens.

Step 9: Handle multi-site hosting on one VPS (repeatable pattern)

On a multi-site VPS, keep things boring and isolated:

  • One /etc/nginx/sites-available/{domain} file per domain
  • One web root per domain (or a distinct upstream if it’s an app)
  • One Let’s Encrypt cert per domain (or a carefully managed SAN cert)

Then issuing the next certificate is just repetition:

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

Operational tip: Keep an inventory so nothing expires quietly:

sudo certbot certificates

Step 10: Common errors and quick fixes (copy/paste diagnostics)

Problem: “Connection refused” during validation

  • Check that Nginx is running: systemctl status nginx
  • Check firewall allows 80/443: sudo ufw status
  • Check cloud security group / provider firewall if applicable

Problem: “Could not find a suitable TLS ALPN challenge” or HTTP-01 fails

  • Confirm DNS points to the correct IP: dig +short A example.com
  • Confirm the right server block answers: sudo nginx -T | grep -n "server_name example.com" -n
  • Make sure port 80 is reachable from the public internet

Problem: Wrong site gets the certificate

  • You likely have a “default” server catching requests.
  • Ensure each domain has an explicit server_name.
  • Avoid using _ as a catch-all unless you know exactly why.

Problem: Renewal works, but browsers still show the old certificate

  • Reload Nginx: sudo systemctl reload nginx
  • Confirm which cert path Nginx uses: sudo nginx -T | grep -E "ssl_certificate|ssl_certificate_key" -n
  • Verify the live cert on 443: openssl s_client -connect example.com:443 -servername example.com

If you’re seeing repeated renewal failures across several sites (usually DNS, redirects, or conflicting vhosts), this guide goes deeper: SSL renewal troubleshooting tutorial.

Step 11: Production checklist for SSL on a hosting VPS

  • Ports open: 80 and 443 reachable publicly
  • DNS correct: A/AAAA records match server IP
  • Nginx vhosts clean: one domain per config, no accidental catch-all
  • Redirect tested: HTTP→HTTPS doesn’t loop; app works on HTTPS
  • Auto-renewal verified: certbot renew --dry-run succeeds
  • Reload guaranteed: deploy hook or plugin-managed reload
  • Security headers planned: add HSTS only after you’re confident
  • Backups exist: at least configs + web roots + critical secrets

Summary: a repeatable SSL workflow you can trust

A good SSL setup is boring. DNS resolves correctly. Nginx vhosts are unambiguous.

Certbot issues cleanly. Renewal triggers an Nginx reload every time. That’s the workflow.

If you want predictable performance and full control for multiple HTTPS sites, run this on a HostMyCode VPS.

If you’d rather hand off OS maintenance but keep VPS-level isolation, managed VPS hosting is the hands-off route.

If you’re standardizing SSL across client sites or moving off shared hosting, keep the stack simple: Nginx, Certbot, and a VPS you control. Start with a HostMyCode VPS, or choose managed VPS hosting if you want help with updates, hardening, and day-to-day server care.

FAQ

Do I need port 80 open if I force HTTPS?

Yes for HTTP-01 validation, unless you use DNS-01. A minimal HTTP vhost that only serves /.well-known/acme-challenge/ plus a redirect is a safe pattern.

Should I enable HSTS right away?

Not on day one. Confirm your HTTPS vhost is correct, renewals work, and all subdomains you care about support HTTPS. Then add HSTS with a short max-age and increase it later.

How do I add SSL for a new domain on the same VPS?

Create a new Nginx server block for the domain. Verify curl -I http://newdomain works. Then run sudo certbot --nginx -d newdomain -d www.newdomain.

What’s the fastest way to check what certificate the server is actually presenting?

Run: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates. It tells you the active certificate on the wire, not what you think you configured.

Can I do this on a dedicated server too?

Yes. The steps are the same, just with more room for multi-site hosting, isolation, and logging. Dedicated servers also make sense if you’re consolidating many SSL sites under one roof.

SSL Setup Guide Tutorial (2026): Install Let’s Encrypt on a VPS with Nginx, Auto-Renewal, and Safe Redirects | HostMyCode