How to enable SSL with Let's Encrypt on a DigitalOcean VPS

How to enable SSL with Let’s Encrypt on a DigitalOcean VPS

We may earn an affiliate commission through purchases made from our guides and tutorials.

Let’s Encrypt is a free, automated Certificate Authority backed by the non-profit ISRG. It speaks ACME — a protocol your server uses to prove “I control this domain” and then fetch a signed TLS certificate. Certbot (from EFF) is the workhorse client that handles the ACME dance and can even edit your Nginx/Apache config for you. On Ubuntu, the recommended way to install Certbot is via snap, which keeps it auto-updated and sets up a renewal timer out of the box.

If you’ve never done this on a DigitalOcean Droplet before, relax. It’s mostly DNS, a couple of firewall tweaks, and one or two Certbot runs.

Prerequisites (don’t skip these)

  • A Droplet you can SSH into as a sudo user.
  • Your domain’s A (and AAAA if you use IPv6) records pointing to the Droplet’s IP. Propagation can take minutes to hours.
  • A running web server (Nginx or Apache).
  • Port 80 (HTTP) and 443 (HTTPS) open — either with UFW on the Droplet and/or DigitalOcean Cloud Firewalls.
  • Nginx server blocks or Apache vhosts already serving your site over HTTP.

Step 1 — Install Certbot (snap method)

On Ubuntu 20.04/22.04/24.04, use snap. This keeps Certbot current and avoids the stale apt packages you’ll see in random blog posts.

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

This is the path recommended by Certbot/EFF; don’t mix install methods (apt + snap + pip is asking for weird conflicts).

Step 2 — Open the firewall for HTTPS (UFW example)

If you’re using UFW, allow the right profile for your web server:

# For Nginx:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'  # optional: keep only HTTPS open, HTTP will still be used for challenge

# For Apache:
sudo ufw allow 'Apache Full'
sudo ufw delete allow 'Apache'      # optional

DigitalOcean’s docs use the same approach in their Nginx/Apache guides.

Step 3A — Get a certificate on Nginx (zero-downtime, auto-config)

If your site already serves plain HTTP on Nginx and the server_name is correct, Certbot can fetch the cert and edit your Nginx config automatically:

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

Certbot will ask for an email (for expiry notices), TOS, and whether to redirect HTTP to HTTPS. Say yes to the redirect; you can always refine later.

Where it goes: Certs land in /etc/letsencrypt/live/yourdomain/. Certbot writes SSL directives to your Nginx server block and reloads Nginx for you (no downtime).

Step 3B — Get a certificate on Apache (auto-config)

Same idea on Apache:

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

Certbot updates the VirtualHost(s) and reloads Apache.

Web server not listening yet? Use standalone (temporary ACME server)

Shut down Nginx/Apache just for issuance (port 80 must be free), then:

sudo systemctl stop nginx    # or: sudo systemctl stop apache2
sudo certbot certonly --standalone -d example.com -d www.example.com
sudo systemctl start nginx   # or: sudo systemctl start apache2

DigitalOcean documents this approach too; it’s handy for first-time setups or API backends behind a reverse proxy. Later, point your server config at the issued files in /etc/letsencrypt/live/....

Need a wildcard (e.g., *.example.com)? Use DNS-01

Wildcards require the DNS-01 challenge. Either use --manual --preferred-challenges dns and add a TXT record by hand during issuance, or install a Certbot DNS plugin for your DNS provider (Cloudflare, Route 53, etc.) and let it automate the TXT dance:

# Example with Cloudflare plugin (after installing the plugin and setting env vars):
sudo certbot -a dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cf.ini \
  -d example.com -d "*.example.com" --agree-tos -m you@example.com --non-interactive

Let’s Encrypt’s docs are explicit: wildcard issuance = DNS-01. Certbot’s docs list DNS plugins and usage.

Step 4 — Test and verify

It’s time to test the live domain.

Check Nginx/Apache config syntax, then reload

# Nginx
sudo nginx -t
sudo systemctl reload nginx

# Apache
sudo apachectl configtest
sudo systemctl reload apache2

Confirm the cert on the wire

Open your site in a browser (https://example.com) — or use curl -I https://example.com to see the 200/301 and TLS handshake details in your browser’s developer tools.

Step 5 — Auto-renew (and how to know it’s working)

With the snap install, a systemd timer (snap.certbot.renew.timer) runs twice daily and renews when certs are <30 days from expiry. You normally don’t enable anything by hand. Verify with:

sudo systemctl list-timers | grep certbot

You should see snap.certbot.renew.timer queued and snap.certbot.renew.service as the target. If you’re paranoid (good), do a dry run:

sudo certbot renew --dry-run

Step 6 — (Optional but smart) Add HSTS once you’re confident

HSTS tells browsers “always use HTTPS for this site.” Only add this after everything — including subdomains — truly works over HTTPS, or you can lock out real users.

Nginx (inside the HTTPS server block):

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

Apache (inside the SSL VirtualHost):

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

Start with a smaller max-age if you’re nervous, then raise it. (HSTS is a commitment; be deliberate.)

Step 7 — Routine operations

A few useful tips and tricks.

Add or remove domains on an existing cert

sudo certbot --nginx -d example.com -d www.example.com -d blog.example.com
# or:
sudo certbot --apache -d ...

Certbot will ask if you want to expand/replace. The renewal timer keeps working for the updated set.

Force a reload on renewal (if you terminate TLS elsewhere)

If you’re not using the Nginx/Apache installer plugin (e.g., you used certonly), add a deploy hook so services reload after renewal:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
echo '#!/bin/sh
systemctl reload nginx || systemctl reload apache2 || true' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload.sh

Watch out for rate limits (especially during testing)

If you’re iterating quickly and hit rate limits, switch to Let’s Encrypt staging (untrusted but unlimited for testing), then flip back to production once your flow is solid.

Troubleshooting that saves a weekend

  • HTTP-01 challenge failing
    Usually DNS or vhost mismatch. Make sure server_name/ServerName matches the -d names and that port 80 reaches your Droplet (no stray LB or firewall rule blocking it). DigitalOcean’s step-by-step Nginx/Apache guides are good cross-checks.
  • Mixed content warnings after HTTPS
    Your pages still pull http:// assets. Fix the site/app URLs (CMS settings, base tags, reverse proxy headers) and/or add a Content-Security-Policy upgrade-insecure-requests rule temporarily while you clean house.
  • Renewals “mysteriously” not happening
    If you used snap, don’t invent your own cron. Confirm the timer exists and last/next run time:
    systemctl list-timers | grep certbot and journalctl -u snap.certbot.renew.service. Dry run: certbot renew --dry-run.
  • You mixed apt and snap installs
    Pick one (snap is recommended). Remove the other before continuing to avoid path/timer conflicts.

Apache and Nginx TLS hardening: quick wins once HTTPS is active

For both servers, the aim is to enforce modern TLS while keeping the configuration minimal. Certbot manages certificate renewal, so you only need to reference its files.

Nginx
Add the following inside the server {} block of your site configuration, usually found in /etc/nginx/sites-available/ (symlinked to /etc/nginx/sites-enabled/).

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

After saving, reload Nginx with systemctl reload nginx.

Apache
Insert the following in your site’s <VirtualHost *:443> block, typically in /etc/apache2/sites-available/ (then enabled with a2ensite):

SSLEngine on
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

Then reload Apache with systemctl reload apache2.

Note
Certbot manages renewal of /etc/letsencrypt/live/... paths, so you don’t need to update these files manually.

One-page Nginx example (from HTTP to HTTPS with redirect)

# /etc/nginx/sites-available/example.com

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;

    # Let’s Encrypt HTTP-01 challenge will hit here automatically
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    root /var/www/example.com/public;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Optional: HSTS once you’re confident
    # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

# Redirect all HTTP to HTTPS (Certbot can add this for you)
server {
    listen 80;
    listen [::]:80;
    server_name _;
    return 301 https://$host$request_uri;
}

That’s the whole job: install Certbot the right way, issue once, confirm auto-renew, and don’t get fancy until you’ve run a clean dry run. If anything fights you, it’s almost always DNS or a firewall rule — fix those and Certbot behaves.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *