HTTP/2 with NGINX & Wireshark in Ubuntu (Part 1)

Introducing HTTP/2

HTTP/2 is the latest version of the HTTP protocol and offers several performance improvements, including multiplexing and server push. IETF, the standards organisation responsible for the development of HTTP, describes this latest version on their HTTP/2 website:

“HTTP/2 is a replacement for how HTTP is expressed “on the wire.” It is not a ground-up rewrite of the protocol; HTTP methods, status codes and semantics are the same.”

One of the major improvements in HTTP/2 is multiplexing which introduces a binary framing layer to enable full request and response multiplexing on a single TCP connection. This allows for interleaving of multiple requests/responses in parallel, eliminating the head of line blocking problem and removing the need for multiple connections.

An additional improvement in HTTP/2 is the server push function which allows a server to preemptively send (push) multiple resources to a client in response to a single request. For example, a client can make a single request for index.html and receive multiple responses from the server: not only the requested resource, but additional resources referenced by index.html such as Javascript, CSS, images, etc.

HTTP/2 Server push

This series of articles will aim to demonstrate both the server push and multiplexing features in HTTP/2. The differences between HTTP/1.1 and HTTP/2 with server push are illustrated in the figures below which show a client requesting a resource home.html which references 4 additional resources: bootstrap.css, bootstrap.js, img1.jpg, and img2.jpg.

http2 vs http1.1

Figure 1 illustrates HTTP/1.1 where the client (browser) first requests and receives home.html. In parsing the response, the client finds it needs to request additional resources (e.g. bootstrap.css) which it proceeds to request from the web server.

Figure 2 illustrates HTTP/2 with server push. The client (browser) requests only home.html. The web server then responds with home.html, along with promises to push the additional required resources bootstrap.css, bootstrap.js, img1.jpg, and img2.jpg. These extra resources are then sent to the client with no further GET requests required by the client.

Lab setup

One of the best ways to learn a protocol is to capture and examine traffic with a protocol analyzer such as Wireshark. The challenge with HTTP/2 is that currently all browser implementations of HTTP/2 require use of TLS encryption (HTTPS). This means we need to be able to decrypt the traffic before it can be inspected in Wireshark. To do this, we’ll setup NGINX as a local webserver running in a Docker container and configured for HTTP/2. Next, we’ll use Firefox Developer to capture the TLS key used to encrypt the requests/responses between the browser and the NGINX web server. Finally, we’ll capture some HTTP/2 traffic using Wireshark and use the captured key to decrypt the packets.

To summarize, the lab setup will consist of:

  • Ubuntu 18 running in VirtualBox (you can also use a stand-alone Ubuntu installation)
  • NGINX web server running in a Docker container
  • NGINX configured for HTTPS, HTTP/2, and server push
  • Firefox Developer to capture the TLS key
  • Wireshark protocol analyzer to capture the HTTP/2 traffic

Part 1: NGINX configuration

This first article will describe how to configure NGINX web server for HTTP/2 and server push. Part 2 will describe how to install and configure a Docker container running NGINX, as well as verify HTTP/2 and server push are working. Part 3 will describe installing Wireshark, capturing and decrypting HTTP/2 traffic, and analyzing the result.

Prerequisites

These steps assume you already have a working version of Ubuntu installed and ready to go. I’m using Ubuntu 18.04 running in VirtualBox on Windows 10 Pro 64-bit. Refer this article if you need to install Ubuntu 18 in VirtualBox.

Create directories

  1. The first step is to create the /docker-nginx directory to contain the project. Open a new terminal and enter the below:

    mkdir docker-nginx
    
  2. Now change into the /docker-nginx directory and create 3 subdirectories:

    cd docker-nginx
    mkdir conf
    mkdir ssl
    mkdir html
    
    • The conf directory will store the NGINX configuration file and dockerfile used for building the container.
    • The ssl directory will store SSL certificates for use by NGINX
    • The html directory will store the HTML files to be served by NGINX

Generate self-signed SSL certificate

  1. HTTP/2 requires TLS encryption so we’ll use openssl to generate a self-signed SSL certificate for NGINX to use. Ensure you are within the /docker-nginx directory and enter the below command to generate a certificate for localhost:

    openssl req -x509 -nodes -newkey rsa:4096 -subj "/C=DE/ST=Berlin/L=Berlin/O=Codeooze AG/OU=IT/CN=localhost" -keyout ssl/nginx.key -out ssl/nginx.crt
    

    openssl prompt

  2. Verify there are 2 files created in the /docker-nginx/ssl directory:

    • nginx.crt
    • nginx.key

Create a simple webpage

Now we need to create some content to be served by NGINX. These will be created in the /html subdirectory. To demonstrate HTTP/2 server push we will create home.html which references several other resources, including a CSS file, Javascript file, and 2 images. So we need to create the following files:

  • home.html
  • bootstrap.css
  • bootstrap.js
  • img1.jpg
  • img2.jpg

For demonstration I’ll use Bootstrap for the CSS and JavaScript files. Bootstrap is a popular front-end library used in many websites.

  1. In Ubuntu, launch Firefox and navigate to https://getbootstrap.com/

  2. Click the Download link

  3. Download the Compiled CSS and JS version.

  4. Extract the downloaded zip file. It should contain 2 directories: css and js. Copy the following files into /docker-nginx/html:

    • css/bootstrap.css
    • js/bootstrap.js
  5. Next you will need 2 image files, img1.jpg and img2.jpg. You can use any images and there are many sites offering free stock images you can try out, for example:

    Ensure you rename your images to img1.jpg and img2.jpg and place in the /docker-nginx/html directory.

    When you’re done the /docker-nginx/html should look like this:

    html directory files

  6. Finally, create the home.html file. This will be a very simple webpage that references bootstrap.css, bootstrap.js and the 2 images.

    <html>
      <head>
        <title>Home page</title>
        <link rel="stylesheet" type="text/css" href="bootstrap.css">
        <script src="bootstrap.js"></script>
      </head>
      <body>
        <h1>Welcome to the home page</h1>
        <p>Browse the images below</p>
        <div class="card-group">
          <div class="card" style="width: 18rem;">
            <img class="card-img-top" src="img1.jpg">
          </div>
          <div class="card" style="width: 18rem;">
            <img class="card-img-top" src="img2.jpg">
          </div>
        </div>
      </body>
    </html>
    

    You can test the webpage by launching it in Firefox:

    firefox home.html &
    

    website screenshot

Create the NGINX config file

  1. Change directory into /docker-nginx/conf and create a new file nginx.conf and paste the below. You can use vi or any text editor to create this file.

    server {
        listen       443 ssl http2;
        server_name  localhost;
    
        ssl_certificate      /etc/nginx/ssl/nginx.crt;
        ssl_certificate_key  /etc/nginx/ssl/nginx.key;
            
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
            
        # HTTP/2 server push
        # whenever a client requests home.html, also push
        # /bootstrap.css, /bootstrap.js, /img1.jpg, and /img2.jpg
        location = /home.html {
            root   /usr/share/nginx/html;
            http2_push /bootstrap.css;
            http2_push /bootstrap.js;
            http2_push /img1.jpg;
            http2_push /img2.jpg;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
    
  2. Some points to note about this file:

    • listen 443 ssl http2 - the server will listen on port 443 for ssl and http2 protocols
    • ssl_certificate and ssl_certificate_key - the previously generated SSL certificate and key are referenced (do not change the paths - these will be copied into this location of the docker container in later steps)
    • http2_push - HTTP/2 server push is configured such that when home.html is requested, the server will automatically push additional files to the client: /bootstrap.css, /bootstrap.js, /img1.jpg, and /img2.jpg.

Checkpoint

Well done for getting this far. You have now completed the following:

  • Generated self-signed SSL certificates for NGINX
  • Created a simple webpage home.html that references 4 resources: /bootstrap.css, /bootstrap.js, /img1.jpg, and /img2.jpg
  • Created the NGINX file nginx.conf and configured to use SSL and HTTP/2 server push