Nginx Tutorial #3: SSL Setup

Photo of Mateusz Dobek

Mateusz Dobek

Updated Feb 27, 2023 • 8 min read

Hello! Sharing is caring so we'd love to share another piece of knowledge with you. We prepared three-part series with the Nginx tutorial.

If you already know something about Nginx or you'd just like to expand your experience and understanding - this is the perfect place for you!

We will tell you how Nginx works, what concepts are behind it, how could you optimize it to boost up your app's performance or how to set it up to have it up and running.

This tutorial will have three parts:

  • Basics concepts - where you get to know the difference between directive and context, inheritance model, and the order in which Nginx pick server blocks, and locations.
  • Performance - tips and tricks to improve speed. We will discuss here gzip, caching, buffers, and timeouts.
  • SSL setup - set up configuration to serve content through HTTPS.

We aimed to create a series in which you can easily find the proper configuration for a particular topic (like gzip, SSL, etc.), or simply read it all through. For the best learning experience, we suggest to set Nginx up on your own machine and try some practice.


SSL (standing for Socket Secure Layer) is a protocol providing a secure connection over HTTP.

SSL 1.0 was developed by Netscape, and never publicly released due to serious security flaws. SSL 2.0 was released in 1995, with some issues, which lead to final SSL 3.0, released in 1996.

The first version of TLS (Transport Layer Security) was written as an upgrade to SSL 3.0. Then TLS 1.1, and 1.2 came out. Right now, just behind the door, there is TLS 1.3 coming soon (which is truly something worth waiting for) and is supported already by some browsers.

Technically SSL and TLS are different (as each is describing the different version of a protocol) - but many use those names interchangeably.

Base SSL/TLS setup

In order to handle HTTPS traffic, you need to have SSL/TLS certificate in place. You can check appendix to generate free certificate via Let’s encrypt.

When you have the certificate in place, you can simply turn HTTPS by:

  • start listening on a port 443 (default port that browsers will use when you type
  • providing certificate, and its key
server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;

  ssl_certificate /etc/nginx/ssl/netguru.crt;
  ssl_certificate_key /etc/nginx/ssl/netguru.key;

We also want to tweak the config to:

  • use only TLS protocol. All of the SSL versions are not used anymore due to the well-known vulnerabilities
  • use predefined, secure server ciphers (similar case as with protocols - those days only a few ciphers are considered secure)

Keep in mind that above settings are always changing. It is a good idea to revisit them from time to time.

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;

  ssl_certificate /etc/nginx/ssl/netguru.crt;
  ssl_certificate_key /etc/nginx/ssl/netguru.key;

TLS Session Resumption

Using HTTPS, imposes TLS handshake, on top of TCP one. This increase significantly time, before actual data transfer is made. Assuming that you are requesting /image.jpg from Warsaw, and connecting to the nearest server in Berlin:

Open connection

TCP Handshake:
Warsaw  ->------------------ synchronize packet (SYN) ----------------->- Berlin
Warsaw  -<--------- synchronise-acknowledgement packet (SYN-ACK) ------<- Berlin
Warsaw  ->------------------- acknowledgement (ACK) ------------------->- Berlin

TLS Handshake:
Warsaw  ->------------------------ Client Hello  ---------------------->- Berlin
Warsaw  -<------------------ Server Hello + Certificate ---------------<- Berlin
Warsaw  ->---------------------- Change Ciper Spec -------------------->- Berlin
Warsaw  -<---------------------- Change Ciper Spec --------------------<- Berlin

Data transfer:
Warsaw  ->---------------------- /image.jpg --------------------------->- Berlin
Warsaw  -<--------------------- (image data) --------------------------<- Berlin

Close connection

To save one roundtrip during TLS handshake, and computational cost of generating a new key, we could reuse session parameters generated during the first request. Client and the server could store session parameters behind the Session ID key. During the next TLS handshake, the client can send the Session ID, and if the server will still have a proper entry in cache - parameters generated during the previous session will be reused.

server {
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1h;

OCSP Stapling

SSL certificate can be revoked at any time. Browsers, in order to know if given certificate is no longer valid, need to perform an additional query via Online Certificate Status Protocol (OCSP). Instead of requiring users to perform given OCSP query, we could do it on the server, cache the result, and serve OCSP response to our clients, during TLS handshake. It is called OCSP stapling.

server {
  ssl_stapling on;
  ssl_stapling_verify on;                               # verify OCSP response
  ssl_trusted_certificate /etc/nginx/ssl/lemonfrog.pem; # tell nginx location of all intermediate certificates

  resolver valid=86400s;                # resolution of the OCSP responder hostname
  resolver_timeout 5s;

Security headers

Here some headers, worth turning on to grant more security. For more headers & detailed information you definitely should check the OWASP Secure Headers Project.

HTTP Strict-Transport-Security

Or HSTS, in short, enforces user-agent to send all request to the origin over HTTPS.

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";


Indicates whether the browser should render or not a page in a frame, an iframe or an object tag.

add_header X-Frame-Options DENY;


This will prevent the browsers from sniffing the file, to deduct file type. The file will be interpreted as thing declared in the Content-Type header.

add_header X-Content-Type-Options nosniff;

Server tokens

Another good practice is to hide information about your web server, in the HTTP response header field:

Server : nginx/1.13.2

This can be accomplished by disabling server_tokens directive:

server_tokens off;

Appendix :: Let’s Encrypt


Up to date can be found here.

For testing purposes use staging environment, to not exhaust rate limits.

Generate new certificate

certbot certonly --webroot --webroot-path /var/www/netguru/current/public/  \
          -d \

Make sure it can be properly renewed

certbot renew --dry-run

Make sure you have automatic renewing added to crontab. Run crontab -e and add the following line

0 3 * * * /usr/bin/certbot renew --quiet --renew-hook "/usr/sbin/nginx -s reload"

Check if SSL is working properly via ssllabs


Thank You for reading. This series could not be possible without plenty of resources found in the depths of the internet. Here are few great websites we found especially useful, during writing this series:

We would be grateful for your feedback and comments, so feel free to discuss! Did you like this series? Do you have some suggestions what topic should be tackled next? Or maybe did you spot a bug? Let us know and see you next time!

Photo of Mateusz Dobek

More posts by this author

Mateusz Dobek

A student. The first programmer in the world who does not drink coffee (although he sometimes sips...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency

Let's talk business!

Trusted by: