HTTPS in Nginx: setting up an SSL certificate
HTTPS encrypts the traffic between the visitor's browser and your server, and proves to the browser that it really is talking to your server. To do that, the server holds a certificate and the private key that belongs to it. Enabling HTTPS in Nginx comes down to three jobs: obtaining the certificate, telling Nginx where it is, and redirecting unencrypted requests to HTTPS.
This guide follows the general workflow with Let's Encrypt, which issues free certificates, and its widely used client, Certbot. If you bought your certificate from another provider, you can pick up from step three. If SSL and TLS are new to you, read the What is SSL? guide first.
In brief
- Certbot obtains the certificate; Nginx is pointed at fullchain.pem and privkey.pem.
- Every request arriving on port 80 is redirected to HTTPS with a 301.
- HSTS is tried with a short lifetime first and extended once everything works.
- Renewal must be automatic, and Nginx must be reloaded after each renewal.
What you need
- Administrator (sudo) access to the server and a running Nginx
- A domain name whose DNS record points to the server's IP address
- Ports 80 and 443 reachable from the internet
- A backup of the current Nginx configuration
Caution
Back up the configuration before changing it and test with "nginx -t" after every change. File paths and package names vary by distribution; adapt the examples to your own server.
On this page
HTTPS in seven steps
-
Check the prerequisites
Before issuing a certificate, Let's Encrypt verifies that the domain really is under your control. In the most common method, the validation server connects to your domain on port 80 and asks for a temporary file. So:
- The DNS record of the domain (and of the
wwwname, if you use it) must point to this server. - Ports 80 and 443 must be open in the firewall.
- Nginx must have a
serverblock for this domain with a correctserver_nameline.
Where site files live varies by distribution:
/etc/nginx/conf.dis common, while/etc/nginx/sites-availableis the Debian/Ubuntu layout.
: Enlarge - The DNS record of the domain (and of the
-
Obtain the certificate with Certbot
The command for installing Certbot depends on the distribution and package manager; the first line below is only an example. Once installed, the
--nginxoption obtains the certificate and adds the necessary lines to the matchingserverblock itself. Each-dis one domain name.Bash# Installation varies by distribution. Example (Debian/Ubuntu package repository): sudo apt install certbot python3-certbot-nginx # Obtain the certificate and write it into the Nginx configuration sudo certbot --nginx -d example.com -d www.example.comCertbot places the files under
/etc/letsencrypt/live/example.com/. Two of them matter to Nginx:fullchain.pem: your site's certificate together with the intermediate certificate.ssl_certificatepoints to this file.privkey.pem: the private key.ssl_certificate_keypoints to this file. It is never shared and never placed under the web root.
: Enlarge -
Write the HTTPS server block and the redirect
If Certbot edited the configuration for you, simply review the result at this step. If you are writing it by hand, you need two blocks: one listens on port 80 and permanently (301) redirects every request to HTTPS, the other answers on port 443 with the certificate.
Nginx# 80: redirect every request to HTTPS server { listen 80; listen [::]:80; server_name example.com www.example.com; return 301 https://$host$request_uri; } # 443: answer with the certificate server { listen 443 ssl; 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; ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; root /var/www/example.com/public; index index.html; }The line
return 301 https://$host$request_uri;redirects while keeping the address and query string the visitor asked for.ssl_protocols TLSv1.2 TLSv1.3;switches off old, insecure protocol versions. The directive that enables HTTP/2 is written differently depending on the Nginx version; check the documentation for your version. -
Test and reload
Test the syntax first; if there are no errors, reload Nginx. A reload does not drop open connections. Then check the redirect and the certificate from outside.
Bash# Test the syntax, then reload sudo nginx -t sudo systemctl reload nginx # Does the HTTP address redirect to HTTPS? curl -sI http://example.com/ | grep -i -E "^(HTTP|location)" # HTTPS response and headers curl -sI https://example.com/ # Show the certificate chain being served openssl s_client -connect example.com:443 -servername example.com < /dev/nullThe first
curloutput should show301and alocationline starting withhttps://. In the browser, use the connection details in the address bar to check the certificate's domain name and expiry date. If something goes wrong, see "Common errors" below.
: Enlarge -
If you proxy to a backend: SSL termination
In most setups Nginx works as a reverse proxy: it receives the request and passes it on to the application behind it. In this arrangement the encrypted connection is opened at Nginx; this is called SSL termination (or TLS termination). The certificate lives in one place and the backend server does not have to deal with encryption.
Nginxupstream app_backend { server 127.0.0.1:8080; } 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; location / { # Decrypted here; plain HTTP goes to the backend proxy_pass http://app_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # The protocol the visitor used: https proxy_set_header X-Forwarded-Proto $scheme; } }There are two options for the connection between Nginx and the backend:
- Plain HTTP: the usual choice when the backend is on the same server (
127.0.0.1) or on a trusted, closed internal network. - Re-encryption: if the traffic crosses a network you do not trust, use
proxy_pass https://…. By default Nginx does not verify the backend's certificate; verification needsproxy_ssl_verify on;andproxy_ssl_trusted_certificate.
Because the backend sees the request as HTTP, it cannot tell that the visitor arrived over HTTPS. The
X-Forwarded-Protoheader carries that information; the application reads it to set secure cookies and build correct addresses. The application should trust this header only when it comes from your own proxy. More detail: Setting up a reverse proxy with Nginx.
: Enlarge - Plain HTTP: the usual choice when the backend is on the same server (
-
Enable HSTS with a short lifetime first
The HSTS header (
Strict-Transport-Security) tells the browser "connect to this site over HTTPS only for the stated period". It is strong protection but hard to undo: the browser will not go back to HTTP until the period runs out. So try it first with amax-ageof a few minutes.Nginx# Trial: 5 minutes add_header Strict-Transport-Security "max-age=300" always; # If all is well, replace the line above with: 1 year # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Define the header only in the block for port 443. The word
alwaysmakes sure the header is sent with error responses too. Before addingincludeSubDomains, make sure all your subdomains work over HTTPS. For the other headers, see the Security headers and HTTPS guide. -
Verify automatic renewal
Let's Encrypt certificates are short-lived; they are not tracked by hand but renewed automatically. Certbot packages usually install a scheduler (a systemd timer or a cron job); it runs regularly and renews only certificates that are close to expiry. Confirm that the setup really works with a trial renewal.
Bash# Trial renewal: does not touch the real certificate sudo certbot renew --dry-run # Certificates and expiry dates sudo certbot certificates # Is the timer installed? (on distributions that use systemd) systemctl list-timers | grep -i certbot # Reload Nginx after a certificate has been renewed sudo certbot renew --deploy-hook "systemctl reload nginx"Nginx reads the certificate file only when it starts and when it is reloaded. Without a reload after renewal, visitors are still served the old certificate even though the file has been renewed. The command given with
--deploy-hookruns only when a certificate has actually been renewed. To make it permanent, you can also place the same command as an executable script in the/etc/letsencrypt/renewal-hooks/deploy/directory; Certbot runs the scripts in that directory after every successful renewal.
: Enlarge
If you do not want Certbot to touch the configuration: the webroot method
If you prefer to manage the Nginx configuration entirely yourself, you can have Certbot obtain the certificate and nothing else. With this method the validation file is written to a directory of your choice, and the block for port 80 keeps that path out of the redirect.
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Validation files are not redirected
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://$host$request_uri;
}# Obtain the certificate only; leave the Nginx configuration alone
sudo certbot certonly --webroot -w /var/www/letsencrypt -d example.com -d www.example.comWildcard certificates cannot be obtained this way; they require validation through a DNS record, and the steps depend on your DNS provider.
TLS settings: what to write and what to leave out
- Protocol:
ssl_protocols TLSv1.2 TLSv1.3;is enough for current browsers. For TLS 1.3, the OpenSSL library Nginx was built with must support that version. - Define it in one place: if the server hosts several sites, writing the protocol setting once in the
httpblock prevents inconsistencies between sites. - Session cache:
ssl_session_cache shared:SSL:10m;reduces repeated handshakes for returning visitors. - Cipher suites: do not copy old lists that circulate on the internet. Recommendations change over time; for current advice, see the documentation of your server software. If in doubt, keeping the default is better than pasting an outdated list.
Common errors and how to fix them
| Symptom | Likely cause | Fix |
|---|---|---|
Works in the browser; certificate error on some phones, in apps or with curl | Missing intermediate certificate: ssl_certificate points to the site certificate only | Point it to fullchain.pem |
| Warning on the padlock icon, some images or scripts do not load | Mixed content: the page requests resources over http:// | Change the addresses in the pages and the database to https:// |
| "Too many redirects" error | Redirect loop: the CDN or proxy in front connects to the server over HTTP, and the server redirects to HTTPS again | Set the service in front to connect to the server over HTTPS, or make the redirect conditional |
| Certificate expired warning | Renewal is not running, or Nginx was not reloaded after renewal | Run a trial renewal; add the reload hook |
| The certificate cannot be issued | DNS points to another server, or port 80 is closed | Check the DNS record and the firewall |
| Name mismatch warning | The certificate does not cover the www name or the subdomain | Add the missing name with -d and obtain the certificate again |
For a redirect loop: if a service in front of Nginx terminates TLS (a CDN such as Cloudflare, for example) and connects to the server over HTTP, you can make the redirect in the port 80 block depend on the protocol reported by that service:
listen 80;
server_name example.com www.example.com;
# Redirect only if the visitor reached the service in front over HTTP
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
root /var/www/example.com/public;Trust this header only if you are sure the requests really come from your own proxy. The lasting fix is to encrypt the connection between the service in front and your server as well. The same problem appears when the application behind Nginx redirects to HTTPS on its own but ignores the X-Forwarded-Proto header.
Checklist
ssl_certificatepoints to the full chain (fullchain.pem) andssl_certificate_keyto the private key.- The private key is readable only by the authorised user and is not exposed in backups or repositories.
- The
http://address goes to thehttps://address in a single step with a 301; there is no loop. - Only TLS 1.2 and 1.3 are enabled.
- There are no mixed content warnings.
- HSTS was tried with a short lifetime, then extended.
- The trial renewal succeeds; Nginx is reloaded after renewal.
- The certificate's expiry date is monitored separately: backup and monitoring.
Frequently asked questions
Is a Let's Encrypt certificate less secure than a paid one?
No. There is no difference in encryption, and browsers accept both in the same way. With paid certificates the difference lies in extras such as separate validation of the organisation's identity, support and warranty.
I renewed the certificate but the browser still shows the old one. Why?
Nginx reads the certificate file only when it starts and when it is reloaded. Test with "nginx -t", run "systemctl reload nginx", and add a reload hook to run after renewal.
Do I need separate certificates for the www and non-www addresses?
No. A single certificate can cover several names; pass each name to Certbot with its own -d option.
There is a CDN in front of my server. Do I still need a certificate on the server?
Yes, it is recommended. If the connection between the CDN and your server stays unencrypted, traffic is unprotected on that leg and problems such as redirect loops can appear. Set the CDN to connect to the server over HTTPS and to verify the certificate.
Can I undo HSTS after enabling it?
You can clear the record in browsers by sending the header with max-age=0, but that only takes effect in browsers that visit the site again, and only while HTTPS is working. This is why you should try a short lifetime before moving to a long one.
BYK Yazılım Support Team
This guide is written and regularly reviewed by the BYK Yazılım support team. Last updated: 4 October 2026.
Related guides
- Security headers and HTTPSHSTS, CSP, X-Content-Type-Options, Referrer-Policy and Permissions-Policy, with an .htaccess example.
- What is SSL? What does SSL do on a website and in email?What https and the padlock on a website and the SSL setting in an email program protect, and how they differ.
- How to set up Nginx as a reverse proxyStep-by-step setup: server block, proxy_pass, forwarded headers, WebSocket, timeouts and real client IP.
- Nginx security settings: rate limiting, headers, access restrictionsRate limiting, security headers, hidden-file and admin-path restrictions, and request limits in Nginx.
Let us talk about your website infrastructure
BYK Yazılım builds corporate websites. Write to us with any questions about your site.
Contact us Our corporate website service