Back to tutorials
Tutorial

SSL Certificate Deployment Tutorial (2026): Install, Automate, and Troubleshoot Let’s Encrypt on a VPS

SSL certificate deployment tutorial for 2026: install Let’s Encrypt on a VPS, automate renewals, and fix common HTTPS failures.

By Anurag Singh
Updated on Jul 29, 2026
Category: Tutorial
Share article
SSL Certificate Deployment Tutorial (2026): Install, Automate, and Troubleshoot Let’s Encrypt on a VPS

Most SSL failures on a VPS aren’t “Let’s Encrypt problems.” They’re usually routing mistakes (the wrong vhost answers), DNS drift (A/AAAA points somewhere else), or web server issues (port 80 blocked, challenge path not served, or an old cert still presented). This SSL certificate deployment tutorial shows a repeatable, production-safe way to deploy Let’s Encrypt on Ubuntu and Debian VPS servers. You’ll automate renewals and pinpoint exactly why HTTPS breaks.

If you manage client sites, don’t treat certificates as a one-and-done task. Treat them like backups: routine, monitored, and periodically tested.

The steps below assume a single VPS hosting one or more domains with Nginx, Apache, or both.

What you’ll set up (and what you’ll avoid)

  • ACME client: Certbot (snap or apt) with webroot or Nginx/Apache plugin
  • Renewals: systemd timer + a post-renew reload hook
  • Verification: confirm the correct cert is being served (not an old one)
  • Troubleshooting: fix failed challenges, renewal loops, and chain issues

You will not need a control panel for this guide. If you do use WHM/cPanel, HostMyCode has a separate hardening guide that fits that workflow better.

Prerequisites checklist (5 minutes)

  • A VPS with root or sudo access (Ubuntu 24.04/26.04 LTS or Debian 12/13 are common in 2026)
  • A domain pointing to the VPS: correct A record (and AAAA if you serve IPv6)
  • Ports open: 80/tcp and 443/tcp reachable from the internet
  • A working virtual host/server block for the domain

On production, work from a stable connection. Keep an SSH session open while you apply changes.

If you want a managed setup where updates, firewall basics, and monitoring are handled consistently, consider managed VPS hosting from HostMyCode.

Step 1: Confirm DNS is correct (don’t skip this)

From your laptop (or any external host), confirm DNS resolves to the IP you expect:

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

Then compare that to the public IP your VPS reports:

curl -4 https://ifconfig.me
curl -6 https://ifconfig.me

If an AAAA record exists but your VPS doesn’t actually serve IPv6, HTTP-01 challenges can fail. It depends on which address a resolver prefers.

Either fix IPv6 end-to-end, or remove the AAAA record.

Step 2: Make sure port 80 is reachable (ACME depends on it)

Let’s Encrypt HTTP-01 validation requires inbound HTTP on port 80 unless you’re using DNS-01. Start with a quick listener check:

sudo ss -lntp | egrep ':80|:443'

Firewall on Ubuntu with UFW:

sudo ufw status verbose
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

If you want a safer baseline firewall that won’t accidentally break mail/DNS on multi-role boxes, follow: UFW firewall setup tutorial (2026).

Step 3: Install Certbot (Ubuntu/Debian)

In 2026, Certbot via snap is usually the least painful path on Ubuntu. On Debian, apt is still a solid choice.

Pick one approach and stick with it. That avoids two Certbots and conflicting timers.

Option A: Ubuntu (recommended) — snap

sudo apt update
sudo apt install -y snapd
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Option B: Debian — apt

sudo apt update
sudo apt install -y certbot

If you need Nginx or Apache integration, add:

sudo apt install -y python3-certbot-nginx
# or
sudo apt install -y python3-certbot-apache

Step 4: Deploy a certificate (choose the right method)

You have three practical deployment patterns. Use the one that matches how you manage vhosts and how much you want Certbot to touch your config.

Method 1 (most controlled): webroot validation

This works with both Nginx and Apache. It also keeps Certbot from rewriting your configuration.

You tell Certbot the domain’s document root. Certbot places the ACME challenge file there.

Example Nginx server block snippet (ensure this exists for port 80):

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

  root /var/www/example.com/public;

  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    allow all;
  }

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

Request the certificate:

sudo certbot certonly --webroot \
  -w /var/www/example.com/public \
  -d example.com -d www.example.com

After issuance, configure your TLS vhost to use the generated files:

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

Method 2 (fastest): Nginx plugin

If your Nginx config is straightforward, the plugin is quick and usually accurate:

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

Still review what changed. On a multi-site box, the first “working” vhost is not always the correct one.

Method 3: Apache plugin

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

If you run Nginx in front of Apache/cPanel, keep SSL termination consistent. Don’t split responsibilities across layers.

For that architecture, use this: Reverse Proxy Setup Guide Tutorial (2026).

Step 5: Force HTTPS without breaking ACME challenges

A classic footgun is forcing HTTP→HTTPS in a way that blocks /.well-known/acme-challenge/. Keep it simple.

Serve the challenge path over HTTP. Redirect everything else.

Nginx redirect pattern

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

  location ^~ /.well-known/acme-challenge/ {
    root /var/www/example.com/public;
  }

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

Apache redirect pattern (.htaccess)

Use this only if you already depend on .htaccess. Otherwise, put redirects in the vhost config.

RewriteEngine On
RewriteRule ^\.well-known/acme-challenge/ - [L]
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Step 6: Harden TLS settings (practical defaults)

Modern browsers in 2026 support TLS 1.3 reliably. Aim for a clean baseline.

Use TLS 1.2/1.3 only, reasonable session settings, and config you can maintain.

Nginx: sane TLS baseline

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

If you want a full header + TLS hardening pass without breaking WordPress or control panels, follow: Nginx Security Headers Configuration Tutorial (2026).

Step 7: Automate renewals and reload your web server

Let’s Encrypt certificates are short-lived by design. Your goal isn’t to remember renewals.

Your goal is to make renewals boring and automatic.

Verify renewal scheduling

If you installed via snap, Certbot typically uses a systemd timer:

systemctl list-timers | grep -i certbot

On Debian/apt installs, you might get cron, a systemd timer, or both. This depends on packaging.

Check both places:

systemctl list-timers | grep -i certbot
sudo ls -la /etc/cron.d/ | grep -i certbot

Dry-run a renewal (safe test)

sudo certbot renew --dry-run

Add a deploy hook to reload services

For Nginx/Apache, a reload is usually enough. Restarting can interrupt active connections and is rarely needed.

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-web.sh
#!/bin/sh
set -eu

# Reload whichever is present
systemctl reload nginx 2>/dev/null || true
systemctl reload apache2 2>/dev/null || true
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-web.sh

Step 8: Verify the certificate actually served to visitors

You can have a fresh, valid certificate on disk and still serve an expired one. The usual causes are vhost ordering, a proxy terminating TLS elsewhere, or a service that never reloaded.

Check from outside the server with OpenSSL:

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

Then confirm the expected files exist on disk:

sudo ls -la /etc/letsencrypt/live/example.com/
sudo certbot certificates

SSL certificate deployment tutorial: troubleshooting the failures you’ll actually see

These issues burn time because they feel “random.” They usually aren’t.

Verify routing, DNS, and which vhost is answering first.

Problem: HTTP-01 challenge fails (403/404)

  • Cause: Your rewrite/redirect blocks /.well-known/acme-challenge/.
  • Fix: Add an explicit location/alias for that path on port 80 (see redirect patterns above).
  • Check:
curl -I http://example.com/.well-known/acme-challenge/test

You want a 404 from your webroot (the file doesn’t exist). You don’t want a redirect loop to HTTPS that fails later.

Problem: Challenge fails because the wrong server answers

  • Cause: DNS points to an old IP, a CDN/proxy, or an IPv6 address you didn’t intend.
  • Fix: Correct A/AAAA records, wait for TTL, re-run issuance.

If you’re moving between providers, use a structured cutover plan. It prevents mixed answers during propagation.

HostMyCode can also handle the changeover via migrations.

Problem: Renewal fails because port 80 is closed

  • Cause: A security group/firewall rule was removed, or you tightened UFW and forgot HTTP.
  • Fix: Open 80 temporarily, renew, then decide whether you want DNS-01 for no-HTTP environments.

On-host verification:

sudo ufw status
sudo ss -lntp | grep ':80'

Problem: Browser shows “certificate not trusted” (chain issues)

  • Cause: You configured cert.pem instead of fullchain.pem in the web server.
  • Fix: Use fullchain.pem for ssl_certificate on Nginx/Apache.

Problem: You renewed, but the site still serves the old cert

  • Cause: The wrong vhost is serving the domain, or the service wasn’t reloaded.
  • Fix: Find the vhost that answers, correct server_name/ServerName, then reload.

Nginx mapping check:

sudo nginx -T | grep -n "server_name example.com" -n

Apache mapping check:

sudo apachectl -S | grep -i example.com

If you need deeper failure analysis (ACME challenge routing, broken chain, permission issues), use: TLS certificate renewal troubleshooting tutorial (2026).

Multi-domain and multi-site VPS setup: practical patterns

Once you host more than a couple of sites, consistency beats cleverness. Pick a pattern you can explain quickly.

It should also be easy to reproduce under pressure.

Pattern A: One certificate per site (recommended)

  • Each domain has its own /etc/letsencrypt/live/<domain>/ directory.
  • Vhost config stays easy to reason about.
  • Renewal failures don’t take down unrelated domains.

Pattern B: A single SAN certificate for many domains

  • Fewer cert objects to manage, but the blast radius is larger.
  • Good only if you control all names and they share the same lifecycle.

Pattern C: Wildcard cert (DNS-01)

  • Useful for many subdomains, but requires DNS API or manual TXT updates.
  • Nice for staging environments and internal services.

Quick operational checklist (copy/paste for your runbook)

  • DNS A/AAAA matches the VPS you intend to serve
  • Port 80 reachable from the internet (or DNS-01 configured)
  • Vhost explicitly serves /.well-known/acme-challenge/ on HTTP
  • Certbot installed via one method (snap or apt)
  • certbot renew --dry-run passes
  • Deploy hook reloads Nginx/Apache after renewals
  • External OpenSSL check confirms correct issuer + expiration

Summary: keep SSL boring

On a hosting VPS, the best SSL setup is the one you can redo at 2 a.m. without guessing. That means predictable vhosts, known challenge routing, and renewals you can test safely.

As you add sites, write down your pattern and reuse it.

If you want a stable network, consistent performance, and full control of your stack, start with a HostMyCode VPS. If you’d rather have updates, monitoring, and the common security mistakes handled for you, managed VPS hosting is the cleaner option.

Running multiple domains on one server makes certificate hygiene non-negotiable. HostMyCode’s VPS plans give you full control for clean Nginx/Apache TLS setups, while managed VPS hosting is a good fit if you want renewals and web stack maintenance handled on a predictable schedule.

FAQ

Do I need to keep port 80 open forever for Let’s Encrypt?

If you use HTTP-01 validation, yes—at least during renewals. If you can’t keep port 80 open, use DNS-01 validation (wildcards commonly use this), but it requires DNS TXT automation or manual steps.

Why does certbot renew succeed but my site still shows the old certificate?

Most of the time, the web server wasn’t reloaded, or a different virtual host is answering first. Run an external OpenSSL check, then confirm the domain maps to the vhost you think it does.

Should I use the Nginx/Apache Certbot plugin or webroot mode?

Use webroot if you want full control and minimal config changes. Use the plugin if you run a simple single-site setup and you’re comfortable reviewing any edits Certbot applies.

What’s the safest way to deploy HTTPS on a live site?

Issue the cert first, enable HTTPS in a separate TLS vhost, and verify the served certificate and application behavior. Add the HTTP→HTTPS redirect only after that checks out.