How to turn on HTTPS with Let's Encrypt on Nginx or Apache
HTTPS keeps passwords, forms and cookies private on the way to your server. Let's Encrypt gives you the certificate for free, and certbot sets it up and renews it for you.
Before you start
- Your domain points at the server. The
Arecord (andAAAAif you use IPv6) forexample.comandwww.example.commust show your server's address. Check withdig +short example.com(install it withsudo apt install dnsutilsif it's missing). - Ports 80 and 443 are open. With UFW:
sudo ufw allow 80/tcpandsudo ufw allow 443/tcp. Keep port 80 open: Let's Encrypt checks your domain over it, and it sends visitors to HTTPS. See our UFW guide. - The site already works over plain HTTP, with a
server_name(Nginx) orServerName(Apache) line for your domain. Certbot uses that line to find the right site.
1. Install certbot
Use the package from Ubuntu or Debian, with the plugin for your web server. For Nginx:
sudo apt update
sudo apt install certbot python3-certbot-nginx
For Apache:
sudo apt update
sudo apt install certbot python3-certbot-apache
2. Get the certificate
Name every address the site uses, with and without www. For Nginx:
sudo certbot --nginx -d example.com -d www.example.com
For Apache:
sudo certbot --apache -d example.com -d www.example.com
Certbot asks for an email address and asks you to accept the terms. Let's Encrypt uses the email only for urgent notices. It stopped sending "your certificate expires soon" emails in 2025.
When it says Congratulations! You have successfully enabled HTTPS, open https://example.com in a browser. You should see the lock.
3. Check that HTTP goes to HTTPS
Current certbot versions (2.9 on Ubuntu 24.04, 4.0 on Debian 13) add this redirect for you. Test it:
curl -I http://example.com/some/page
You should see 301 Moved Permanently and a Location: line starting with https://. If you get 200, run the certbot command from step 2 again with --redirect at the end.
4. Fix "not fully secure" warnings
If the lock has a warning, the page still loads images or scripts over http://. Change your site's own address to https://:
- WordPress: Settings → General, change both WordPress Address and Site Address to
https://. - Laravel: set
APP_URL=https://example.comin.env, then runphp artisan config:cacheif you cache your config. - Other apps: look for a "base URL" or "site URL" setting.
5. Make sure renewal works
Let's Encrypt certificates last 90 days today. That drops to 64 days in February 2027 and 45 days in 2028. So renewal must run on its own. The certbot package adds a timer that checks twice a day. Make sure it is on:
sudo systemctl enable --now certbot.timer
systemctl list-timers certbot.timer
The NEXT column should show a time. Then do a practice renewal. It uses Let's Encrypt's test server and changes nothing:
sudo certbot renew --dry-run
You want Congratulations, all simulated renewals succeeded. After a real renewal, certbot reloads Nginx or Apache, so the new certificate is used right away.
To see your certificates and how many days they have left:
sudo certbot certificates
To fix a renewal that fails, and get a warning before a certificate expires, see how to make sure your HTTPS certificates renew.
6. Add HSTS (optional, once everything works)
HSTS tells browsers to always use HTTPS for your site, even if someone types http://. Browsers remember it, so start with a short time (5 minutes) and raise it after a week with no problems.
Nginx: add this line inside the server { } block that has listen 443 ssl:
add_header Strict-Transport-Security "max-age=300" always;
Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
If a location block has its own add_header lines, Nginx ignores the server-level ones there, so add the line to that block too.
Apache: turn on the headers module, then add the line inside the <VirtualHost *:443> block. Certbot puts that block in a file ending in -le-ssl.conf in /etc/apache2/sites-available/.
sudo a2enmod headers
Header always set Strict-Transport-Security "max-age=300"
sudo apache2ctl configtest && sudo systemctl reload apache2
Check it with curl -I https://example.com: you should see a Strict-Transport-Security line. When you're happy, change 300 to 31536000 (one year). Only add includeSubDomains if every subdomain has HTTPS too. Leave out preload unless you're sure: it is hard to undo.
To add the other security headers and hide your server's version numbers, see how to add security headers on Nginx or Apache.
If something goes wrong
- "Timeout during connect" or "Connection refused": Let's Encrypt can't reach port 80. Check the firewall on the server and in your hosting provider's control panel.
- "NXDOMAIN" or the wrong IP address: the DNS record is missing or still points somewhere else. DNS changes can take a while.
- Certbot says it deployed the certificate to
sites-enabled/default: it didn't find your site, so it changed the default site instead. Add aserver_nameline for the domain to your own site's file, reload Nginx, and run certbot again. - Too many attempts: Let's Encrypt limits failed tries. Fix the problem first, and test with
--dry-runbefore you try for real again. - You write your own web server config:
sudo certbot certonly --webrootonly gets the certificate and leaves your files alone. Then pointssl_certificateorSSLCertificateFileat/etc/letsencrypt/live/example.com/fullchain.pemyourself.
Official sources
- Getting started (Let's Encrypt)
- User guide (certbot docs)
- 64-day certificates coming February 2027 (Let's Encrypt)
- Expiration emails have ended (Let's Encrypt)
- Rate limits (Let's Encrypt)
- add_header (Nginx docs) and mod_headers (Apache docs)
- Strict-Transport-Security (MDN)
Next steps: test your site with our free site checker, then work through the rest of the security checklist for a new PHP server.
ServerNest is being built to do this for you: a free Let's Encrypt certificate for each site, the redirect to HTTPS and HSTS as simple switches, and a check that renewal is running. Join early access to try it first.