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

Part 2: Docker install and container build

This article is part 2 in a series about HTTP2 server push in NGINX. This article continues with the environment setup by describing how to install Docker and configure a Docker container running the NGINX web server with HTTP/2 and HTTPS in Ubuntu 18.

Previously…

Check out these previous articles which describe setting up the environment and configuring NGINX web server for HTTP/2:

Install Docker

Now we are ready to install Docker and build a container running NGINX.

Follow the steps below to install Docker in Ubuntu 18 using the repository, or you can refer the official Docker Docs for other options.

  1. Launch a new terminal window

  2. First, make sure apt supports HTTPS:

    sudo apt-get update
    
    sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
    
  3. Add Docker’s GPG key:

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    

    docker gpg key add command

  4. Enter the below to get the latest stable docker repository:

    sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
    

    docker repository

  5. Now we can install Docker with the following commands:

    sudo apt-get update
    sudo apt-get install docker-ce docker-ce-cli containerd.io
    
  6. Finally, verify the installation by running the hello-world image:

    sudo docker run hello-world
    

    The hello-world image will not exist, so Docker will download it then run it. You should see something like the below:

    docker hello-world

Build and run the NGINX docker container

Now we are ready to build a docker container running NGINX web server.

  1. Change into the /docker-nginx/conf directory and create a file named dockerfile containing the below:

    FROM nginx
    COPY ./html/ /usr/share/nginx/html
    COPY ./conf/nginx.conf /etc/nginx/conf.d/nginx.conf
    COPY ./ssl/ /etc/nginx/ssl
    

    Some notes on what this means:

    • FROM nginx tells Docker we want to use the nginx image.
    • Next we tell docker to copy the local /html subdirectory into the container at location /usr/share/nginx/html.
    • nginx.conf is copied into container location /etc/nginx/conf.d/nginx.conf.
    • The ssl/ directory containing the self-signed certificates is copied to container location /etc/nginx/ssl

    Keep in mind that making changes to any of the copied files after the container has been built will require a re-build of the container.

  2. Change into the /docker-nginx directory and enter the following to build the container:

    sudo docker build -f conf/dockerfile -t nginx-http2 .
    
    • The -f option tells Docker to use the dockerfile created previously in the /conf directory.
    • The -t option will tag the container with the name nginx-http2

    Since the nginx image does not exist, Docker will download it as part of the build.

  3. Ensure you do not get any errors and all files are successfully copied to the container. A clean build will look similar to the screenshot below.

    docker build

  4. List the available docker images to verify the build completed successfully:

    sudo docker images
    

    If you have followed all the previous steps there should be 3 images listed:

    • nginx-http2
    • nginx
    • hello-world

    docker images

    If you have hit an error and need to delete an image before rebuilding, use the below command:

    sudo docker rmi [image_name OR image_id]
    
  5. Run the docker container:

    sudo docker run -dit --name nginx-http -p 80:80 -p 443:443 nginx-http2
    

    This will map container ports 80 and 443 to local ports 80 and 443.

    Note: If there is an existing service running on local port 80 or port 443 you will get an error. Modify the command to use a free port and try again.

  6. Now connect to a terminal within the running container:

    sudo docker exec -it nginx-http /bin/bash
    

    This will behave like a normal terminal. For example, you can change directories to /usr/share/nginx/html where the webpage was copied and view the files:

    cd /usr/share/nginx/html
    ls -l
    

    docker terminal

    Exit the terminal by entering the command exit and hitting Enter.

  7. You should be able to access the NGINX web server on localhost port 443. Open Firefox and browse to https://localhost

    Since we are using a self-signed certificate you will get a warning: Your connection is not secure

    firefox SSL warning

    Click Advanced then click Add Exception….

    In the prompt click Confirm Security Exception.

    firefox SSL warning

  8. If everything has worked you will see the NGINX welcome page.

    nginx welcome page

  9. Navigate to the webpage you created at https://localhost/home.html

    nginx webpage

  10. The docker container will contine to run in the background. You can stop the container with the following command:

    sudo docker rm -f nginx-http
    
  11. You can also start the container with output printed to stdout for debugging purposes:

    sudo docker run -a STDOUT -it --name nginx-http -p 80:80 -p 443:443 nginx-http2
    

    Refresh the webpage in Firefox and check the terminal for output.

    You can see in the debugging output that HTTP/2 is being used.

    nginx debugging

Other ways to validate HTTP/2

There are a number of other ways to verify the webpage is using HTTP/2. Below are 2 examples.

Using Firefox to validate HTTP/2

  1. Open Firefox and navigate to https://localhost/home.html.

    Note: You can also explicity specify HTTP2 with the following URL, however this shouldn’t be neccessary: https://http2.localhost/home.html

  2. In Firefox, open the Menu and select Web Developer > Network, or alternatively hit Ctrl + Shift + E.

  3. To add the Protocol column, right-click one of the existing column headers (such as Status, Method or Domain) and select Protocol.

    Firefox network tab

  4. Now refresh the page to populate the Network activity details.

  5. The Protocol should show as HTTP/2.0

    Firefox network tab

Using curl to validate HTTP/2

  1. Open a new terminal and enter the below curl command:

    curl -I -k https://localhost/home.html
    
    • -I option tells curl to fetch the HTTP headers only
    • -k option tells curl to accept the self-signed SSL certificate
  2. You should see the HTTP/2 header returned:

    HTTP/2 200 
    server: nginx/1.15.8
    date: Mon, 11 Feb 2019 08:03:46 GMT
    content-type: text/html
    content-length: 571
    last-modified: Mon, 11 Feb 2019 04:55:09 GMT
    etag: "5c61002d-23b"
    accept-ranges: bytes
    

    curl http2

Verify HTTP/2 Server Push

At the time of writing (Firefox v65.0 64-bit) I was unable to find a way in Firefox to verify that server push is working. There are a few long-standing open bugs related to this:

Google Chrome to the rescue.

  1. If you don’t already have Chrome installed in Ubuntu, download and install it from https://www.google.com/chrome/

  2. Open Google Chrome

  3. Enable the Developer tools by opening the menu and selecting More tools > Developer tools (or hit Ctrl + Shift + i)

  4. Select the Network tab

  5. Navigate to https://http2.localhost/home.html

  6. You will get a security warning similar to Firefox. Again, this is because we used a self-signed certificate.

    chrome ssl warning

  7. Click Advanced and then click Proceed to http2.localhost (unsafe)

  8. The page will load and the Network tab will populate to indicate the network activity.

    The Initiator column will indicate that the resources were pushed with /home.html.

    chrome server push

    You can also add the Protocol column (same steps as in Firefox) which will show h2, which indicates HTTP/2 was used.

Conclusion

Congratulations for getting this far! There were quite a few steps involved but now we have successfully confirmed that NGINX is serving our website using HTTP/2 and server push.

Stay tuned for the next article which will describe how to capture and decrypt this HTTP/2 traffic using Wireshark.