Securing Nginx with HTTPS: A Step-by-Step Guide
Learn how to secure your Nginx web server with HTTPS in this comprehensive step-by-step guide. We cover obtaining free SSL/TLS certificates from Let's Encrypt using Certbot, configuring Nginx for encrypted connections, and implementing essential security measures like HSTS. Protect your data, build user trust, and improve SEO with a properly configured HTTPS setup.
Securing Nginx with HTTPS: A Step-by-Step Guide
Securing Nginx with HTTPS is usually a small job, but it is one of those jobs where small mistakes are loud. A missing certificate file stops Nginx from reloading. A forgotten firewall rule makes the site look down. A rushed HSTS header can lock users into a broken HTTPS setup longer than you intended.
The good news is that the normal path is simple. You point DNS at the server, make sure Nginx answers on port 80, use Certbot to request a Let's Encrypt certificate, test the generated Nginx config, reload, then verify renewal. That is the path this guide follows.
I will use example.com and www.example.com below. Replace them with your real names, and do not skip the checks before reloading Nginx.
Before You Request a Certificate
Before touching Certbot, confirm the boring pieces. Most certificate problems come from DNS, firewalls, or a server block that does not answer for the requested domain.
Check DNS from somewhere outside the server:
dig +short example.com
dig +short www.example.com
Both names should resolve to the public address that reaches your Nginx host or load balancer.
Make sure ports 80 and 443 are open at every layer: cloud security group, host firewall, network firewall, and any load balancer in front of the host.
sudo ss -tulnp | grep nginx
sudo ufw status
On a cloud VM, also check the provider console. I have seen plenty of clean Nginx configs fail because the instance firewall allowed 443 but the cloud security group still blocked it.
Finally, confirm Nginx already serves the domain over plain HTTP:
curl -I http://example.com
curl -I http://www.example.com
The response does not need to be pretty. It can be a placeholder page. It just needs to prove that requests for that hostname arrive at this Nginx server.
Install Certbot
For Debian/Ubuntu:
sudo apt update
sudo apt install certbot python3-certbot-nginx
For RHEL-compatible systems:
sudo dnf install certbot python3-certbot-nginx
Older CentOS systems may use yum and may need EPEL enabled first. Package names also vary by distro version, so use your distribution's package docs if those commands do not find the plugin.
Request the Certificate
The Nginx plugin can request the certificate and edit the matching server block:
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask for an email address, agreement to the terms, and sometimes whether to redirect HTTP to HTTPS. For a normal public website, choose the redirect. For an API or internal service, check clients first. Some older clients or health checks may still call http:// and expect a specific response.
If Certbot says it cannot find a matching server block, check server_name. A block like this gives Certbot something clear to work with:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Run a syntax check before asking Certbot to edit anything:
sudo nginx -t
If the current config is already broken, fix that first.
What the Nginx Config Should Look Like
After a successful run, you will usually see one HTTP block that redirects and one HTTPS block that serves the site:
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/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Do not copy this blindly over a production config. Preserve your existing location rules, proxy settings, logs, and upload limits. The important parts are the listen 443 ssl, certificate paths, and the redirect behavior on port 80.
Test, Reload, and Verify
Always test before reload:
sudo nginx -t
Then reload:
sudo systemctl reload nginx
Check from the command line:
curl -I http://example.com
curl -I https://example.com
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
The HTTP request should redirect if you chose that option. The HTTPS request should return the expected status. The openssl command should show a certificate whose subject or subject alternative names cover the domain and whose dates are current.
Browser testing still matters. Open the site in a private window and click the lock icon to inspect the certificate. If your site loads assets from old http:// URLs, the page may show mixed-content warnings even though the main certificate is fine. Fix those asset URLs in the app, CMS, or template layer.
Renewal
Let's Encrypt certificates are short-lived, so renewal must work without you remembering it. Certbot normally installs a systemd timer or cron job. Check it:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
The dry run is the useful part. It performs a renewal simulation and catches common problems such as broken HTTP validation, DNS changes, missing plugins, or a config that cannot reload.
If you terminate TLS at a load balancer or CDN instead of directly on the Nginx host, renewal may need a DNS challenge or a different deployment path. Do not assume the default HTTP challenge will work if public traffic never reaches this server.
TLS Settings Worth Checking
For most modern public sites, TLS 1.2 and TLS 1.3 are the practical baseline:
ssl_protocols TLSv1.2 TLSv1.3;
Avoid hand-tuning cipher lists unless you know why. Certbot's included options-ssl-nginx.conf and the Mozilla SSL Configuration Generator are better starting points than a copied cipher string from an old blog post. Cipher guidance changes over time, and compatibility requirements differ between a public marketing site and an internal legacy API.
HSTS is useful, but it deserves caution:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Start with a short value during testing:
add_header Strict-Transport-Security "max-age=300" always;
Only use includeSubDomains when every subdomain can serve valid HTTPS. If old.example.com, mail.example.com, or a customer-specific subdomain is not ready, that option can break real users.
Common Failures
If Certbot cannot verify the domain, check DNS first, then port 80, then the Nginx server block. The HTTP-01 challenge needs Let's Encrypt to reach a token under /.well-known/acme-challenge/. Redirects are fine when configured correctly, but a proxy, CDN, or catch-all block can accidentally send the challenge somewhere else.
If Nginx fails to reload after Certbot changes, run:
sudo nginx -t
sudo journalctl -u nginx -n 80 --no-pager
The syntax test usually tells you the exact file and line. Common causes are duplicate listen 443 ssl blocks, a removed certificate path, or a snippet included from a path that does not exist on this server.
If HTTPS works locally but not for users, check the public path. A load balancer may still point at an old target group. A CDN may have cached a redirect. IPv6 DNS may point at a different host than IPv4. Test both:
curl -4 -I https://example.com
curl -6 -I https://example.com
The cleanest HTTPS setup is boring: DNS points to the right place, Nginx has one obvious server block for the domain, Certbot renewal passes in a dry run, and headers are added only after the basics work.