
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 coverwwwif 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_nameincludes 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:
- Create
/var/www/secondsite.tld/public - Create
/etc/nginx/sites-available/secondsite.tld - Enable and reload
- 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-runand confirm Nginx reload still succeeds. - After Nginx changes: run
sudo nginx -tbefore reload. - After DNS changes: verify with
digfrom 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.