
Most HTTPS outages aren’t caused by “bad certificates.” They usually come from one of three issues:
- The certificate is installed on the wrong virtual host.
- Port 80 is blocked, so ACME validation can’t complete.
- Renewals succeed, but the web server never reloads.
This SSL certificate setup guide tutorial walks through a clean Let’s Encrypt install on a VPS (Nginx or Apache). It also covers renewal automation and the fixes you’ll use when browsers still complain.
The steps below assume Ubuntu 24.04 LTS or Debian 12/13 on a VPS or dedicated server.
If you host multiple client sites on the same box, pay attention to the guardrails. They help prevent one site’s config from breaking everything else.
Before you start: a 10-minute preflight checklist
Run these checks first. They catch the “it worked in my terminal” problems before you burn time on Certbot.
- DNS points to the right IP: Your domain’s A/AAAA records must resolve to this server.
- Ports open: TCP 80 and 443 must be reachable from the internet. Check both the cloud firewall and UFW.
- Time is correct: A skewed clock can break ACME validation and trigger TLS warnings.
- You can identify the right vhost/server block: Critical on multi-site servers where defaults can “catch” traffic.
Quick commands:
# Confirm DNS
getent ahosts example.com
# Confirm ports from the server perspective (listeners)
sudo ss -lntp | egrep ':80|:443'
# Confirm system time
timedatectl status
If you’re still finishing DNS for a move, use a low-TTL cutover.
Test before you flip traffic.
HostMyCode has a clear walkthrough here: DNS cutover tutorial.
Need a server for this tutorial? A small VPS can handle a few sites comfortably.
You can resize later.
Start with a HostMyCode VPS if you want root access and predictable performance.
Pick your method: Certbot with Nginx/Apache plugin vs webroot
You’ve got two sensible options:
- Plugin method (recommended): Certbot updates your Nginx/Apache vhost, can set redirects, and typically wires in reload behavior.
- Webroot method: Certbot writes ACME challenge files into a directory you choose. This keeps server configs untouched.
On a multi-tenant box (agency/reseller), webroot is usually calmer and more predictable.
If you run one or two sites and you’re comfortable with Certbot edits, the plugin method is faster.
SSL certificate setup guide tutorial for Nginx (Ubuntu/Debian)
This section issues a Let’s Encrypt certificate for Nginx using Certbot’s packaged plugin.
On Ubuntu/Debian in 2026, the distro package plus the Nginx plugin is still the least-friction option.
1) Install Certbot and the Nginx plugin
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
certbot --version
2) Confirm your Nginx server block matches the domain
Open your site config (common paths):
/etc/nginx/sites-available/example.com/etc/nginx/conf.d/example.com.conf
Make sure your domain appears in server_name:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
}
Then validate and reload:
sudo nginx -t
sudo systemctl reload nginx
3) Request the certificate and enable HTTPS
sudo certbot --nginx -d example.com -d www.example.com
During the prompts:
- Use a real email so you actually receive expiry notices.
- Pick the redirect option only if the site behaves correctly over HTTPS.
4) Verify the certificate is live
sudo certbot certificates
# Check from the server
curl -I https://example.com
# Inspect the negotiated certificate
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Common pitfall: A “default” Nginx site catches the hostname.
If Certbot attaches the cert to the wrong server block, disable the default config:
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
SSL certificate setup guide tutorial for Apache (Ubuntu/Debian)
Apache works well with Certbot.
The key is a correct ServerName and a reachable HTTP vhost. Without both, validation can’t complete.
1) Install Certbot and the Apache plugin
sudo apt update
sudo apt install -y certbot python3-certbot-apache
certbot --version
2) Ensure required Apache modules are enabled
sudo a2enmod ssl headers rewrite
sudo systemctl reload apache2
3) Confirm your vhost exists for port 80
Typical file path:
/etc/apache2/sites-available/example.com.conf
Minimal example:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
Enable the site if needed:
sudo a2ensite example.com.conf
sudo apachectl configtest
sudo systemctl reload apache2
4) Request the certificate and configure HTTPS
sudo certbot --apache -d example.com -d www.example.com
5) Verify Apache is presenting the correct certificate
curl -I https://example.com
sudo certbot certificates
Common pitfall: Multiple vhosts exist and the wrong one wins due to load order.
Check with:
sudo apachectl -S
Webroot method (safer on multi-site servers)
If you don’t want Certbot editing server configs, use webroot.
Point Certbot at your DocumentRoot. It then drops ACME challenge files under /.well-known/acme-challenge/.
Nginx or Apache:
sudo certbot certonly --webroot \
-w /var/www/example.com/public \
-d example.com -d www.example.com
Then reference the certificate paths in your web server config.
Nginx TLS snippet example:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Apache TLS snippet example:
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
Practical check: Your site must serve /.well-known/acme-challenge/ from the same host being validated.
Aggressive rewrite rules (or redirects to a different hostname) often break validation.
Automate renewals (and make sure the server reloads)
On Ubuntu/Debian, Certbot usually sets up a systemd timer for renewals.
Don’t assume it’s there. Verify it.
Then run a dry-run renewal to confirm the full path works.
systemctl list-timers | grep -i certbot || true
# Dry run renewal (safe)
sudo certbot renew --dry-run
If renewal succeeds but browsers still show the previous certificate, suspect a missing reload.
Add a deploy hook so Nginx/Apache reloads only after a successful renewal:
# Nginx reload 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 reload hook (use this instead if Apache)
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
Quick diagnostic: If you run both Apache and Nginx (reverse proxy), reload the service that terminates TLS.
That’s often Nginx, not the backend.
Troubleshooting: fix the 7 HTTPS problems you’ll actually see
This section is deliberately practical.
These failures show up in real ops work and support queues.
1) ACME challenge fails: “Connection refused” or “Timeout”
- Port 80 blocked (cloud firewall, security group, or UFW).
- Domain points to the wrong IP (old server still in DNS).
- Reverse proxy sends
/.well-known/somewhere else.
# Confirm firewall
sudo ufw status verbose || true
# Quick reachability check from an external network is best,
# but from the server you can at least confirm listeners:
sudo ss -lntp | egrep ':80|:443'
If you want a clean firewall baseline on Ubuntu/Debian, follow: VPS firewall setup guide.
2) Wrong certificate served (multi-site mix-up)
This is almost always a vhost selection problem.
The “default” site catches the hostname. The browser then receives a certificate for a different domain.
Nginx:
sudo nginx -T | grep -n "server_name" | head
Apache:
sudo apachectl -S
Fix it by ensuring the correct vhost contains the right server_name/ServerName.
Also disable unused default sites.
3) “Too many redirects” after enabling HTTPS
Redirect loops usually come from duplicate redirects.
You might have one at the web server and another inside the app (often WordPress).
A proxy that doesn’t forward the original scheme correctly can also trigger loops.
- If you use a reverse proxy, ensure the backend sees
X-Forwarded-Proto: https. - In WordPress, confirm
siteurlandhomeare HTTPS.
For a fast, targeted checklist, use: HTTPS redirect troubleshooting tutorial.
4) Mixed content warnings (page loads, but “Not secure” appears)
The page is on HTTPS, but some assets (images, scripts, fonts) still load over HTTP.
Browser dev tools will show exactly which requests are failing.
- Replace hard-coded HTTP URLs in your CMS theme and content.
- Enable HSTS only after you’re sure the host and key subdomains work over HTTPS.
5) Chain errors: “NET::ERR_CERT_AUTHORITY_INVALID” or missing intermediates
With Let’s Encrypt, chain issues usually happen because the server presents cert.pem instead of fullchain.pem.
- Nginx should use
fullchain.pemforssl_certificate. - Apache should use
fullchain.pemforSSLCertificateFile.
6) Renewal succeeded, but the cert still shows the old expiry
Assume “no reload” first.
The other common cause is TLS termination at a load balancer or CDN. In that case, you’re checking the edge certificate, not the origin.
- Verify your deploy hook reloads the right service.
- If you use a CDN, confirm whether the origin cert or edge cert is the one users see.
7) You’re migrating servers and HTTPS breaks on cutover day
Treat SSL as part of the migration plan, not a last-minute checkbox.
You can export/import certificates, but you must also move the private key and chain files.
After that, confirm renewal still works on the new host.
If you’re planning a move, follow: VPS SSL certificate migration tutorial.
Hardening basics: TLS settings that won’t break modern clients
You don’t need a custom cipher spreadsheet.
You do need clean protocol choices.
Also enable HTTP/2 (and HTTP/3 where it makes sense), plus headers you can maintain without constant exceptions.
- Prefer TLS 1.2 and 1.3 (disable older protocols).
- Enable HTTP/2 on Nginx/Apache where possible for better parallelism.
- Turn on HSTS carefully after confirming HTTPS works on the host and key subdomains.
If you’re running cPanel/WHM with Apache or LiteSpeed, put security headers in a central include.
Don’t hand-edit them across vhosts. Use: cPanel security headers setup guide.
What changes on cPanel/WHM, Plesk, and DirectAdmin?
Control panels make issuance and renewals easier.
But the failure modes don’t change.
DNS still has to be right, ports must be reachable, and the certificate must land on the correct vhost.
- cPanel/WHM: AutoSSL handles issuance/renewal for hosted domains. Failures usually come down to DNS, validation routing, or broken vhost mapping.
- Plesk: The Let’s Encrypt extension is straightforward. Pay attention to proxy rules and additional domains/aliases.
- DirectAdmin: The Let’s Encrypt integration is reliable, but on multi-IP servers you must bind the correct IP to each domain.
If you run WHM, keep this link nearby for the usual renewal failures: cPanel AutoSSL troubleshooting.
Operational checklist (print this before you touch production)
- Lower DNS TTL before major changes (especially migrations).
- Open 80/443 at both the cloud firewall and OS firewall layers.
- Confirm your vhost/server block matches the domain and isn’t shadowed by a default site.
- Use
fullchain.pem(notcert.pem) in web server configs. - Run
certbot renew --dry-runafter initial issuance. - Add a deploy hook so the correct service reloads after renewal.
- Document where certificates live and who receives expiry emails.
If you’d rather spend time running your sites than chasing renewal quirks, put them on a VPS built for day-to-day admin. HostMyCode offers flexible VPS plans and hands-off help with patching and routine ops through managed VPS hosting.
FAQ
Do I need port 80 open if I only want HTTPS?
Yes for most Let’s Encrypt HTTP-01 validations.
DNS-01 avoids port 80, but it requires DNS automation.
For typical hosting, leave port 80 open and redirect everything to HTTPS.
Should I force HTTPS redirects immediately?
If the site loads cleanly over HTTPS and you’ve checked for mixed content, go ahead.
If you’re mid-migration or still testing, enable HTTPS first. Add redirects after you’re satisfied.
Why does the certificate look different from one network to another?
You may be seeing a CDN/edge certificate, a different backend server, or stale DNS.
Confirm which IP you’re reaching and where TLS is terminated (proxy, load balancer, or origin).
What’s the safest way to handle SSL during a server migration?
Issue certificates on the new server before cutover (preferred), then switch DNS with low TTL.
If you have to move existing keys, migrate the private key and full chain carefully. Then verify renewals on the new host.
Summary: a reliable HTTPS setup you can maintain
You now have Let’s Encrypt working on Nginx or Apache.
You’ve also tested renewals and added a reload hook.
That hook prevents the classic “renewed but still expired” surprise.
On multi-site servers, webroot is usually the safer default.
It avoids cross-vhost config edits.
On larger stacks, let your control panel handle automation.
Spend your time on monitoring and clean migrations.
If you’re rebuilding or consolidating hosting in 2026, run these steps on a staging VPS first. Then repeat them in production.
HostMyCode can provision the platform quickly and keep it stable as you grow, whether you choose a self-managed HostMyCode VPS or go with managed VPS hosting for ongoing operations.