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.

The one thing to check: certificates expire after a few weeks. Getting one is easy. Make sure renewal works on its own (step 5), because nobody will remind you.

Before you start

  • Your domain points at the server. The A record (and AAAA if you use IPv6) for example.com and www.example.com must show your server's address. Check with dig +short example.com (install it with sudo apt install dnsutils if it's missing).
  • Ports 80 and 443 are open. With UFW: sudo ufw allow 80/tcp and sudo 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) or ServerName (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.com in .env, then run php artisan config:cache if 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 a server_name line 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-run before you try for real again.
  • You write your own web server config: sudo certbot certonly --webroot only gets the certificate and leaves your files alone. Then point ssl_certificate or SSLCertificateFile at /etc/letsencrypt/live/example.com/fullchain.pem yourself.

Official sources

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.

← Back to the security blog