Back to tutorials
Tutorial

SSL Certificate Setup Guide (Tutorial) for Ubuntu VPS: Nginx + Let’s Encrypt in 2026

SSL certificate setup guide for Ubuntu VPS with Nginx + Let’s Encrypt: install, auto-renew, fix common errors, and verify TLS.

By Anurag Singh
Updated on Jun 01, 2026
Category: Tutorial
Share article
SSL Certificate Setup Guide (Tutorial) for Ubuntu VPS: Nginx + Let’s Encrypt in 2026

TLS is easy to enable and just as easy to misconfigure. The padlock only means the browser negotiated HTTPS. It does not confirm your redirects work. It also won’t tell you if renewals will fail quietly or if the server still offers weak protocols.

This SSL certificate setup guide walks through a clean Let’s Encrypt rollout on an Ubuntu VPS with Nginx. It also includes checks that catch common mistakes before your users do.

The steps below target Ubuntu Server 24.04 LTS (a common 2026 baseline) with Nginx 1.24+ packages. By the end, you’ll have HTTPS and a correct HTTP→HTTPS redirect.

You’ll also have a solid A+ style TLS baseline and renewals you can rely on.

Before you start: prerequisites checklist (domain, DNS, and access)

Hold off on Certbot until these are true. You’ll save time and avoid Let’s Encrypt rate limits.

  • A domain name pointing to your VPS public IP (A/AAAA record).
  • Ports open: 80/tcp and 443/tcp reachable from the internet.
  • Root or sudo SSH access to the server.
  • Nginx installed and serving your site on HTTP first (even a placeholder page is fine).

If you’re still picking infrastructure, start with a HostMyCode VPS. It gives you direct control over Nginx, firewall rules, and certificates.

If you’d rather have patching and baseline security handled for you, managed VPS hosting is the lower-maintenance route.

Step 1 — Confirm DNS is correct (and not “half-propagated”)

From your local machine (or any Linux shell), confirm the domain resolves to the right IP. Replace example.com with your domain.

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

The returned addresses should match your VPS.

If you recently changed DNS, query the authoritative nameserver. This avoids being fooled by cached answers:

dig @ns1.your-dns-host.tld example.com A +short

If you want a deeper DNS refresher, HostMyCode’s walkthrough on zone records and propagation is a useful checklist. It covers A/AAAA and common CNAME gotchas: DNS management for VPS hosting.

Step 2 — Install Nginx and create a clean server block

On Ubuntu, install Nginx and start it at boot:

sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx

Create a web root directory for the domain:

sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 0755 /var/www/example.com

Add a simple page. This lets you confirm plain HTTP works before you introduce TLS:

sudo tee /var/www/example.com/public/index.html >/dev/null <<'HTML'
<!doctype html>
<html><head><meta charset="utf-8"><title>OK</title></head>
<body>HTTP is working.</body></html>
HTML

Now create an Nginx server block:

sudo tee /etc/nginx/sites-available/example.com >/dev/null <<'NGINX'
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.html index.htm;

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

Enable the site and reload Nginx:

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

Open http://example.com in a browser. You should see “HTTP is working.”

Step 3 — Open the firewall (UFW) for 80/443

If you use UFW (typical on VPS builds), allow Nginx traffic:

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

Confirm the active rules:

sudo ufw status verbose

If you run a tighter security posture, keep SSH restricted to your IP.

Expose only 80/443 publicly.

For a broader hardening baseline, HostMyCode’s kernel patching/mitigation guidance fits well into regular maintenance windows: Linux kernel security patches for VPS administrators.

Step 4 — Install Certbot and the Nginx plugin (the safe way on Ubuntu)

On Ubuntu 24.04 LTS, the most dependable approach in 2026 is Snap-based Certbot. It stays current.

That matters because ACME behavior and renewal logic change over time.

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

Confirm it’s installed correctly:

certbot --version

Step 5 — Issue the certificate and let Certbot configure Nginx

Run Certbot with the Nginx plugin. Request both the root and www hostnames:

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

During the prompts:

  • Use an email you monitor (expiry and security notices go there).
  • Agree to the terms.
  • Select the option to redirect HTTP to HTTPS when offered.

Certbot will adjust your Nginx config.

It will also place certificate files here:

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

If Certbot didn’t reload for you, reload manually:

sudo nginx -t
sudo systemctl reload nginx

Step 6 — Verify HTTPS, redirects, and the actual certificate served

First, verify the redirect is a single hop and doesn’t loop:

curl -I http://example.com

You’re looking for a 301 (or 308) and a Location: https://example.com/.

Next, confirm the server is presenting the certificate you expect.

Check the hostname and the chain:

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

Check that:

  • Not Before / Not After dates look reasonable (Let’s Encrypt is typically 90 days).
  • Subject includes CN=example.com (and SANs cover www if you requested it).
  • Issuer is Let’s Encrypt.

Step 7 — Turn on auto-renewal and test it for real

Certbot (Snap) usually creates a systemd timer automatically. Confirm it exists:

sudo systemctl list-timers | grep certbot

Now run a dry run. This catches “it worked once” setups that fail during renewal later.

sudo certbot renew --dry-run

If the dry run succeeds, your renewal path is working.

Tip: If your setup needs a post-renew action (like a reload in a customized config), add a deploy hook:

sudo certbot renew --deploy-hook "systemctl reload nginx"

Step 8 — Harden TLS settings in Nginx (without breaking clients)

Certbot gets HTTPS online. It does not always apply a consistent TLS profile across every server block.

A sensible 2026 baseline looks like this:

  • TLS 1.2 and TLS 1.3 enabled
  • Server-preferred ciphers (mostly relevant for TLS 1.2)
  • OCSP stapling enabled
  • HSTS enabled once you’re sure HTTPS is stable

Create a shared TLS snippet you can reuse across sites:

sudo tee /etc/nginx/snippets/tls-params.conf >/dev/null <<'NGINX'
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

# TLS 1.2 ciphers (TLS 1.3 ciphers are not configured here)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
           ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
           ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

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;

# Enable HSTS after you confirm HTTPS works everywhere (including subdomains if you add includeSubDomains)
add_header Strict-Transport-Security "max-age=15552000" always;

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;
NGINX

Now edit the HTTPS server block (Certbot created it) and include the snippet. Your file won’t match this article exactly, but the placement is the same:

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

Inside the server { listen 443 ssl; ... } block, add:

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

Validate and reload:

sudo nginx -t
sudo systemctl reload nginx

HSTS caution: After a browser caches HSTS, it will force HTTPS for your domain.

Don’t enable preload unless you understand the implications and you’re ready to commit.

Step 9 — Fix the 5 most common Let’s Encrypt errors (fast diagnostics)

Most failures come down to reachability.

The other common cause is the ACME challenge request hitting the wrong server block.

Use these checks to narrow it down quickly.

Problem A: “Timeout during connect” (firewall or security group)

  • Confirm Nginx listens on 80: sudo ss -ltnp | grep ':80'
  • Confirm UFW: sudo ufw status verbose
  • From an external host, test: curl -I http://example.com

Problem B: “NXDOMAIN” (DNS not set or wrong nameservers)

  • Re-check dig +short A example.com
  • Verify the domain uses the DNS provider you’re editing (nameservers in WHOIS/registrar).

Problem C: “Unauthorized” (wrong webroot, wrong server_name, or redirect issues)

  • Ensure the server_name includes the exact hostname you’re requesting.
  • Remove any overly aggressive redirects on HTTP that block /.well-known/acme-challenge/.
  • Check Nginx config: sudo nginx -T | sed -n '1,200p'

Problem D: You have Cloudflare/proxy enabled and HTTP-01 fails

If your DNS provider proxies traffic (common with CDN toggles), the ACME HTTP-01 challenge can fail.

This often happens when the proxy path is misconfigured.

Two safe options:

  • Temporarily disable proxying (DNS-only) for the hostname while issuing the cert.
  • Use DNS-01 validation (advanced; requires API tokens and is best when you need wildcard certs).

Problem E: Rate limits from repeated failed requests

Stop issuing certificates.

Fix DNS and plain HTTP first, then run:

sudo certbot renew --dry-run

If you need to test changes without burning real issuance attempts, use Certbot’s --staging flag during trials.

Step 10 — Add a second site or app behind Nginx (without breaking HTTPS)

On most VPS deployments, you’ll host more than one domain.

Put each domain in its own server block. Then request certificates per domain.

For a second domain:

  1. Create /var/www/secondsite.tld/public
  2. Create /etc/nginx/sites-available/secondsite.tld
  3. Enable and reload
  4. Run Certbot again: sudo certbot --nginx -d secondsite.tld -d www.secondsite.tld

If you host multiple client sites, a VPS is usually the right minimum. Dedicated servers start to pay off once you’re constrained on CPU/RAM.

They also help when you need stricter isolation for noisy workloads.

Operational checklist (what to keep doing after the tutorial)

  • Monthly: run sudo certbot renew --dry-run and confirm Nginx reload still succeeds.
  • After Nginx changes: run sudo nginx -t before reload.
  • After DNS changes: verify with dig from at least two networks.
  • After server migrations: confirm the certificate directory (/etc/letsencrypt) and symlinks were transferred correctly.

If you’re moving a live site to a new VPS, treat TLS as part of the migration plan.

Don’t leave it as a cleanup task after launch.

HostMyCode offers assisted moves via HostMyCode migrations, which helps reduce downtime while you repoint DNS and validate HTTPS.

Want HTTPS configured cleanly on a server you control? Start with a HostMyCode VPS and work through the steps above. If you’d rather not keep an eye on renewals, patch cycles, and Nginx config drift for production sites, managed VPS hosting is the practical choice.

FAQ: SSL certificate setup on a VPS

Do I need to stop Nginx to issue a Let’s Encrypt certificate?

No. With the --nginx plugin, Certbot updates Nginx config and completes the challenge while Nginx stays online. Typically you’ll see a reload, not a full stop.

Can I use this SSL certificate setup guide for Apache instead of Nginx?

The ideas transfer, but the commands and file paths don’t. Apache commonly uses /etc/apache2/sites-available/ and Certbot’s --apache plugin.

Why does my browser still warn after I installed the certificate?

Usually it’s one of these: DNS still points to the wrong IP, you issued a cert for example.com but you’re visiting www.example.com, or a proxy/CDN is presenting a different certificate than your VPS.

What’s the safest way to force HTTPS?

Use an HTTP→HTTPS redirect at the web server (Certbot can set this up), then enable HSTS only after you’ve verified every path and subdomain behaves correctly.

Should I use a wildcard certificate?

Only if you truly need lots of unpredictable subdomains. Wildcards require DNS-01 validation and a DNS provider API token, which adds operational risk if it’s handled poorly.

Summary: a repeatable SSL rollout you can trust

You installed Nginx, issued a Let’s Encrypt certificate, enforced HTTPS redirects, confirmed the certificate being served, and proved renewals work with a dry run.

That combination is what separates “it works right now” from “it will still work next quarter.”

If you want a stable home for production sites, match your hosting to your workload. A HostMyCode VPS gives you full control over Nginx and TLS tuning, while managed VPS hosting keeps the routine upkeep from consuming your week.