Nginx SSL/TLS Configuration in Practice: From HTTPS Certificates to A+ Security Hardening
It was 3 AM when the monitoring alert went off. I checked my phone to find that my website certificate had expired—a 90-day free certificate that I had completely forgotten about. The browser’s red “Not Secure” warning was like a mocking flag, hanging on my blog’s homepage.
After that incident, I spent an entire week diving deep into Nginx SSL/TLS configuration from scratch. From an SSL Labs F rating to A+, from manual renewal to full automation, from performance drag to handshake times under 100ms—I want to share all the pitfalls I encountered along the way.
HTTPS is more than just adding a small lock to the browser address bar. Google has explicitly stated that HTTPS is a search ranking factor, and data shows that properly configured HTTPS can boost search traffic by 15-20%. More importantly, it prevents man-in-the-middle eavesdropping—Baidu Smart Cloud data indicates HTTPS can reduce data breach risk by 83%. Simply put, not using HTTPS is like shouting your bank card password in a public place.
This article will guide you through configuring Nginx HTTPS from scratch: Let’s Encrypt certificate issuance, TLS 1.3 security hardening, SSL Labs A+ rating configuration templates, and automatic renewal solutions essential for production environments. All configurations can be copied and used directly.
Chapter 1: HTTPS Fundamentals and Certificate Type Selection
1.1 Why Every Website Needs HTTPS
HTTP transmits data in plaintext, meaning every page you visit and every form you submit is running naked across the network. Public WiFi at coffee shops, corporate network gateways, ISP equipment in residential areas—any intermediate node can see what you’re sending.
HTTPS adds a layer of TLS encryption between HTTP and TCP. Data is encrypted before sending and decrypted upon arrival, so eavesdroppers in between only see a garbled mess.
TLS does far more than encryption. It also provides authentication—ensuring you’re accessing the real example.com, not a phishing site after DNS hijacking. That’s why browsers display red warnings for expired certificates or domain mismatches: they’re telling you, “This website might not be what it claims to be.”
1.2 Three Types of SSL Certificates: DV, OV, EV
Certificates are categorized by validation strictness into three types:
| Type | Validation Method | Price | Use Cases |
|---|---|---|---|
| DV (Domain Validation) | Validates domain ownership | Free - Low | Personal blogs, test environments, small projects |
| OV (Organization Validation) | Validates domain + organization identity | Medium | Corporate websites, SaaS products |
| EV (Extended Validation) | Strictest identity verification | High | Finance, payments, government institutions |
Honestly, for most personal projects and small teams, DV certificates are completely sufficient. Let’s Encrypt provides free DV certificates with 90-day validity, and browsers trust them just as much as paid DV certificates. The only “drawback” is needing to renew every 90 days—but once you configure automatic renewal, this isn’t a problem at all.
The difference between OV and EV certificates is mainly in how they display in the browser address bar. EV certificates display the company name (like “Industrial and Commercial Bank of China”), but browsers have been de-emphasizing EV certificates visually, and Chrome doesn’t even show the company name by default anymore. Combined with OV/EV certificates costing hundreds to thousands, the value proposition isn’t great.
1.3 Let’s Encrypt: The Best Choice for Free Certificates
Let’s Encrypt is a non-profit certificate authority operated by ISRG (Internet Security Research Group). It has several core advantages:
- Completely Free: DV certificates are free forever
- Automation: Automatic issuance and renewal via ACME protocol
- Widely Trusted: All major browsers and operating systems trust it
- 90-Day Validity: Short-term certificates are more secure, forcing you to set up automation
There are drawbacks: only DV certificates, no OV/EV; wildcard certificates require DNS validation (slightly more troublesome). But for 99% of personal projects and small-to-medium websites, these “drawbacks” aren’t really issues.
Next, we’ll use Certbot—Let’s Encrypt’s official client—to apply for certificates.
Chapter 2: Let’s Encrypt Certificate Application in Practice
2.1 Installing Certbot
Certbot installation varies by operating system. Ubuntu users have it easiest:
# Ubuntu 20.04+ / Debian 10+
sudo apt update
sudo apt install certbot python3-certbot-nginx
CentOS/RHEL users need to enable the EPEL repository first:
# CentOS 8 / RHEL 8
sudo dnf install epel-release
sudo dnf install certbot python3-certbot-nginx
After installation, check the version with certbot --version. Once confirmed, the next step is to apply for a certificate.
2.2 Applying for a Certificate
Certbot supports multiple validation methods. The most common are standalone (independent validation) and webroot (website root directory validation). If your Nginx is already running, webroot mode is recommended:
# webroot mode: use when website is already running
sudo certbot certonly --webroot -w /var/www/example.com -d example.com -d www.example.com
The -w flag specifies the website root directory, and -d specifies the domain. You can apply for certificates for multiple domains at once.
If Nginx isn’t running yet, or you’re just temporarily starting a server for testing, use standalone mode:
# standalone mode: requires temporarily using port 80
sudo certbot certonly --standalone -d example.com -d www.example.com
In this mode, Certbot temporarily starts an HTTP server and automatically shuts it down after validation. The validation process requires port 80 to be free, so if your Nginx is already running, you need to stop it first.
There’s an even simpler method—Certbot’s Nginx plugin can automatically modify Nginx configuration:
# automatic configuration mode: Certbot automatically modifies Nginx config
sudo certbot --nginx -d example.com -d www.example.com
This command automatically applies for the certificate, modifies Nginx configuration, and sets up HTTPS redirect. Great for beginners to get started quickly, but for production environments, manual configuration is recommended for finer control over SSL parameters.
2.3 Certificate File Structure
After successful application, certificate files are stored by default in the /etc/letsencrypt/live/example.com/ directory:
/etc/letsencrypt/live/example.com/
├── cert.pem # Domain certificate
├── chain.pem # Intermediate certificate chain
├── fullchain.pem # Full certificate chain (cert + chain)
└── privkey.pem # Private key file
Nginx configuration requires two files: fullchain.pem (ssl_certificate) and privkey.pem (ssl_certificate_key). privkey.pem is a sensitive file with permissions set to 600—never leak it.
Chapter 3: Nginx SSL Basic Configuration
3.1 The Most Basic HTTPS Configuration
With certificates in hand, the next step is configuring HTTPS in Nginx. The simplest configuration looks like this:
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;
# Other configuration...
root /var/www/example.com;
index index.html;
}
This configuration gets HTTPS running but only scores a D. The problem: no TLS version specified, so it defaults to supporting TLS 1.0 and 1.1—both long deprecated as insecure.
3.2 HTTP Redirect to HTTPS
Configuring HTTPS alone isn’t enough—users might access the HTTP version directly. You need to redirect all HTTP requests to HTTPS:
server {
listen 80;
server_name example.com www.example.com;
# Permanent redirect to HTTPS
return 301 https://$server_name$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;
# Other configuration...
}
return 301 is a permanent redirect—the browser will cache this jump. Next time the user types http://, the browser will jump directly to https://, saving one request.
3.3 A Common Configuration Mistake
I’ve seen many people add http2 after listen 443 ssl;, writing listen 443 ssl http2;. In Nginx 1.25.1 and later, HTTP/2 support has been moved to the http2 directive:
# New syntax for Nginx 1.25.1+
server {
listen 443 ssl;
http2 on; # Standalone http2 directive
server_name example.com;
# ...
}
If your Nginx version is newer, use the new syntax to avoid configuration warnings. Check the version with nginx -v.
Chapter 4: TLS 1.3 Security Hardening Configuration
4.1 TLS Version Selection
TLS has four versions: 1.0, 1.1, 1.2, and 1.3. Versions 1.0 and 1.1 have been deprecated, and all major browsers stopped supporting them in 2020.
| Version | Security | Performance | Compatibility | Recommendation |
|---|---|---|---|---|
| TLS 1.0 | Insecure | Slow | Widely supported | Disable |
| TLS 1.1 | Insecure | Slow | Widely supported | Disable |
| TLS 1.2 | Secure | Average | Almost universal | Keep as fallback |
| TLS 1.3 | Most Secure | Fastest | Modern browsers | Must enable |
TLS 1.3’s core advantage is performance: handshake reduced from 2-RTT (round-trips) to 1-RTT, theoretically cutting handshake latency in half. In practice, TLS 1.3 handshake times can be kept under 100ms.
Use the ssl_protocols directive in Nginx configuration to specify TLS versions:
ssl_protocols TLSv1.2 TLSv1.3;
I don’t recommend enabling only TLS 1.3. Although almost all browsers support TLS 1.3 in 2026, some legacy HTTP clients (like certain API calling tools, embedded devices) might not support it yet. Keeping TLS 1.2 as a compatibility option is the safer choice.
4.2 Cipher Suite Configuration
Cipher Suite determines the encryption algorithm, key exchange method, and message authentication code. Choose wrong, and HTTPS might be meaningless.
Recommended TLS 1.3 cipher suite list for 2026:
ssl_protocols TLSv1.2 TLSv1.3;
# TLS 1.3 ciphers (Nginx automatically uses OpenSSL's default list)
# This line can be omitted, but keeping it is fine
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
# TLS 1.2 ciphers (backward compatibility)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# Prefer server-specified ciphers
ssl_prefer_server_ciphers on;
Key points of this configuration:
- ECDHE: Prefer elliptic curve key exchange, safer and faster than RSA
- AES-GCM: GCM mode provides authenticated encryption, safer than CBC mode
- CHACHA20-POLY1305: Better mobile performance, suitable for devices without AES hardware acceleration
If you’re unsure, use Mozilla SSL Configuration Generator: https://ssl-config.mozilla.org/
4.3 HSTS: Enforce HTTPS Access
HSTS (HTTP Strict Transport Security) tells browsers: “This website only accepts HTTPS access.” Even if the user manually types http://, the browser will locally force redirect to https://, saving one network request.
# HSTS header
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Parameter explanation:
max-age=31536000: Validity period 1 year (in seconds)includeSubDomains: Include all subdomainspreload: Add to HSTS Preload List (need to submit at hstspreload.org)
Note: Once HSTS is enabled, browsers will cache it for a long time. If you still need to test the HTTP version, don’t add preload yet, or set a shorter max-age.
Chapter 5: SSL Performance Optimization Tips
5.1 SSL Session Cache
Each TLS handshake requires key exchange and certificate verification, which has significant overhead. Session Cache lets clients reuse previous sessions within a certain time frame, skipping the full handshake process.
# Session Cache configuration
ssl_session_cache shared:SSL:10m; # 10MB cache, about 40000 sessions
ssl_session_timeout 1d; # Session validity 1 day
ssl_session_tickets off; # Disable Session Ticket (more secure)
shared:SSL:10m means all worker processes share a 10MB cache area. 1MB can store about 4000 sessions, so 10MB is enough for small-to-medium websites.
ssl_session_tickets off is for security. Session Ticket mechanism requires the server to maintain a key to encrypt tickets—if this key leaks, attackers can decrypt all historical sessions. Disabling it is more secure but increases server load—suitable for high-security scenarios.
5.2 OCSP Stapling
When browsers verify certificates, they need to check if the certificate has been revoked. The traditional way is to initiate an OCSP query to the CA, which adds a network request and exposes which websites the user has visited.
OCSP Stapling lets the server query OCSP status on behalf of the browser, cache the result, and send it to the browser in one go. It protects privacy and reduces latency.
# OCSP Stapling configuration
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
The resolver directive specifies DNS servers for resolving OCSP server domain names. Google’s 8.8.8.8 and 8.8.4.4 are common choices.
After configuration, test with OpenSSL:
openssl s_client -connect example.com:443 -status < /dev/null 2>&1 | grep -A 17 "OCSP response"
If you see “OCSP Response Status: successful”, OCSP Stapling is working properly.
5.3 ssl_buffer_size Tuning
ssl_buffer_size controls the size of TLS records. The default is 16KB, which is too large for small responses (like API requests) and increases TTFB (Time To First Byte).
For API servers or blog sites dominated by small responses, reduce this value:
ssl_buffer_size 4k; # Suitable for small response scenarios
For large file download servers, keep the default 16KB or increase to 32KB.
Chapter 6: Certificate Automatic Renewal Configuration
6.1 Certbot Automatic Renewal Command
Let’s Encrypt certificates are only valid for 90 days and must be renewed regularly. Certbot provides the renew command:
# Test renewal (doesn't actually renew, just checks)
sudo certbot renew --dry-run
# Actual renewal
sudo certbot renew
The renew command checks the expiration time of all certificates and only renews certificates with less than 30 days remaining validity. This means you can safely run this command daily—it won’t waste Let’s Encrypt rate limit quota.
6.2 Configure crontab Scheduled Task
The most reliable way is to set up automatic renewal with crontab:
# Edit root user's crontab
sudo crontab -e
Add these two lines:
# Check twice daily at 3:00 AM and 3:00 PM
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
0 15 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
--quiet is silent mode, no log output. --post-hook "systemctl reload nginx" reloads Nginx configuration after successful renewal to activate the new certificate.
Why check twice a day? Because renewal failures occasionally happen (like temporary DNS resolution failure), multiple checks increase success rate.
6.3 Renewal Testing and Troubleshooting
When renewal fails, Certbot records detailed logs in /var/log/letsencrypt/letsencrypt.log. Common issues:
- Port occupied: standalone mode needs port 80, ensure Nginx isn’t using it
- DNS resolution failure: check if domain DNS records correctly point to the server
- Rate limits: Let’s Encrypt allows max 5 applications for the same certificate per week, use
--dry-runfor testing - Certificate directory permissions: ensure Certbot has read/write permissions for
/etc/letsencrypt/
Complete renewal testing process:
# 1. First dry-run test
sudo certbot renew --dry-run
# 2. If dry-run succeeds, actual renewal
sudo certbot renew
# 3. Check certificate validity
sudo certbot certificates
# 4. Reload Nginx
sudo systemctl reload nginx
Complete Configuration Template
Below is a production-ready Nginx SSL configuration template that achieves SSL Labs A+ rating:
# HTTP redirect
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
# Certificate configuration
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS version
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher Suite
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
# Session Cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Performance tuning
ssl_buffer_size 4k;
# Website configuration
root /var/www/example.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
After configuration, test your rating at SSL Labs: https://www.ssllabs.com/ssltest/
Summary
Configuring HTTPS isn’t hard to get started with—a few lines of configuration and you’re running. But to achieve SSL Labs A+ rating while balancing performance and compatibility requires understanding TLS versions, Cipher Suite, Session Cache, and OCSP Stapling.
Core configuration points review:
- Certificate Application: Let’s Encrypt is free and easy to use, Certbot automation saves worry
- TLS Version: TLS 1.2 + 1.3, disable 1.0 and 1.1
- Cipher Suite: Prefer ECDHE and AES-GCM, include CHACHA20 for compatibility
- HSTS: Enforce HTTPS, reduce redirect overhead
- Performance Optimization: Session Cache and OCSP Stapling are both essential
- Automatic Renewal: crontab double insurance, automatic Nginx reload after renewal
Copy the configuration template above into your Nginx config, change the domain and certificate paths, and you’re ready to go. After configuration, remember to test your SSL Labs rating—let me know your results in the comments. My guess? Probably A+.
Configure Nginx SSL/TLS for A+ Security Rating
Complete configuration process from certificate application to security hardening
⏱️ Estimated time: 30 min
- 1
Step1: Install Certbot and Apply for Certificate
Use Certbot to apply for a Let's Encrypt free SSL certificate:
• Ubuntu/Debian: sudo apt install certbot python3-certbot-nginx
• CentOS/RHEL: sudo dnf install certbot python3-certbot-nginx
• Apply certificate: sudo certbot certonly --webroot -w /var/www/example.com -d example.com
• Certificate directory: /etc/letsencrypt/live/example.com/
• Need two files: fullchain.pem and privkey.pem - 2
Step2: Configure Nginx HTTPS Basic Settings
Add SSL certificate and HTTP redirect in Nginx configuration:
• listen 443 ssl; configure HTTPS listener
• http2 on; enable HTTP/2 (Nginx 1.25.1+)
• ssl_certificate points to fullchain.pem
• ssl_certificate_key points to privkey.pem
• return 301 https://$server_name$request_uri; redirect HTTP to HTTPS - 3
Step3: Configure TLS 1.3 and Cipher Suite
Enable secure TLS versions and cipher suites:
• ssl_protocols TLSv1.2 TLSv1.3; disable old versions
• ssl_ciphers configure ECDHE + AES-GCM + CHACHA20
• ssl_prefer_server_ciphers on; prefer server selection
• add_header Strict-Transport-Security enable HSTS - 4
Step4: Optimize SSL Performance
Configure Session Cache and OCSP Stapling to improve performance:
• ssl_session_cache shared:SSL:10m; shared session cache
• ssl_session_timeout 1d; session validity 1 day
• ssl_stapling on; enable OCSP Stapling
• ssl_buffer_size 4k; optimize for small response scenarios - 5
Step5: Configure Certificate Automatic Renewal
Use crontab to configure Certbot automatic renewal:
• sudo crontab -e edit scheduled tasks
• 0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
• Check twice daily (early morning and afternoon)
• --post-hook ensures Nginx reloads new certificate after renewal - 6
Step6: Test SSL Configuration Security
Verify if configuration achieves A+ rating:
• Visit https://www.ssllabs.com/ssltest/ to test SSL configuration
• Use openssl s_client -connect example.com:443 -status to test OCSP
• Check certbot certificates to confirm certificate validity
• Ensure HSTS, OCSP Stapling, Session Cache are all working
FAQ
How long is a Let's Encrypt certificate valid? Do I need to renew manually?
How do I test the security of my Nginx SSL configuration?
• A+ is the highest rating, indicating secure and excellent performance configuration
• You can also use openssl s_client command to test OCSP Stapling locally
• Test command: openssl s_client -connect example.com:443 -status
Does HTTPS affect website performance? How to optimize?
• TLS 1.3 handshake reduced from 2-RTT to 1-RTT, cutting latency in half
• Session Cache lets clients reuse sessions, skipping full handshake
• OCSP Stapling reduces network requests for certificate validation
• After optimization, handshake time can be controlled within 100ms
What to do if certificate renewal fails? How to troubleshoot?
• Port occupied: standalone mode needs port 80 to be free
• DNS resolution failure: check if domain correctly points to server IP
• Rate limits: Let's Encrypt allows max 5 applications for same certificate per week
• Check logs: /var/log/letsencrypt/letsencrypt.log
What's the difference between TLS 1.2 and TLS 1.3? Why enable both?
• Performance: handshake reduced from 2-RTT to 1-RTT, cutting latency in half
• Security: removed insecure encryption algorithms
• Compatibility: enabling both supports legacy clients, with TLS 1.2 as fallback
What should I pay attention to when configuring HSTS?
• max-age recommended to be 31536000 (1 year)
• includeSubDomains affects all subdomains
• preload needs to be submitted at hstspreload.org to take effect
• For first-time configuration, recommend setting a shorter max-age for testing, then extend after confirming no issues
How to choose SSL certificate type? What's the difference between DV, OV, and EV?
• DV certificate: free, validates domain ownership, suitable for personal blogs and small projects
• OV certificate: validates organization identity, displays company info, suitable for corporate websites
• EV certificate: strict validation, Chrome no longer displays company name, not great value
• Let's Encrypt free DV certificate is recommended for 99% of scenarios
11 min read · Published on: Apr 20, 2026 · Modified on: Apr 20, 2026
Nginx Practice Guide
If you landed here from search, the fastest way to build context is to jump to the previous or next post in this same series.
Previous
Nginx Performance Tuning: gzip, Caching, and Connection Pool Configuration
Comprehensive guide to Nginx performance tuning, including gzip compression to reduce transfer size by 60-80%, proxy_cache strategies for 95% hit rates, and worker_connections configuration to boost concurrency by 3-4x
Part 2 of 3
Next
This is the latest post in the series so far.
Related Posts
Nginx Reverse Proxy Complete Guide: Upstream, Buffering, and Timeout
Nginx Reverse Proxy Complete Guide: Upstream, Buffering, and Timeout
Docker Multi-Stage Build in Practice: Shrinking Production Images from 1GB to 10MB
Docker Multi-Stage Build in Practice: Shrinking Production Images from 1GB to 10MB
Supabase Edge Functions in Practice: Deno Runtime and TypeScript Development Guide

Comments
Sign in with GitHub to leave a comment