All posts
HTTPS padlock with Let's Encrypt, Nginx, and Apache logos
  • HTTPS
  • SSL
  • Let's Encrypt
  • Certbot
  • Nginx
  • Apache
  • Security

Set Up HTTPS with Let's Encrypt on Nginx and Apache

A complete guide to obtaining and auto-renewing free TLS certificates from Let's Encrypt using Certbot on Ubuntu, for both Nginx and Apache — including HTTP-to-HTTPS redirects, HSTS, and troubleshooting.

3 min read

Let’s Encrypt provides free, automatically renewed TLS certificates. Certbot is the official client that installs the certificate and configures your web server in a single command. This guide covers both Nginx and Apache on Ubuntu 22.04 / 24.04.

Prerequisites

Before you begin:


Nginx: Install Certbot and Obtain a Certificate

1. Install Certbot

sudo apt update
sudo apt install certbot python3-certbot-nginx

2. Obtain and install the certificate

Replace yourdomain.com with your actual domain:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will:

  1. Verify ownership of your domain via an HTTP challenge on port 80
  2. Download and install the certificate
  3. Modify your Nginx config to enable HTTPS on port 443
  4. Add a redirect from HTTP to HTTPS

During the process it asks for an email address (used for expiry notifications) and whether to redirect HTTP traffic.

3. What Certbot adds to your Nginx config

After running Certbot your server block will contain additions similar to:

server {
    listen 443 ssl;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # ... your existing location blocks
}

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

4. Verify the configuration

sudo nginx -t
sudo systemctl reload nginx

Visit https://yourdomain.com in a browser. You should see a valid padlock.


Apache: Install Certbot and Obtain a Certificate

1. Install Certbot

sudo apt update
sudo apt install certbot python3-certbot-apache

2. Enable required Apache modules

sudo a2enmod ssl
sudo a2enmod rewrite
sudo systemctl restart apache2

3. Obtain and install the certificate

sudo certbot --apache -d yourdomain.com -d www.yourdomain.com

Certbot will:

  1. Verify domain ownership
  2. Download the certificate
  3. Create a new SSL virtual host config at /etc/apache2/sites-available/yourdomain.com-le-ssl.conf
  4. Add an HTTP-to-HTTPS redirect to the original virtual host

4. What Certbot creates for Apache

The generated SSL virtual host looks like:

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf

    # ... your existing directives
</VirtualHost>
</IfModule>

And it modifies the HTTP virtual host to add:

RewriteEngine on
RewriteCond %{SERVER_NAME} =yourdomain.com [OR]
RewriteCond %{SERVER_NAME} =www.yourdomain.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

5. Verify

sudo apachectl configtest
sudo systemctl reload apache2

Automatic Certificate Renewal

Let’s Encrypt certificates expire after 90 days. Certbot installs a systemd timer that renews certificates automatically when they are 30 days from expiry.

Check the timer status

sudo systemctl status certbot.timer

You should see active (waiting). The timer runs twice per day.

Test the renewal process

Run a dry run to confirm renewal would succeed without actually modifying anything:

sudo certbot renew --dry-run

A successful dry run outputs Congratulations, all simulated renewals succeeded.

Manual renewal

If you ever need to renew immediately:

sudo certbot renew

Certbot only renews certificates that are within 30 days of expiry. Force renewal regardless:

sudo certbot renew --force-renewal

HTTP Strict Transport Security (HSTS) tells browsers to always use HTTPS for your domain, even if the user types http://. Add it to your SSL server block after you are confident HTTPS is working correctly.

Nginx:

server {
    listen 443 ssl;
    # ... other directives

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Apache (inside the SSL <VirtualHost *:443> block):

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

Enable the headers module in Apache first:

sudo a2enmod headers
sudo systemctl reload apache2

Warning: Once HSTS is active, browsers will refuse HTTP connections for the duration of max-age. Test thoroughly before enabling, and do not set preload unless you intend to submit to the HSTS preload list.


Multiple Domains on One Server

You can issue certificates for multiple domains. Each domain needs its own Certbot command or you can combine them:

# Each domain separately (separate certs)
sudo certbot --nginx -d site-one.com -d www.site-one.com
sudo certbot --nginx -d site-two.com -d www.site-two.com

# Combined into one cert (up to 100 SANs per cert)
sudo certbot --nginx -d site-one.com -d www.site-one.com -d site-two.com -d www.site-two.com

Separate certificates are easier to manage independently. Combined certificates reduce the number of files to track.


Wildcard Certificates

Wildcard certificates cover *.yourdomain.com (all subdomains). They require DNS-based verification rather than HTTP, so you must complete a DNS challenge manually or use a DNS provider plugin.

sudo certbot certonly \
  --manual \
  --preferred-challenges dns \
  -d yourdomain.com \
  -d "*.yourdomain.com"

Certbot will instruct you to add a _acme-challenge TXT record to your DNS. Once you add it, press Enter to continue. Wildcard renewal also requires DNS access, so automation is harder without a supported DNS provider plugin.


Certificate File Locations

After issuance, certificates live at:

/etc/letsencrypt/live/yourdomain.com/
  fullchain.pem   — certificate + intermediate chain (use this in web servers)
  privkey.pem     — private key
  cert.pem        — certificate only (rarely needed directly)
  chain.pem       — intermediate chain only

These are symlinks to the actual versioned files under /etc/letsencrypt/archive/.


Troubleshooting

Error: Could not bind to TCP port 80

Another process is already listening on port 80 (usually your web server). Certbot’s standalone mode needs port 80 free. If you use the --nginx or --apache plugin, Certbot borrows the web server’s port automatically — no conflict.

Connection timed out during challenge

Port 80 is blocked by a firewall. Open it:

sudo ufw allow 80
sudo ufw allow 443
sudo ufw reload

DNS problem: NXDOMAIN

Your domain’s A record hasn’t propagated yet, or it points to the wrong IP. Check with:

dig +short yourdomain.com

Certificate not trusted in browser

You may be testing on the staging environment, or the certificate was issued for a different domain. Check with:

sudo certbot certificates

Rate limits

Let’s Encrypt enforces rate limits (5 duplicate certificates per week per domain). If you hit them, use the staging environment for testing:

sudo certbot --nginx --staging -d yourdomain.com

Staging certificates are not trusted by browsers but do not consume production rate limits.


Important Notes

  • Certificates are valid for 90 days and auto-renewed at 60 days remaining.
  • Always run --dry-run to confirm renewal works before relying on the timer.
  • Never delete files in /etc/letsencrypt/ manually — use certbot delete instead.
  • If you change your server config significantly, re-run Certbot to ensure it can still find and modify the right blocks.
  • HSTS is irreversible within the max-age window — enable it only after your HTTPS setup is stable.

Frequently Asked Questions

Is Let’s Encrypt suitable for production?

Yes. Let’s Encrypt certificates are trusted by all major browsers and are used by millions of production websites. The 90-day validity and automatic renewal are by design — short lifetimes reduce the damage from key compromise.

Can I use Let’s Encrypt with a subdomain?

Yes. Include the subdomain in the -d flag: sudo certbot --nginx -d sub.yourdomain.com. It receives its own certificate (or can be combined with the apex domain in a single certificate).

What happens if the auto-renewal fails?

Let’s Encrypt sends expiry notification emails to the address you registered with Certbot. Certbot also logs renewal attempts to /var/log/letsencrypt/letsencrypt.log. Most failures are due to a misconfigured web server blocking the HTTP challenge — fix the config and run certbot renew manually.

Do I need to open any ports permanently?

Yes — port 443 for HTTPS traffic. Port 80 is needed for the HTTP challenge during issuance and renewal, and for the HTTP-to-HTTPS redirect. Both should remain open.

Can I use Let’s Encrypt with a non-standard port?

The HTTP challenge requires port 80. If your server is behind a firewall or load balancer that terminates on a non-standard port, you may need to use the DNS challenge instead, or configure port-forwarding so that port 80 on the public IP reaches Certbot.