Api Gateway Kiến Thức Linux

How to use Nginx for HTTPS and WSS

congdonglinux-NGINX-logo

Introduction

Nginx is a web server, also works as a load balancer, and may help us a lot in security and routing terms, because when deploying our applications to a production environment, we don’t want to put ports on the url, and also the dns has to look like clean to our users, also for security reasons, we don’t want to show the port explicitly where the service is being run.

Also, I decided to make this tutorial, because I was working on a cryptocurrency exchange platform in a freelancing job, and the frontend communicates with the relayer(backend that receives the user order’s for exchanging Tokens/Crypto) by HTTPS and WSS. The HTTP it was for the general APIs and for posting new buy/sell orders, and the Websockets was for making a full-duplex communication for near realtime updates about the orders. But, this is an exchange, and HTTP and WS it’s not suitable regarding security compliances, so we need to set up HTTP and WSS for this. I’m using this exchange application example, although this example can be applied to any another application. I just wanted to share, because it took quite a few times to make WSS work together with HTTPS.

All the commands and examples that we are going to run here, will be considering Ubuntu as the operational system, but it can be extended to MacOS, Windows and RedHat Linux Distributions.

Self-Signed Certificates and Trusted Certificates(CA)?

SSL certificates are what enable websites to move from HTTP to HTTPS, which is more secure. An SSL certificate is a data file hosted in a website’s origin server. SSL certificates make SSL/TLS encryption possible, and they contain the website’s public key and the website’s identity, along with related information. Devices attempting to communicate with the origin server will reference this file to obtain the public key and verify the server’s identity. The private key is kept secret and secure.

No alt text provided for this image

So basically, the flow of a HTTPS request is:

  • User access the site or the API
  • Check DNS records from internal DNS cache, otherwise it will communicate with nameservers over the public internet to get the IP address of the URL host
  • The host offers the public key to the client to encrypt the TCP/IP packets, and the request is sent, and only the host have the private key to decrypt the request

The main difference between both certificates is your browser can easily identify your SSL Certificate. When your browser finds the http connection with a server with the self-signed certificate the user will have security alert message. This alert message informs the user that the Certificate has not been issued by an organization that the user can trust. This type of message is not suitable for commercial websites.

Thus, self-signed SSL Certificate is not right option for ecommerce websites, which involved money transaction. In order to get rid of this message the SSL Certificate must be signed by Certificate Authority. This Certificate Authorities are third party entity that verifies the identity of an online business and then guarantees for that identity through the issuance of the Digital Certificate.

Regarding breaking security of HTTPS connections, we have the famous Man-in-the-middle attack, also known as hijack attacking, where the cracker places himself in the middle of the connection between the user and host, being able to get user’s information. This is a topic for another article, you can read more about this on:

Step 1 – Install Nginx and Basic Configuration

So, we can use Nginx as a reverse proxy to get all your requests on your DNS or IP on port 80 and 433 to your applications.

First of all let’s install Nginx:

sudo apt-get install nginx
sudo service nginx start

Check that the service is running by tipping:

sudo service nginx status

You will also want to enable Nginx, so it starts when your server boots:

sudo systemctl enable nginx

Add the following rules on the IP tables of your servers

sudo iptables -I INPUT -p tcp -m tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT

Step 2 – Create the SSL Certificate

TLS/SSL works by using a combination of a public certificate and a private key. The SSL key is kept secret on the server. It is used to encrypt content sent to clients. The SSL certificate is publicly shared with anyone requesting the content. It can be used to decrypt the content signed by the associated SSL key.

The /etc/ssl/certs directory, which can be used to hold the public certificate, should already exist on the server. Let’s create an /etc/ssl/private directory as well, to hold the private key file. Since the secrecy of this key is essential for security, we will lock down the permissions to prevent unauthorized access:

sudo mkdir /etc/ssl/private
sudo chmod 700 /etc/ssl/private

Now, we can create a self-signed key and certificate pair with OpenSSL in a single command by typing:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt

You will be asked a series of questions. Before we go over that, let’s take a look at what is happening in the command we are issuing:

  • openssl: This is the basic command line tool for creating and managing OpenSSL certificates, keys, and other files.
  • req: This subcommand specifies that we want to use X.509 certificate signing request (CSR) management. The “X.509” is a public key infrastructure standard that SSL and TLS adheres to for its key and certificate management. We want to create a new X.509 cert, so we are using this subcommand.
  • -x509: This further modifies the previous subcommand by telling the utility that we want to make a self-signed certificate instead of generating a certificate signing request, as would normally happen.
  • -nodes: This tells OpenSSL to skip the option to secure our certificate with a passphrase. We need Nginx to be able to read the file, without user intervention, when the server starts up. A passphrase would prevent this from happening because we would have to enter it after every restart.
  • -days 365: This option sets the length of time that the certificate will be considered valid. We set it for one year here.
  • -newkey rsa:2048: This specifies that we want to generate a new certificate and a new key at the same time. We did not create the key that is required to sign the certificate in a previous step, so we need to create it along with the certificate. The rsa:2048 portion tells it to make an RSA key that is 2048 bits long.
  • -keyout: This line tells OpenSSL where to place the generated private key file that we are creating.
  • -out: This tells OpenSSL where to place the certificate that we are creating.

While we are using OpenSSL, we should also create a strong Diffie-Hellman group, which is used in negotiating Perfect Forward Secrecy with clients.

We can do this by typing:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Step 3 – Configure Nginx to use SSL

The default Nginx configuration in CentOS is fairly unstructured, with the default HTTP server block living within the main configuration file. Nginx will check for files ending in .conf in the /etc/nginx/conf.d directory for additional configuration.

We will create a new file in this directory to configure a server block that serves content using the certificate files we generated. We can then optionally configure the default server block to redirect HTTP requests to HTTPS.

Create and open a file called ssl.conf in the /etc/nginx/conf.d directory:

sudo vi /etc/nginx/conf.d/ssl.conf

Place the following content under this file:

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




    server_name server_IP_address;




    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;




    ########################################################################
    # from https://cipherli.st/                                            #
    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html #
    ########################################################################




    . . .




    ##################################
    # END https://cipherli.st/ BLOCK #
    ##################################




    root /usr/share/nginx/html;




    location / {
    }




    error_page 404 /404.html;
    location = /404.html {
    }




    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
 
}

Step 4 – Configure Nginx to redirect all HTTP to HTTPS

With our current configuration, Nginx responds with encrypted content for requests on port 443, but responds with unencrypted content for requests on port 80. This means that our site offers encryption, but does not enforce its usage. This may be fine for some use cases, but it is usually better to require encryption. This is especially important when confidential data like passwords may be transferred between the browser and the server.

Thankfully, the default Nginx configuration file allows us to easily add directives to the default port 80 server block by adding files in the /etc/nginx/default.d directory. Create a new file called ssl-redirect.conf and open it for editing with this command:

sudo vi /etc/nginx/default.d/ssl-redirect.conf

Then paste in this line:

return 301 https://$host$request_uri/;

Step 5 – Configure the Reverse proxy to your application

Now we have to configure the reverse proxy part, first we will do that for the HTTP and after for the WebSockets part. Under the location section, in the /etc/nginx/conf.d/ssl.conf file, you have to insert the configuration to reverse proxy to your application. Remember that the proxy must go through HTTP, and not HTTPS, because the HTTPS it’s handled by Nginx, and the “dangerous path” where all your TCP/IP packets has to be encrypted is in the middle of way, when your request goes through the public internet. Once it reaches the Nginx, your server will have the private key, will decrypt that, and everything is secured.

Indeed, there isn’t a perfect security architecture, so if you want to enhance your security, and make that Nginx reverse proxy also to HTTPS, just remember that your application must be configured for that. For example, if you have a node express server running, you would need a HTTPS configuration with the proper SSL certificates set up on it. This adds another level of security, and it’s good to man in the middle attacks.

So, going with HTTP only approach, under the location section, on the /etc/nginx/conf.d/ssl.conf file, add the following, just remember to change the port:

location / {
      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;


      # Fix the “It appears that your reverse proxy set up is broken" error.
      proxy_pass          http://localhost:<the-port-where-your-application-runs>;
   
   proxy_read_timeout  90;
}

Step 6 – Configure Support to WebSockets

The WebSockts support it’s a little configuration also in the location section in  the /etc/nginx/conf.d/ssl.conf file, just add this:

      # WebSocket support
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade"; 

After this, your ssl.conf file should be like this:

server {

    listen 443 http2 ssl;
    listen [::]:443 http2 ssl;

    server_name server_IP_address;

    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;


    ########################################################################
    # from https://cipherli.st/                                            #
    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html #
    ########################################################################

    . . .


    ##################################
    # END https://cipherli.st/ BLOCK #
    ##################################


    root /usr/share/nginx/html;

    location / {
      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;




      # Fix the “It appears that your reverse proxy set up is broken" error.
      proxy_pass          http://localhost:<the-port-where-your-application-runs>;
      proxy_read_timeout  90;




      # WebSocket support
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";


    }


    error_page 404 /404.html;
    location = /404.html {
    }


    error_page 500 502 503 504 /50x.html;
    location = /50x.html {

    }
}

Step 7 – Create Trusted Certificates

Your configuration for HTTPS and WSS might work for development purposes, but mainly WSS will probably not work in a Test/Prod environment, when you have multiple people using the system. When using a self-signed certificate for WSS, people might get the error:

WebSocket connection to 'wss://…' failed: Error in connection establishment: net::ERR_CONNECTION_CLOSED

This means that you need some trusted certificate, and that your Nginx configuration must only have .pem files of trusted certificates. It’s kind of easy, you just need a reachable domain, and use certbot.

Before everything, make sure that you have a reachable domain, because certbot will do a HTTP request on the domain that you pass. Just do this command to generate:

certonly --webroot --webroot-path=/var/www/html --email <your-email> --agree-tos --no-eff-email --staging -d <your-domain>  -d www.<your-domain>

After this you should have your trusted certificates, then you just have to add them on the nginx configuration, replacing the self-signed ones by this trusted one, and now everything is fine, don’t forget to restart Nginx:

sudo service nginx restart

Thanks everyone, I hope this guide might be useful for you, because I did it due to the difficult to find a proper one for the WSS part, for HTTPS you can find a lot, but for WSS there is really a lack of documentation over the public internet.

Let’s discuss more and share your thoughts over the comments, so I can keep improving my articles!

Đăng ký liền tay Nhận Ngay Bài Mới

Subscribe ngay

Cám ơn bạn đã đăng ký !

Lỗi đăng ký !

Add Comment

Click here to post a comment

Đăng ký liền tay
Nhận Ngay Bài Mới

Subscribe ngay

Cám ơn bạn đã đăng ký !

Lỗi đăng ký !