17 sept 2023
Introduction to nginx
ToC
Introduction
Besides my usual dayjob shennanigans I’ve acquired some freelancing experience doing some design for a national dancer which hired me to do a portfolio for her projects and career. In the beginning this was a simple static page that I’d write in Svelte like this blog and basically would act as a contact card in steroids for her. After some prototyping and design I quickly stumbled upon the hydra of this work: the gallery. The images originally were served as static assets in the page, hosted in Vercel. That would be more than ok for most of the static pages you find around the web, but we are talking about high resolution pictures and in a scenic art poor records and media of it its definitely a no-no.
I started wheighing my options: I’m kinda well versed in Node and TypeScript, so this could be a quick and easy project. Just host the process in one of the many free hosting sites that crave for the sweet sweet bytes of your data and personal identifiable information and you are good-to-go. That would be the end of story but sadly, I lack common sense and I’m a masochist, so I decided to take it further and use it as oportunity to learn .NET and decided to go with it since C# is the main language that my current company uses. In that sense, I’m the black sheep so it would be nice to finally blend in and learn at least the basics of a new language, MS SQL, and Azure… Oh, boy I was wrong on that one.
Learning nginx because I’m broke (and eager to learn)
Since Azure costs an eye and this is a one-off project that I don’t want to pay for the hosting and free services usually come with the caveat of being slow and getting cold too quickly even if being requested frequently (such as MongoDB Atlas free tier). So I figured that having a VPS would be the best option: I could host multiple projects on the same machine; have multiple projects in diferent languages or tools; keep my machine private; have a better location for the users of my apps to connect to since I can host have it hosted in the US.
Context and directives
nginx
uses a similar syntax to common JSON using a key-value pair approach. Minus some differences like the syntax being:
keyword {
keyword {
key value; # Notice the key and value have no colon and ends with a
# semicolon.
}
}
However, these key-values are known as directives which, as we’ll see, tell
nginx
how to behave and what property to work with and what value to pass it.
Then there are so-called directive, which are declarations using keywords such
as: http, server, events. All of which recieve directives and tell nginx
how
to behave and are called context.
Take for example, this barebones nginx.conf
:
http {
server {
listen 8080;
root /home/hapless/nginx-basics;
}
}
events {}
Lets break down this conf snippet:
http
: context gives information on what to do with http requests that come to the machine that is running the nginx process.server
: creates the instance of the server itself. In this case, we set up port 8080 so anything that goes through ourlocalhost:8080
should be communicating with nginx. The default is port 80.root
: will take a directory where all files that match the types included in the context of the service will be served from. By default,nginx
serveshtml
with no including or context of types provided.
Ownership of assets
There are some quiet defaults that can lead you to some trouble when setting up
a nginx
instance. One of them is the ownership of the files being served to
the client. By default, nginx starts the process as user www:www
. This can lead
to some authorization issues, specially on Linux. Since my user hapless, created
the static files that I want to serve in the nginx-basics
, the default nginx
user wouldn’t be able to serve it once localhost:8080
is requested. Remember
to, either initialize nginx
with the user directive that owns the files to be
served or chmod
them as a whole.
user hapless hapless;
http {
include mime.types;
server {
listen 8080;
root /home/haplesss/nginx-basics;
}
}
Location Context
Creating a location
So far we’ve managed to serve a static and styled html
using nginx which is
currently sitting on the nginx-basics
’s root directory. However, we can
specify what else to serve with the location
keyword, that opens a diretory
inside the directed route.
We’ll create a directory and inside it, create a new index.html
(mkdir fruits && touch fruits/index.html
) with a different body. In this case, we’ll serve a list of fruits. Then we’ll add it to our nginx.conf
. Note that we pass the same route, so what nginx does
is to append or concat the declared location. Now this new html will be
accesible via localhost:8080/fruits
.
http {
include mime.types;
server {
listen 8080;
root /home/hapless/nginx-basics;
location /fruits {
root /home/hapless/nginx-basics;
}
}
}
Aliases and locations
We can reuse assets with the alias
keyword, so instead of writing a new
location and point to fruits, we can use the same route and declare it as alias.
If we were to do it the same as location /fruit
it’d append /carbs
and throw
a 404 to the client.
http {
include mime.types;
server {
listen 8080;
root /home/hapless/nginx-basics;
location /fruits {
root /home/hapless/nginx-basics;
}
location /carbs {
alias /home/hapless/nginx-basics/fruits;
}
}
}
By default nginx looks for index.html
, but we can specify the file to be
served using try_files
that takes kinda a weird syntax.
http {
include mime.types;
server {
# ...
location /vegetables {
alias /home/hapless/nginx-basics;
try_files /vegetables/veggies.html /index.html = 404;
}
}
}
Let’s see it and break it down.
try_files /vegetables/veggies.html # look for this specific asset
/index.html # if not, use the root index.html
= 404; # if that fails, throw a 404
Regexes
We can use regular expressions to create a new route and serve a specific file
if the route requested meets a given criteria. In this example, we declare the
use of of a regex using ~*
and the square brackets. The route /count
will
take any number and will serve the root index.html
and 404 if it doesn’t match.
http {
include mime.types;
server {
# ...
location ~* /count/[0-9] {
root /home/hapless/nginx-basics;
try_files /index.html = 404;
}
}
}
Redirects
Redirects with http code 307 to the content to be served in location /fruits
http {
include mime.types;
server {
location /crops {
return 307 /fruits;
}
}
}
Final configuration
Our final configuration would have looked like this:
http {
include mime.types;
server {
listen 8080;
root /home/hapless/nginx-basics;
location /fruits {
root /home/hapless/nginx-basics;
}
location /carbs {
alias /home/hapless/nginx-basics/fruits;
}
location /vegetables {
root /home/hapless/nginx-basics;
try_files /vegetables/veggies.html /index.html = 404;
}
location ~* /count/[0-9] {
root /home/hapless/nginx-basics;
try_files /index.html = 404;
}
location /crops {
return 307 /fruits;
}
}
}
Conclusion
For this first approach to nginx
we’d get the following configuration. Next
time, I’ll take a peek to load balancing and rewrites of it. I need to make this
redirect the request to my .NET web api and send the client back the
information. In my case, a combination of assets and metadata for photos.
nginx
its an awesome tool and I’m glad for having my feet wet with this
project that will certainly teach me how to design robust systems and take
advantage of containers and VPSs.