
SSL problems rarely show up in your dashboard first. You usually find out through a browser warning, a failed webhook, or an email client that won’t connect.
This Let's Encrypt setup guide tutorial shows you how to issue, install, and automatically renew certificates on an Ubuntu VPS for both Nginx and Apache. The same commands and verification steps work across most servers.
For a clean production baseline, start with a VPS where you control DNS, firewall rules, and exact web stack versions. A HostMyCode VPS works well when you need predictable performance plus root access for Certbot, Nginx/Apache, and systemd timers.
What you’ll set up (and what you’ll avoid breaking)
- Issue a Let’s Encrypt certificate using HTTP-01 (webroot) or the Nginx/Apache plugin
- Force HTTPS with safe redirects (no loops)
- Enable automatic renewals and verify they work
- Harden TLS basics (protocols, stapling, HSTS where appropriate)
- Troubleshoot the top renewal failures: blocked port 80, wrong DNS, and broken vhosts
Assumptions: Ubuntu Server 24.04 LTS or 22.04 LTS, a public IPv4/IPv6, and a domain name pointing at your server.
If you’re in the middle of a DNS move, follow this DNS migration tutorial first. Otherwise, you may validate against the old IP and chase “random” failures.
Step 1: Confirm DNS and open the right ports
Let’s Encrypt must reach your server from the public internet. Before you install anything, confirm the basics.
Check A/AAAA records
domain=example.com
dig +short A $domain
dig +short AAAA $domain
The IPs you get back must match your VPS. If they don’t, fix DNS before touching Certbot.
If you recently changed records, use this DNS propagation troubleshooting guide to see what resolvers actually return.
Verify ports 80 and 443 are reachable
Run these from your laptop (not from the server):
curl -I http://example.com
curl -I https://example.com
If port 80 is blocked, HTTP-01 validation fails. With UFW you typically need:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
If you’re worried about locking yourself out, keep a second SSH session open while you change firewall rules.
For deeper diagnosis, keep this firewall troubleshooting tutorial nearby.
Step 2: Install Certbot the right way on Ubuntu
On Ubuntu, the snap package is the most reliable option in 2026. It also avoids older distro packages that can lag behind Let’s Encrypt and ACME changes.
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
certbot --version
You should see a recent Certbot version (the exact number changes over time). On hardened servers, make sure snap services can run. Also confirm outbound HTTPS is allowed.
Let’s Encrypt setup guide tutorial: Nginx certificate + auto HTTPS
Nginx is simplest when you let Certbot update the server block. Certbot handles the ACME challenge path, adds certificate paths, and can enable redirects.
Step 3A: Install Nginx and confirm your server block
sudo apt install -y nginx
sudo nginx -t
sudo systemctl enable --now nginx
Confirm you have a server block for the domain. Typical paths:
/etc/nginx/sites-available/example.com- Symlink to
/etc/nginx/sites-enabled/
Minimal HTTP config (before SSL):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ =404;
}
}
Step 3B: Issue and install the cert with the Nginx plugin
sudo certbot --nginx -d example.com -d www.example.com
During the prompts:
- Use a real admin email address. Renewal and incident notices go there.
- Enable the redirect option for public sites (which is most sites).
Certbot usually places files here:
/etc/letsencrypt/live/example.com/fullchain.pem/etc/letsencrypt/live/example.com/privkey.pem
Step 3C: Confirm HTTPS and the full chain
curl -I https://example.com
# Confirm the certificate chain and SNI
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates
If you hit a redirect loop, you almost always have competing redirects (app + Nginx, or CDN + origin). Remove duplicates and keep one source of truth.
Apache SSL on Ubuntu: issue the cert without breaking vhosts
Apache works well with Certbot. Two issues show up repeatedly: duplicate vhosts and a missing ServerName.
Step 4A: Install Apache and required modules
sudo apt install -y apache2
sudo a2enmod ssl headers rewrite
sudo apachectl configtest
sudo systemctl enable --now apache2
Step 4B: Ensure your HTTP vhost is correct
Typical file path:
/etc/apache2/sites-available/example.com.conf
Basic vhost (HTTP):
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
</VirtualHost>
sudo a2ensite example.com.conf
sudo apachectl configtest
sudo systemctl reload apache2
Step 4C: Issue and install the cert with the Apache plugin
sudo certbot --apache -d example.com -d www.example.com
Once it finishes, Certbot will add a new *:443 vhost (or update an existing one). It will also set the certificate paths.
Step 4D: Verify the live SSL vhost and headers
sudo apachectl -S
curl -I https://example.com
If Apache serves the default site instead of your domain, apachectl -S will usually reveal vhost ordering issues or a missing ServerName.
Step 5: Use the webroot method (cleanest for custom configs)
If you don’t want Certbot touching your configs, use --webroot. This is also safer if you’ve hand-tuned Nginx/Apache and don’t want automated edits.
For Nginx or Apache, make sure the HTTP vhost serves /.well-known/acme-challenge/ from the same document root. Then run:
sudo certbot certonly --webroot \
-w /var/www/example.com/public \
-d example.com -d www.example.com
After that, reference the certificate files manually in your SSL vhost/server block.
Step 6: Force HTTPS safely (and avoid redirect loops)
Redirect loops waste hours and can take a site down during a migration. Keep enforcement in one place: your app, your web server, or your CDN edge. Don’t do all three.
Nginx redirect (HTTP → HTTPS)
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Apache redirect (HTTP → HTTPS)
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
If you’re behind Cloudflare or another proxy that terminates TLS, enforce redirects at the edge. Or configure your origin to respect X-Forwarded-Proto. Otherwise, you can bounce between HTTP and HTTPS indefinitely.
For Nginx, see Nginx real IP configuration to restore real client IP logging once you’re proxied.
Step 7: Enable and verify auto-renewal (don’t assume)
Let’s Encrypt certificates are short-lived. Renewals must be automatic. You also need to prove they work.
Confirm the systemd timer
systemctl list-timers | grep -E 'certbot|letsencrypt' || true
systemctl status snap.certbot.renew.timer
With snap installs, renewals usually run through a timer. Next, simulate the renewal flow:
sudo certbot renew --dry-run
If the dry run fails, fix it immediately. Real renewals often fail overnight, and you’ll notice only after users do.
Reload your web server after renewals
Certbot often installs a deploy hook for you. Still, verify it instead of guessing.
List renewal configs:
sudo ls -1 /etc/letsencrypt/renewal/
If you need an explicit deploy hook, add one:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
# Nginx reload hook
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
For Apache, swap nginx to apache2.
Step 8: Practical TLS hardening that won’t break clients
You don’t need exotic cipher tweaks. You need consistent settings, a valid chain, and behavior you can predict during renewals.
Nginx: baseline SSL settings
In 2026, TLS 1.2 and 1.3 are the baseline for public sites. Add this inside your SSL server block:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# OCSP stapling (requires a valid resolver)
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
ssl_stapling on;
ssl_stapling_verify on;
HSTS: use it only after you’re sure
HSTS tells browsers to stick to HTTPS for a long time. That’s useful, but it can also lock you into HTTPS before everything is ready.
Start with a short window:
add_header Strict-Transport-Security "max-age=86400" always;
After a week of clean renewals and no mixed-content warnings, increase max-age. Avoid includeSubDomains unless every subdomain is HTTPS-ready.
Step 9: Common Let’s Encrypt failures and quick fixes
Most certificate failures fall into a few predictable categories. Use the same checklist each time to diagnose them quickly.
Problem: “Timeout during connect (likely firewall problem)”
- Open port 80 publicly (not just in UFW, but at the provider firewall if present).
- Confirm Nginx/Apache is listening on
:80:sudo ss -lntp | grep ':80' - Check security groups / upstream firewalls.
Problem: “Invalid response from http://example.com/.well-known/…”
- Your vhost is pointing to the wrong document root.
- A rewrite rule is intercepting
/.well-known/. - You have a proxy rule that routes everything to an app that returns 404.
Fix by adding an explicit challenge location in Nginx:
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/example.com/public;
}
Problem: “Too many redirects”
- Disable one layer of redirects (app, Nginx/Apache, or CDN).
- If you’re behind a proxy, ensure it sends
X-Forwarded-Protoand your app honors it.
Problem: Renewal works, but users still see an old certificate
- Your web server didn’t reload after renewal.
- You have multiple vhosts, and the wrong one is active.
- A load balancer or CDN is caching the certificate (edge termination).
Step 10: Multi-site servers and wildcard certificates (practical guidance)
On a VPS with multiple domains, issue certificates per domain. That keeps renewals scoped. It also keeps troubleshooting straightforward.
Wildcard certificates (*.example.com) require DNS-01 validation. This helps when you have lots of subdomains, but it ties renewals to DNS automation.
If your provider doesn’t support API updates, you’ll update TXT records manually on every renewal. For most sites, standard -d example.com -d www.example.com certificates are the most reliable choice.
Operational checklist: what to document after you finish
- Domain(s) covered by each certificate
- Web server and config path (Nginx:
/etc/nginx/sites-available/, Apache:/etc/apache2/sites-available/) - Renewal method (plugin vs webroot)
- Deploy hook in use (reload service name)
- Where you will receive renewal failure alerts (email, monitoring)
If you want alerts before users see warnings, add basic uptime and certificate-expiry monitoring. Use this server monitoring tutorial to set up notifications before expirations turn into incidents.
Summary: stable SSL is mostly process, not magic
Let’s Encrypt is dependable if your DNS is correct, port 80 is reachable, and your web server reloads after renewals. Run certbot renew --dry-run after any major web server change. You’ll catch most failures early.
If you’re building a new hosting environment or consolidating multiple sites, start with infrastructure that gives you clean networking and predictable performance. A managed VPS hosting plan from HostMyCode can also handle routine patching and baseline service reliability while you keep control of your web stack.
Need SSL that renews reliably across multiple sites and domains? Run your stack on a HostMyCode VPS, or choose managed VPS hosting if you want the maintenance handled and the certificates staying current without babysitting.
FAQ
Do I need port 80 open if my site is HTTPS-only?
For HTTP-01 validation, yes. Let’s Encrypt must reach http://yourdomain/.well-known/acme-challenge/. You can keep port 80 open but redirect all traffic to HTTPS.
Can I issue certificates for a server’s IP address?
No. Let’s Encrypt issues certificates for domain names, not bare IPs. Point a domain (A/AAAA) to the server, then request the cert for that hostname.
Why did renewal fail after I enabled a new redirect?
Redirects can break the ACME challenge path or create loops. Make sure /.well-known/acme-challenge/ stays reachable over plain HTTP and returns a 200 during validation.
Is Certbot the only option in 2026?
No. You can use acme.sh or built-in integrations in some control panels. Certbot is still a solid choice on Ubuntu because it’s widely supported and easy to validate with dry runs.
What’s the safest way to test renewals?
Run sudo certbot renew --dry-run, then confirm your web server reload hook works by checking the certificate dates with openssl s_client.