By Brian Boucheron and Vinayak Baranwal
Securing your Nginx server with HTTPS not only protects data but also boosts SEO rankings and user trust.
Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free TLS/SSL certificates, thereby enabling encrypted HTTPS on web servers. It makes it easier with a software client, Certbot, which tries to automate whatever (or everything) it can. Today, the whole procedure of acquiring and installing a certificate is automated at all steps on Apache and Nginx.
In this tutorial, you’ll learn how to secure Nginx with Let’s Encrypt on Ubuntu using Certbot, enabling free SSL, HTTPS redirects, HSTS, and automated certificate renewal.
This tutorial will use a separate Nginx server configuration file instead of the default file. We recommend creating a new Nginx server block for each domain. This approach helps prevent common mistakes and keeps the default configuration as a fallback.
[info] Let DigitalOcean worry about managing Nginx and Let’s Encrypt. DigitalOcean App Platform will let you deploy directly from GitHub in minutes. App Platform will also handle SSL certs and routing for you.
To follow this tutorial, you will need:
example.com
pointing to your server’s public IP address.www.example.com
pointing to your server’s public IP address./etc/nginx/sites-available/example.com
as an example.certbot renew --dry-run
, giving you peace of mind that your certificates will always stay up to date.The first step to using Let’s Encrypt to obtain an SSL certificate is to install the Certbot software on your server.
Install Certbot and it’s Nginx plugin with apt:
sudo apt install certbot python3-certbot-nginx
Certbot is now ready to use, but in order for it to automatically configure SSL for Nginx, we need to verify some of Nginx’s configuration.
Certbot needs to be able to find the correct server block in your Nginx configuration for it to be able to automatically configure SSL. Specifically, it does this by looking for a server_name
directive that matches the domain you request a certificate for.
If you followed the server block set up step in Nginx installation, you should have a server block for your domain at /etc/nginx/sites-available/example.com
with the server_name
directive already set appropriately.
To check, open the configuration file for your domain using nano or your favorite text editor:
sudo nano /etc/nginx/sites-available/example.com
Find the existing server_name
line. It should look like this:
...
server_name example.com www.example.com;
...
If it does, exit your editor and move on to the next step.
If it doesn’t, update it to match. Then save the file, quit your editor, and verify the syntax of your configuration edits:
sudo nginx -t
If you get an error, reopen the server block file and check for any typos or missing characters. Once your configuration file’s syntax is correct, reload Nginx to load the new configuration:
sudo systemctl reload nginx
Certbot can now find the correct server block and update it automatically.
Next, let’s update the firewall to allow HTTPS traffic.
Before we can enable HTTPS, the firewall must allow secure traffic. If you’re using UFW, adjust its rules to include HTTPS so that encrypted connections can pass through.
If you have the ufw firewall enabled, as recommended by the prerequisite guides, you’ll need to adjust the settings to allow for HTTPS traffic. Luckily, Nginx registers a few profiles with ufw upon installation.
You can see the current setting by typing:
sudo ufw status
It will probably look like this, meaning that only HTTP traffic is allowed to the web server:
OutputStatus: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx HTTP ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx HTTP (v6) ALLOW Anywhere (v6)
To additionally let in HTTPS traffic, allow the Nginx Full profile and delete the redundant Nginx HTTP profile allowance:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
Your status should now look like this:
sudo ufw status
OutputStatus: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
Next, let’s run Certbot and fetch our certificates.
Certbot provides a variety of ways to obtain SSL certificates through plugins. The Nginx plugin will take care of reconfiguring Nginx and reloading the config whenever necessary. To use this plugin, type the following:
sudo certbot --nginx -d example.com -d www.example.com
This runs certbot with the --nginx
plugin, using -d
to specify the domain names we’d like the certificate to be valid for.
If this is your first time running certbot, you will be prompted to enter an email address and agree to the terms of service. After doing so, certbot will communicate with the Let’s Encrypt server, then run a challenge to verify that you control the domain you’re requesting a certificate for.
If that’s successful, certbot will ask how you’d like to configure your HTTPS settings.
OutputPlease choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
Select your choice then hit ENTER. The configuration will be updated, and Nginx will reload to pick up the new settings. certbot will wrap up with a message telling you the process was successful and where your certificates are stored:
OutputIMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2020-08-18. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
By default, Certbot stores certificates in /etc/letsencrypt/live/
and updates your Nginx server block to use fullchain.pem
(certificate + chain) and privkey.pem
(private key).
You can confirm the configuration by running:
sudo nginx -T | grep ssl_
This will display the SSL directives currently in use by Nginx.
When you run Certbot with the --nginx
flag, it analyzes your Nginx configuration to locate the server blocks that correspond to the domains you provided. Certbot then automatically modifies these server blocks to enable SSL/TLS by inserting the required HTTPS directives. If you opt for HTTP-to-HTTPS redirection, Certbot also configures Nginx to forward all HTTP requests to HTTPS. This process ensures that all visitors access your site securely over an encrypted connection.
The process is seamless: Certbot handles the insertion of SSL certificate paths, private key references, and can also add security enhancements like HTTP Strict Transport Security (HSTS) headers if you opt in. This automation saves you from manually editing configuration files and helps prevent common mistakes.
The diagram below illustrates how, after Certbot’s changes, all HTTP requests are redirected to HTTPS, and HSTS can be enabled to further enforce secure connections:
Before troubleshooting or customizing your configuration, it’s useful to understand some core SSL/TLS concepts that Certbot and Let’s Encrypt rely on:
Certificate
A digital file issued by a Certificate Authority (CA) like Let’s Encrypt that proves your server’s identity and enables encrypted HTTPS connections. It contains your domain name, the issuing CA, and validity dates, among other metadata.
Private Key
A secret cryptographic key stored securely on your server (e.g., /etc/letsencrypt/live/example.com/privkey.pem
). It’s used to establish secure communications and must never be shared publicly. If compromised, your certificate’s security is broken.
Certificate Chain (Intermediate Certificates)
Links your server certificate to the root certificate trusted by browsers. Certbot installs the fullchain.pem
file, which includes your server certificate and all necessary intermediates.
ACME Challenge Types
Let’s Encrypt uses the ACME protocol to verify domain ownership before issuing a certificate. Common challenge types include:
http://yourdomain/.well-known/acme-challenge/…
which Let’s Encrypt fetches over port 80 to verify control.Understanding these concepts will help you interpret Certbot’s behavior, diagnose issues, and make informed decisions when configuring SSL/TLS on your server.
When you run sudo certbot --nginx ...
, Certbot parses your existing Nginx server block(s) to locate the one matching your server_name
. It then injects SSL/TLS directives and, if selected, an HTTP-to-HTTPS redirect.
For example, a typical server block before running Certbot might look like:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
}
After running Certbot with the redirect option, it will be modified to:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
}
Add the following directive to enforce HTTP Strict Transport Security (HSTS), which tells browsers to only connect over HTTPS and preload this setting for subdomains:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Key changes Certbot makes:
ssl_certificate
and ssl_certificate_key
pointing to the Let’s Encrypt certificate and private key.options-ssl-nginx.conf
(predefined strong SSL settings).ssl-dhparams.pem
for Diffie-Hellman key exchange security.Certbot also reloads Nginx to apply these changes immediately.
Tip: After enabling HTTPS, consider restricting your server to TLS 1.2+ only, disabling weak ciphers, and enabling OCSP stapling to further boost your SSL Labs score.
For a detailed walkthrough on strong SSL/TLS configurations, see Mozilla’s SSL Configuration Generator for Nginx.
Your certificates are downloaded, installed, and loaded. Try reloading your website using https://
and notice your browser’s security indicator. It should indicate that the site is properly secured, usually with a lock icon. If you test your server using the SSL Labs Server Test, it will get an A grade.
Even with the Nginx plugin, you might encounter errors when issuing or renewing Let’s Encrypt certificates. Here are common problems and how to fix them:
sudo certbot --nginx --staging -d example.com -d www.example.com
dig +short example.com
dig +short www.example.com
sudo ufw allow 80
sudo ufw allow 443
server_name
matches exactly and no conflicting blocks exist.sudo less /var/log/letsencrypt/letsencrypt.log
This file contains the full ACME conversation and can pinpoint why a request failed.
For more troubleshooting guidance, see the official Let’s Encrypt documentation: https://letsencrypt.org/docs/.
Pick the ACME challenge that fits your setup and constraints:
Aspect | HTTP‑01 | DNS‑01 |
---|---|---|
Use cases | Most single‑domain sites | Wildcards, behind CDN/proxy, no port 80 |
Port requirements | Port 80 reachable | No HTTP port required |
Works behind CDN/proxy | Often problematic | Yes (validates via DNS) |
Automation | Simple with --nginx |
Requires DNS API plugin |
Propagation delays | None | DNS TTL/propagation can delay |
example.com
, www.example.com
).Example (HTTP‑01 with Nginx plugin):
sudo certbot --nginx -d example.com -d www.example.com
*.example.com
).Example (manual DNS‑01 for wildcard):
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d '*.example.com'
Example (DNS‑01 with a DNS provider plugin, e.g., Cloudflare):
sudo apt install python3-certbot-dns-cloudflare
sudo certbot --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cf.ini \
-d example.com -d '*.example.com'
Tip: For DNS‑01 automation, use your DNS provider’s Certbot plugin (Cloudflare, Route 53, DigitalOcean, etc.) so TXT records are created and cleaned up automatically.
Let’s finish by testing the renewal process.
Let’s Encrypt’s certificates are only valid for ninety days. This is to encourage users to automate their certificate renewal process. The certbot package we installed takes care of this for us by adding a systemd timer that will run twice a day and automatically renew any certificate that’s within thirty days of expiration.
You can query the status of the timer with systemctl:
sudo systemctl status certbot.timer
Output● certbot.timer - Run certbot twice daily
Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Mon 2020-05-04 20:04:36 UTC; 2 weeks 1 days ago
Trigger: Thu 2020-05-21 05:22:32 UTC; 9h left
Triggers: ● certbot.service
To test the renewal process, you can do a dry run with certbot:
sudo certbot renew --dry-run
If you see no errors, you’re all set. When necessary, Certbot will renew your certificates and reload Nginx to pick up the changes. If the automated renewal process ever fails, Let’s Encrypt will send a message to the email you specified, warning you when your certificate is about to expire.
Yes! Let’s Encrypt is a public Certificate Authority that provides free SSL/TLS certificates trusted by all major browsers. There are no fees for issuance or renewal. You can automate the process with Certbot on Ubuntu and Nginx. Donations are optional and help support the project.
Let’s Encrypt certificates are valid for 90 days. This short lifetime encourages automation and limits risk if a private key is compromised. Certbot on Ubuntu sets up a systemd timer to renew certificates automatically. You can test renewal with sudo certbot renew --dry-run
.
Yes. You can use multiple -d
flags with Certbot to create a SAN (Subject Alternative Name) certificate for several domains (e.g., example.com
, www.example.com
). For wildcard domains (e.g., *.example.com
), use the DNS-01 challenge. Example:
sudo certbot --nginx -d example.com -d www.example.com
First, check DNS propagation (e.g., dig example.com
). Make sure ports 80 (HTTP) and 443 (HTTPS) are open (sudo ufw allow 80 443
). Confirm your Nginx server_name
matches the domain. If you’re behind a CDN or proxy, temporarily bypass it or use the DNS-01 challenge. Certbot logs are at /var/log/letsencrypt/letsencrypt.log
.
Certbot installs a systemd timer on Ubuntu that runs twice daily to check and renew certificates. You can verify the timer with sudo systemctl status certbot.timer
. No manual intervention is needed unless renewal fails.
Yes. Certbot is configured to reload Nginx automatically after a successful renewal, so your site will use the new certificate without downtime. You can customize this behavior with Certbot hooks if needed.
Let’s Encrypt enforces rate limits to ensure fair use. For example, you can issue up to 50 certificates per registered domain per week. There are also limits for failed validations and duplicate certificates. See Let’s Encrypt Rate Limits for details.
Let’s Encrypt requires your server to be publicly accessible on port 80 (HTTP) or 443 (HTTPS) for HTTP-01 or TLS-ALPN-01 challenges. If your server is behind a firewall or not publicly accessible, use the DNS-01 challenge to prove domain ownership.
Check Certbot’s logs at /var/log/letsencrypt/letsencrypt.log
for detailed error messages. Common issues include DNS misconfiguration, closed ports, or incorrect Nginx server blocks. Tools like dig
, curl
, and nginx -t
can help diagnose problems.
Absolutely. Let’s Encrypt certificates are trusted by all major browsers and are widely used in production. Ensure you automate renewals, monitor expiry, and follow best practices for Nginx security (such as enabling HSTS and keeping your server updated).
To secure Nginx on Ubuntu with free SSL, install Certbot, configure HTTPS, enable HSTS, and verify automated renewals.
You have now successfully secured Nginx with Let’s Encrypt on Ubuntu using Certbot. Your site is running on free SSL, with HTTPS enforced, HSTS enabled, and certificates set to renew automatically. To further enhance security, check out the DigitalOcean Nginx config generator, or for a hands-on prose guide, refer to the Nginx Security Hardening Guide by SecopSolution.
If you have further questions about using Certbot, the official documentation is a good place to start.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Senior Technical Writer at DigitalOcean
Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
@bboucheron, I deleted the server and the DNS record that was having letsencript. After installing a new server, and setting up the same domain name I tried to install letsencript again and have the following error;
"An unexpected error occurred: The server experienced an internal error :: Error retrieving account “https://acme-v02.api.letsencrypt.org/acme/acct/95574462”
Do you know why please?
Hi
Can any one help with below requests: I have an app running on ubuntu 20.04 and nginx on http://example.com:9999
I want to migrate all of the urls (http://example.com:9999 and http://example.com:9999/x/x to https://example.com and https://example.com/x/x
Can anyone pls help?
Hello, i have an issue with my website. I have installed wordpress with LEMP following this tutorial https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-with-lemp-on-ubuntu-18-04 and setted up my certificate with this tutorial. My problem is now my website can’t be accessed if i type “mydomain.com”. If i type “mydomain.com” and then click on the browser’s bar to see the url it shows me “https://www.mydomain.com” and if i erase the “https://www.” my website loads correctly but if i check the browser’s bar again it shows me "https://mydomain.com/
It’s kinda confusing because if i type on the console:
sudo ufw status
It shows me
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
Additionally I changed the General settings of wordpress “WordPress Address (URL)” and “Site Address (URL)” to “https://mydomain.com”
Also i have updated my Cloudflare’s dns records to:
A www MY IP A mydomain.com MY IP
I have to say that i don’t have a CNAME on my records.
Can you help me please?
sudo certbot renew --dry-run
command is not working anymore. You will get something like:
Could not choose appropriate plugin: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.')
Attempting to renew cert (xxxx.com) from /etc/letsencrypt/renewal/xxx.com.conf produced an unexpected error: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.'). Skipping.
This article neglects to mention that, prior to running sudo certbot --nginx -d example.com -d www.example.com
you should create a folder called .well-known at the home directory. Otherwise you’ll get a message along the lines of:
IMPORTANT NOTES:
- The following errors were reported by the server:
Domain: example.com
Type: dns
Detail: During secondary validation: Invalid response from
http://www.example.com [xxx.xx.xxx.xxx]: 404
Domain: www.example.com
Type: dns
Detail: During secondary validation: Invalid response from
http://www.example.com/.well-known/acme-challenge/xxxxxxxxxxxxxxxxxxx
[xxx.xx.xxx.xxx]: 404
I was able to obtain the initial cert without issue, but the dry run in the last step fails (my domain replaced with example.com):
Attempting to renew cert (*example.com*) from /etc/letsencrypt/renewal/*example.com*.conf produced an unexpected error: HTTPSConnectionPool(host='acme-staging-v02.api.letsencrypt.org', port=443): Max retries exceeded with url: /directory (Caused by SSLError(SSLError("bad handshake: SysCallError(104, 'ECONNRESET')"))). Skipping.
All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/*example.com*/fullchain.pem (failure)
I can ping acme-staging-v02.api.letsencrypt.org and have tried disabling ufw but get the same error.
Im having trouble with the step 4, anyone can help me, this is what I got:
root@KatzeCommunity:/# sudo certbot --nginx -d katzecommunity.net -d www.katzecommunity.net
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for katzecommunity.net
http-01 challenge for www.katzecommunity.net
Waiting for verification...
Challenge failed for domain katzecommunity.net
Challenge failed for domain www.katzecommunity.net
http-01 challenge for katzecommunity.net
http-01 challenge for www.katzecommunity.net
Cleaning up challenges
Some challenges have failed.
IMPORTANT NOTES:
- The following errors were reported by the server:
Domain: katzecommunity.net
Type: unauthorized
Detail: Invalid response from
http://katzecommunity.net/.well-known/acme-challenge/S4wuYcayDEV88smz7q2NBOzTJ4h3ZeZQI8P2Gvg81Rs
[159.65.216.130]: "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n
<meta charset=\"utf-8\"/>\n <title>Katze Community</title>\n
<base href=\"/\"/>\n "
Domain: www.katzecommunity.net
Type: unauthorized
Detail: Invalid response from
http://www.katzecommunity.net/.well-known/acme-challenge/YS_jGkFICDdcgVswBdPp5jLmVU2Zy1urIUDUYYNFy2Y
[159.65.216.130]: "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n
<meta charset=\"utf-8\"/>\n <title>Katze Community</title>\n
<base href=\"/\"/>\n "
To fix these errors, please make sure that your domain name was
entered correctly and the DNS A/AAAA record(s) for that domain
contain(s) the right IP address.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.