Nginx and External Config Files for Redirects

Nginx is my favourite webserver and that's mostly because I like the config file format and how many features it brings for my needs like reverse proxy for node apps, static file serving and cache headers for SPAs and rewriting APIs into specific paths of a URL.

I'm currently working on a project that will need a few redirects to weed out typos in URLs, to deprecate a number of them (which will just yield a 410 http error code) and otherwise will mostly be static content.

This is why I thought it would be a great idea to find the most convenient way for me to create a lot of redirects in nginx and to have them as part of the project, in code version control and not on some server where they will be forgotten about until something goes wrong.

A short introduction to nginx maps

For the following two cases (the 301/302 redirect) and the 410 redirect we can use maps, which are the shortest possible way to store all of the desired URLs if we have many redirects or retired pages.

inside the default.conf

map $request_uri $rewrite_url {
  include /redirects/redirects.map;
}

content of the redirects.map

/old-url1 /new-url1;
/old-url2 /new-url2;

so you can use those methods to additionally decrease your burden of writing and maintaining these config files, however we'll not be making use of maps in the example files because they make things needlessly complex ¯\_(ツ)_/¯. If you want maps, map them yourself!

If you decide to use maps you might come across a specific error message:

nginx: [emerg] could not build map_hash, you should increase map_hash_bucket_size: 64

which just means you need to increase some memory limits in the http block of your server:

http {
  map_hash_max_size 8000;
  map_hash_bucket_size 8000;
  # [...]
}

External Redirects File

For the redirects we'll use a format like this (301.redirects):

rewrite /old-url$ /new-url permanent;

and in the configuration file we will need to include it like so:

server {
  listen 80 default_server;
  port_in_redirect off;

  location / {
    root /var/www/html;
    try_files $uri $uri/ /404.html;
  }

  location /new-url {
    root /var/www/html;
    try_files $uri $uri/ /404.html;
  }

    # including the redirects file
  include ./conf.d/redirects/301.redirects;
}

With this we'll make sure that if somebody wants to access the old URL, for example: http://example.com/old-url it will be redirected to the new url like http://example.com/new-url.

Nginx 410 GONE

When migrating a site it's not atypical to simply retire some pages and to let search engines know that this, in fact, is intentional and not some accident, can be achieved through the 410 http status code.

To retire some pages we can include them in the same fashion as with the redirects:

# including the redirects file
  include ./conf.d/redirects/301.redirects;
    # include retired pages
  include ./conf.d/redirects/410.redirects;
}

and then we add a file called 410.redirects in the redirects directory. You can call these files anything you want by the way, just not anything ending in .conf.

location = /deprecated {
  return 410;
}

So when a user accesses http://example.com/deprecated they will simply see the text:

410 Gone
nginx/1.19.1

and search engines will know to no longer send traffic to that page.

Wrapping it up (in a container)

Now, I thought it'd be much more fun to preview this in a docker container than to just write about it, so I put all the config files and the dockerfile into a small repository: https://github.com/JonathanMH/nginx-external-redirect

You can either build the docker file by building:

docker build -t nginx-external-redirect .
docker run --init -p 80:5000 -t nginx-external-redirect

or use docker-compose:

docker-compose up

and you can try the following URLs:

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!