
Most “HTTPS issues” on a VPS aren’t certificate problems. They usually come from the wrong vhost answering your domain. Other common causes are DNS pointing at an old IP, or renewals failing quietly until the cert expires.
This VPS SSL setup guide tutorial walks through a clean Let’s Encrypt install with Certbot on Ubuntu/Debian. You’ll get separate steps for Nginx and Apache. You’ll also run checks that keep renewals boring (in a good way).
For production, start from a stable baseline. That makes problems easier to diagnose.
A HostMyCode VPS gives you full control over firewall rules, the web stack, and DNS timing. That control makes SSL troubleshooting far less guessy.
What you’ll set up (and what you need first)
You’ll finish with:
- A valid Let’s Encrypt certificate for your domain (and optional
www) - Correct Nginx or Apache configuration for HTTPS
- HTTP → HTTPS redirect (without redirect loops)
- Automatic renewal with a tested dry-run
- Basic post-SSL verification (chain, headers, and renew timers)
Prerequisites:
- Ubuntu 24.04/24.10+ or Debian 12/13 on a VPS (commands below assume systemd)
- A domain pointing to your VPS IPv4 (and IPv6 if you serve it)
- Ports 80 and 443 open in your firewall/security group
- Root access or a sudo user
Before you install Certbot, confirm DNS and plain HTTP reachability. If you skip this, you’ll chase “validation errors” that are really DNS propagation or a blocked port.
Step 1: Verify DNS points to the right server
From your laptop (or any resolver), check A/AAAA records:
dig +short A example.com
dig +short AAAA example.com
# Optional: check www too
dig +short A www.example.com
Then confirm your VPS responds on port 80.
Let’s Encrypt HTTP-01 validation uses port 80 unless you choose DNS-01:
curl -I http://example.com
If curl times out, fix the firewall or web server first.
If you’re planning DNS changes and want to avoid downtime, follow HostMyCode’s cutover flow: DNS TTL reduction tutorial and then the DNS cutover checklist.
Step 2: Install Certbot (Ubuntu/Debian)
Update packages first:
sudo apt update
sudo apt -y upgrade
Install Certbot plus the plugin that matches your web server:
# For Nginx
sudo apt -y install certbot python3-certbot-nginx
# For Apache
sudo apt -y install certbot python3-certbot-apache
Tip: If you’re not sure which web server is listening, check ports 80/443:
sudo ss -ltnp | egrep ':80|:443'
Step 3A: Issue and install the certificate (Nginx)
First, make sure your Nginx config is valid and reloadable. Certbot’s installer mode can’t work around a broken configuration.
sudo nginx -t
sudo systemctl reload nginx
Request a certificate (replace the domains with yours):
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask for an email and agreement to the terms. It will also ask if you want an HTTP → HTTPS redirect. If you don’t need any HTTP-only endpoints, choose the redirect.
After it completes, review what changed. Nginx site files commonly live in:
/etc/nginx/sites-available//etc/nginx/sites-enabled/
You’re looking for listen 443 ssl; and certificate paths like:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Quick sanity check:
curl -I https://example.com
Step 3B: Issue and install the certificate (Apache)
Apache needs mod_ssl and a working vhost.
Enable the modules, confirm the config, then reload:
sudo a2enmod ssl headers rewrite
sudo apachectl configtest
sudo systemctl reload apache2
Request and install the certificate:
sudo certbot --apache -d example.com -d www.example.com
Apache vhosts are typically in:
/etc/apache2/sites-available//etc/apache2/sites-enabled/
After Certbot runs, you should see a new SSL vhost (often named -le-ssl.conf). You should also see a redirect in the port 80 vhost.
Verify:
curl -I https://example.com
Step 4: Force HTTPS the safe way (avoid loops)
Redirect loops usually show up when:
- You redirect at both the load balancer/CDN and the origin without passing the right headers
- Your app forces HTTPS but the proxy terminates SSL and talks HTTP to the backend
- You have multiple conflicting rewrite rules
On a simple single-server VPS (no proxy), Certbot’s redirect is usually enough.
If you run a reverse proxy, decide where TLS terminates. Then redirect only once.
If Nginx sits in front of Apache, keep the proxy behavior consistent with your design: Reverse proxy setup tutorial.
Nginx redirect example (port 80 server block):
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Apache redirect example (inside port 80 vhost):
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Step 5: Test your certificate chain and TLS basics
A browser can look “fine” while specific clients fail. Chain issues and outdated TLS behavior are common causes.
Check directly from the VPS:
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | openssl x509 -noout -issuer -subject -dates
You want:
- Correct subject CN/SANs (your domain)
- Valid dates
- A known issuer chain
If you want modern TLS hardening (protocols, ciphers, OCSP stapling, HSTS), do it after you confirm basic HTTPS works. That keeps your first round of troubleshooting focused.
Start here: TLS hardening tutorial, then add policy headers using Security headers setup guide.
Step 6: Confirm automatic renewals actually run
On Ubuntu/Debian in 2026, Certbot usually installs a systemd timer. Confirm it exists and is active:
systemctl list-timers | grep -E 'certbot|letsencrypt'
systemctl status certbot.timer
Next, run a dry-run renewal. Many people skip this step. Then they discover a broken renewal 60–90 days later.
sudo certbot renew --dry-run
If the dry-run fails, don’t guess. Read the exact error and map it to the usual causes:
- Port 80 blocked (firewall, provider ACL, or iptables rules)
- Wrong DNS (stale A record, incorrect AAAA, or CDN pointing elsewhere)
- Web server mismatch (you requested with
--nginx, later switched to Apache) - Validation hits a different vhost (default site responds instead of your domain)
If you need a full “why is renewal failing?” workflow, follow: SSL renewal troubleshooting tutorial.
Step 7: Add a post-renew hook (reload Nginx/Apache)
Certbot often reloads your web server automatically. On custom setups, it’s safer to be explicit.
Use a deploy hook so the reload runs only after a successful renewal.
Nginx hook:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Apache hook:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload apache2
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh
Re-run the dry-run to make sure the hooks don’t error:
sudo certbot renew --dry-run
Step 8: Lock down the firewall (keep 80/443, close the rest)
HTTPS won’t save you if the box exposes services you don’t need. Keep the rule set tight.
With UFW:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
If you manage iptables directly, build a hosting-safe baseline that won’t break mail/DNS on multi-role servers: IPTables firewall configuration tutorial.
Step 9: Quick diagnostics for common SSL problems on a VPS
Less theory, more “what do I run next?”
Problem: Certificate issued, but browser shows the wrong site
- Check which vhost answers your domain:
curl -I http://example.com
curl -I https://example.com
- For Nginx, list loaded server blocks:
sudo nginx -T 2>/dev/null | grep -n "server_name" | head
- For Apache, list enabled sites and vhosts:
ls -1 /etc/apache2/sites-enabled/
sudo apachectl -S
Problem: “Too many redirects”
- Check if you redirect at multiple layers (CDN + origin + app)
- If behind a proxy, ensure your app sees the original scheme via
X-Forwarded-Proto
Problem: Renewal fails with HTTP-01 challenge errors
- Confirm port 80 is reachable from the public internet
- Confirm DNS A/AAAA still points to this VPS
- Look at Certbot logs:
sudo tail -n 200 /var/log/letsencrypt/letsencrypt.log
Problem: Mixed content warnings after enabling HTTPS
This is almost always hardcoded http:// asset URLs (themes, CSS, scripts). Fix it in the application.
In WordPress, set the Site URL to HTTPS and update embedded links.
Step 10: If you run cPanel/WHM, don’t use Certbot on the host
On cPanel servers, let WHM manage certificates via AutoSSL. It understands the account/vhost layout. It also renews without colliding with cPanel’s config.
If AutoSSL renewals fail, use this workflow: cPanel AutoSSL troubleshooting.
If you’re deciding whether you even want a panel, keep it practical. Panels can save time on shared hosting and reseller servers. You pay for that convenience with cost and extra moving parts.
For small fleets, a clean Nginx/Apache + Certbot setup is often simpler to reason about.
Checklist: production-ready HTTPS on a VPS
- DNS A/AAAA records match the VPS IP(s)
- Ports 80 and 443 reachable externally
- Cert issued for apex + www (or you intentionally skipped www)
- HTTP redirects exactly once (no loops)
certbot renew --dry-runsucceeds- Systemd timer exists and is active
- Web server reload hook present if needed
- Optional: TLS hardening + security headers after baseline works
Summary: keep renewals boring, keep troubleshooting fast
Let’s Encrypt on a VPS stays reliable if you treat it like plumbing. Get DNS right. Keep port 80 reachable. Make vhosts unambiguous. Trust only what passes a renewal dry-run.
Do that, and SSL stops turning into a recurring fire drill.
If you want predictable SSL behavior without fighting resource limits, start on a plan that fits your workload. A HostMyCode VPS works well for single-site and multi-site stacks. And managed VPS hosting is there when you’d rather hand off patching, monitoring, and baseline hardening.
If you’re moving a site to a VPS or rebuilding your stack around HTTPS, HostMyCode keeps the basics clean: stable virtual machines, predictable networking, and migration help when you need it. Pick a HostMyCode VPS for full control, or choose managed VPS hosting if you want the admin work handled for you.
FAQ
Do I need to keep port 80 open if my site is HTTPS-only?
Yes, in most cases. Let’s Encrypt HTTP-01 validation uses port 80. You can switch to DNS-01 validation if you must close port 80, but it adds DNS automation work.
Should I include both example.com and www.example.com on the certificate?
If you serve both, yes. If you only want one canonical hostname, still consider covering both and redirecting one to the other to prevent edge-case warnings.
How do I confirm renewals will keep working?
Run sudo certbot renew --dry-run, confirm a systemd timer exists, and make sure your DNS won’t be changed by a later migration without updating records.
What’s the fastest way to debug a renewal failure?
Check DNS (dig), confirm port 80 reachability (curl -I http://domain), then read /var/log/letsencrypt/letsencrypt.log for the exact validation error.
Is Certbot safe to use with a reverse proxy or CDN?
Yes, but decide where HTTPS terminates and avoid double redirects. If you terminate TLS at a CDN and proxy HTTP to origin, make sure the origin vhost and app respect forwarded scheme headers.