
Most SSL outages don’t come from “bad certificates.” They come from two predictable mistakes: issuing a cert for the wrong hostname, or letting renewal fail quietly.
This Let’s Encrypt setup guide tutorial shows a clean, repeatable way to set up SSL on an Ubuntu VPS with Nginx or Apache. It also makes renewal boring, so you’re not scrambling on day 90.
You’ll install Certbot, confirm DNS and firewall basics, apply a modern TLS baseline, and run a renewal dry-run you can trust.
This guide fits real hosting setups: a single site, multi-domain VPS deployments, and small agency servers.
What you’ll build (and what you need before you start)
By the end, your server will:
- Serve HTTPS for one or more hostnames (Apex +
www, or multiple domains). - Redirect HTTP → HTTPS safely (no redirect loops).
- Renew certificates automatically with systemd timers.
- Run a “dry-run” renewal check you can monitor.
Prerequisites:
- An Ubuntu VPS (Ubuntu 24.04 LTS or newer is ideal) with root or sudo access.
- A domain pointing to your VPS IP (A/AAAA records correct).
- Ports
80and443open to the internet (at least during issuance/renewal).
If you’re still choosing where to run this, a HostMyCode VPS is a solid fit for HTTPS, email, and multi-site hosting.
If you’d rather have patching and security maintenance handled alongside you, use managed VPS hosting instead.
Step 1: Confirm DNS points to the correct server
Let’s Encrypt only issues a certificate after it verifies you control the domain.
Start by confirming each hostname resolves to your VPS IP.
Check the apex domain and any additional names you plan to include (like www).
domain=example.com
# Check A/AAAA
dig +short A $domain
dig +short AAAA $domain
# Check both apex and www if you plan to cover them
dig +short A www.$domain
Expected: The returned IP(s) match your server.
If they don’t, fix DNS at your registrar or DNS provider before you touch Certbot. For domain registration and straightforward DNS management, you can use HostMyCode Domains.
TTL tip for production cutovers: If you’re moving an existing site, drop the TTL a few hours ahead of time (for example, 300 seconds).
Keep the old record handy so rollback takes seconds, not guesses.
Step 2: Open the right ports without weakening the server
HTTP-01 validation needs inbound access to port 80. HTTPS traffic needs port 443.
If you use UFW, allow both explicitly:
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
If you’re locking down a new VPS, don’t guess at firewall rules.
Follow our UFW firewall setup tutorial first. Then come back here.
Step 3: Install Certbot the right way on Ubuntu (no mystery builds)
On Ubuntu, the safest route is the packaged Certbot plus the plugin for your web server.
Update APT, then install the packages for your stack.
sudo apt update
# For Nginx
sudo apt install -y certbot python3-certbot-nginx
# For Apache (use this instead if you run Apache)
# sudo apt install -y certbot python3-certbot-apache
Confirm what you installed:
certbot --version
In 2026, plenty of older guides still copy/paste deprecated flags. Don’t.
Follow the current CLI output and use the plugin that matches your web server.
Step 4 (Nginx): Issue and install the certificate in one pass
Before you request a certificate, confirm Nginx is running.
Also confirm it serves the domain over plain HTTP.
sudo systemctl status nginx --no-pager
curl -I http://example.com
If you can’t connect, stop and fix Nginx or your firewall rules first.
Certbot can’t validate what the internet can’t reach.
Now issue the certificate and let Certbot update your server block:
sudo certbot --nginx -d example.com -d www.example.com
During the prompts:
- Use a real email address. It’s your last line of warning if renewal breaks.
- Choose the redirect option (HTTP → HTTPS) unless you deliberately need HTTP.
Where files land:
- Certificates:
/etc/letsencrypt/live/example.com/ - Renewal configs:
/etc/letsencrypt/renewal/
Reload Nginx, then verify what’s being served:
sudo nginx -t
sudo systemctl reload nginx
# Confirm the certificate chain and hostname match
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Step 4 (Apache): Issue and install the certificate with the Apache plugin
On Apache, the flow is the same.
First confirm it’s listening on port 80 and serving the right virtual host.
sudo systemctl status apache2 --no-pager
curl -I http://example.com
Then issue the certificate and let Certbot update your vhost config:
sudo certbot --apache -d example.com -d www.example.com
Reload and confirm Apache is happy with the config:
sudo apachectl configtest
sudo systemctl reload apache2
Step 5: Fix the two most common causes of failed issuance
Most issuance failures come from the same few problems.
Run through this list before you retry Certbot.
- DNS points elsewhere:
dig +short A example.comdoesn’t match your VPS. - Port 80 blocked: firewall, security group, or upstream provider rule blocks inbound HTTP.
- Wrong virtual host / server block: the domain serves a default site, not your vhost.
- Redirect loop: a global HTTP→HTTPS redirect exists before the challenge location is reachable.
If you suspect a redirect loop or vhost confusion, back off aggressive redirects temporarily. Then retry Certbot.
On Nginx, dump the active config and search for your domain:
sudo nginx -T | sed -n '1,200p'
Find the block with server_name example.com www.example.com;.
Confirm it listens on 80. Also confirm it doesn’t force HTTPS before the ACME challenge can be served.
Step 6: Set a modern TLS baseline (without breaking clients)
Certbot gets you HTTPS. It doesn’t guarantee sensible TLS defaults.
Keep the baseline simple: TLS 1.2/1.3, no odd cipher tuning, and a few safe headers.
On Nginx, Certbot may generate snippets for you. You can also maintain a small baseline include.
Create /etc/nginx/snippets/ssl-params.conf:
sudo nano /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# OCSP stapling helps latency and improves some client checks
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Basic hardening headers (adjust per app)
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;
Then include it in your HTTPS server block:
include /etc/nginx/snippets/ssl-params.conf;
If you want a deeper header set that stays compatible with common apps (including WordPress), follow our Nginx security headers configuration tutorial.
Step 7: Make renewal boring (systemd timer + a real test)
On Ubuntu, Certbot usually installs a systemd timer that runs twice per day.
Verify the timer exists and is active:
systemctl list-timers | grep -E 'certbot|letsencrypt'
systemctl status certbot.timer --no-pager
Next, run a dry-run renewal. This is the fastest way to prove renewals will work later.
sudo certbot renew --dry-run
What “success” looks like: Certbot completes the challenge and reports no errors.
If it fails, treat it like an incident you can fix in daylight.
The failures that happen quietly at 2 a.m. are the ones that turn into tickets.
If you want a focused playbook for the issues you’ll actually run into (blocked HTTP-01, broken chain, wrong webroot, stale DNS), keep this internal guide close: TLS certificate renewal troubleshooting tutorial.
Step 8: Multi-site VPS? Issue separate certs cleanly
On a multi-site VPS, resist the urge to bundle unrelated domains into one certificate.
It makes troubleshooting harder. It also increases the blast radius of a single bad vhost change.
Issue one certificate per site:
# Site 1
sudo certbot --nginx -d site-one.com -d www.site-one.com
# Site 2
sudo certbot --nginx -d site-two.com -d www.site-two.com
See what Certbot is managing:
sudo certbot certificates
Pitfall: If two server blocks both declare the same server_name, Certbot may edit the wrong file.
Remove duplicates, reload Nginx, then re-run Certbot.
Step 9: If you can’t expose port 80, use DNS validation (manual, but workable)
Some environments keep port 80 closed. Others route HTTP somewhere other than your origin.
In either case, HTTP-01 won’t validate.
DNS-01 works without opening ports. It’s often manual unless your DNS provider supports API automation.
Provider plugins vary, so here’s the universal manual approach:
sudo certbot certonly --manual --preferred-challenges dns -d example.com -d www.example.com
Certbot will ask you to create a TXT record like this:
_acme-challenge.example.com TXT "random-token"
After you add it, confirm it has propagated:
dig +short TXT _acme-challenge.example.com
Reality check: Manual DNS validation won’t auto-renew unless you repeat the process each time.
If you need hands-off renewal, keep port 80 reachable for the origin or use a DNS provider with supported automation.
Step 10: Quick checklist for SSL troubleshooting on a VPS
When a browser throws a certificate warning, work the basics in order.
Don’t guess.
- Confirm the hostname: Is the user visiting
wwwbut you only issuedexample.com? - Confirm the serving IP: Is DNS pointing at the right VPS, or an old shared hosting box?
- Check the active certificate:
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates - Check the web server config test:
- Nginx:
sudo nginx -t - Apache:
sudo apachectl configtest
- Nginx:
- Check renewal logs:
/var/log/letsencrypt/letsencrypt.log
If you run cPanel and need to troubleshoot AutoSSL (which fails differently than Certbot), bookmark our cPanel-specific guide: cPanel AutoSSL troubleshooting tutorial.
Step 11: Operational hardening that pays off (small changes, fewer incidents)
A handful of habits will cut SSL-related tickets fast:
- Put SSL expiry into monitoring: alert if a cert has less than 14 days remaining.
- Log your renewal attempts: dry-run monthly via cron/systemd and alert on non-zero exit.
- Keep HTTP-01 path reachable: don’t blanket-block
/.well-known/unless you know why. - Document where certs live: note
/etc/letsencrypt/live/and the vhost paths for each site.
For a practical monitoring baseline (uptime, resource alerts, and log checks), see our server monitoring tutorial.
Summary: a repeatable SSL setup you can trust
On a VPS, HTTPS should behave the same way every time.
Confirm DNS first. Keep ports 80/443 reachable (or choose DNS validation on purpose).
Install Certbot with the right plugin and treat renewal as part of the deployment.
Once --dry-run renewals pass, SSL stops being a quarterly fire drill.
If you want a stable platform for multiple domains, renewals, and routine maintenance, start with a HostMyCode VPS.
If you’d rather offload updates, hardening, and recovery planning, managed VPS hosting keeps the ops load lighter while you keep control of your apps.
If you deploy SSL across several client sites, infrastructure quality shows up fast: disk performance, network consistency, and clean admin access matter. HostMyCode offers VPS plans for single sites through multi-tenant stacks, plus managed VPS hosting if you want help with updates, hardening, and recovery planning.
FAQ
Do I need both example.com and www.example.com on the certificate?
If users can reach both hostnames, yes. Add both -d example.com -d www.example.com. Then redirect one to the other so you don’t serve the same site from two URLs.
How do I confirm auto-renewal is actually running?
Check systemctl status certbot.timer and run sudo certbot renew --dry-run.
Also review /var/log/letsencrypt/letsencrypt.log to confirm recent renewal attempts.
Why does Let’s Encrypt fail even though my site loads?
Your browser can load the site over HTTPS while HTTP-01 validation still fails.
The usual culprits are a blocked port 80, a redirect loop, or DNS still pointing at an older server.
Should I use Certbot or cPanel AutoSSL?
If you run cPanel/WHM, AutoSSL is usually simpler because it integrates with accounts and vhosts.
On a plain Nginx/Apache VPS, Certbot is the cleaner default.
Where are the certificate files stored on Ubuntu?
Look under /etc/letsencrypt/live/your-domain/. That directory contains symlinks to the current full chain and private key used by your web server.