How to create a static web server for HTML with NGINX

Marcel Dempers
4 min readMay 8, 2019

--

I get this question quite often. People ask whats a good way to host an AngularJS, ReactJS, JavaScript, JQuery, Bootstrap HTML based website.

In this post, I am going to demonstrate how to create and run a web server that’s great at serving static HTML websites. It’s called NGINX, let’s do this!

You might be thinking, Why NGINX ?

NGINX is extremely fast. It can serve thousands of requests per second. NGINX is also very lightweight. It’s optimized to serve static files.

When running on a server, you can hardly notice it’s there when you look at the CPU\Memory footprint.

It’s also great at servicing SSL traffic and it’s built with security in mind.

Many runtimes like NodeJS are not designed to sit right in public traffic because of their attack surface. It’s always recommended to run these runtimes behind a proxy or loadbalancer so they’re not directly accessible.

Now NGINX is a loadbalancer. It can proxy pass traffic to multiple back-ends, so you can run a back-end NodeJS API behind it if you want. It therefore helps you to reduce the attack surface of your website because it’s designed to deal with public traffic.

Let’s start by creating a container

Let’s start out with a simple dockerfile. In this example we’re going to have a single HTML file.

FROM nginx:1.15.8-alpine#configurationcopy ./nginx.conf /etc/nginx/nginx.conf#content, comment out the ones you dont need!copy ./*.html /usr/share/nginx/html/#copy ./*.css /usr/share/nginx/html/#copy ./*.png /usr/share/nginx/html/#copy ./*.js /usr/share/nginx/html/

FROM nginx:1.15.8-alpine: The Alpine operating system keeps our attack surface even lower. It’s roughly 4MB in size. If an attacker compromises the container, there would be a limited set of tools compared to a larger operating system.

When we add the NGINX binaries to the Alpine image, it’s like 16 MB :) So it’s super tiny. You can’t go wrong.

We use the docker COPY directive in the dockerfileto copy what we need inside the container image. We only copy what we need, not ‘*’ — keeping the image small. You can use more COPY directives and combinations of wildcards to add more files if needed.

The main step is copying our NGINX config file, nginx.conf Let’s take a look at that!

Firstly, we run the process as an unprivileged user, so an attacker cannot do much if the container is compromised:

user nginx;

We can tune the number of worker processes, connections and configure logging

worker_processes 1;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events {worker_connections 1024;}

In the “http” block, we define the mime types, and do some basic default logging format and access log configuration

http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main ‘$remote_addr — $remote_user [$time_local] “$request” ‘‘$status $body_bytes_sent “$http_referer” ‘‘“$http_user_agent” “$http_x_forwarded_for”’;access_log /var/log/nginx/access.log main;#to be continued...

Server block is where the important stuff is. We configure a port to listen on.

server {
listen 80;
#to be continued...

It’s always important for our server to have some kind of health probe. This helps container orchestrators like Kubernetes and Docker swarm to make scheduling decisions based on the health of our NGINX server. Also, if you have loadbalancers in front of this NGINX server, it may have health probe features you can take advantage of if you have a health /status endpoint.

location = /status {access_log off;default_type text/plain;add_header Content-Type text/plain;return 200 “alive”;}

Next stop, is the location block. We define all traffic / that hits our server, to receive an HTML file called index.html This is our home page, the page the user will see when coming to our website. We also define the folder where we will be able to serve additional HTML content from /usr/share/nginx/html/

Notice that the folder above is the same as the one in the dockerfile where we copy all our static files into.

If you want to serve other pages, they will need to be in this folder. Including JavaScript, CSS stylesheets and images.

location / {
gzip off;
root /usr/share/nginx/html/;
index index.html;
}

We also specify location ~* \.(js|jpg|png|css)$ to tell NGINX to serve requests for static resources such as JavaScript, Style sheets and images.

  location ~* \.(js|jpg|png|css)$ {
root /usr/share/nginx/html/;
}

sendfile is a configuration to optimize file sending.

sendfile on;
keepalive_timeout 65;

By default, NGINX handles file transmission itself and copies the file into the buffer before sending it. By enabling the sendfile config, we eliminate the step of copying the data into the buffer and enable direct copying of data from one file descriptor to another.

Now that we have a container, we need some HTML content. Checkout the gist below where we put everything together:

With these three files in the same folder we can run:

docker build . -t our-server

And run it using:

docker run -it --rm -p 8080:80 our-server

Our static HTML web server will be up at http://localhost:8080

Hope this helps anyone trying to host static content! Come say hi on YouTube

Follow me on Twitter for more on DevOps and architecture :)

--

--

Responses (1)