This post briefly demonstrates how to atomically switch a directory tree of static files served by nginx.
Consider the following minimal nginx config file:
$ cat conf/nginx.conf events { use epoll; } http { server { listen 0.0.0.0:80; location / { root /static/current ; } } }
The goal is to replace the directory /static/current
atomically while nginx is running.
This snippet shows the directory layout that I started out with:
$ tree . ├── conf │ └── nginx.conf └── static ├── version-1 │ └── hello.html └── version-2 └── hello.html
conf/nginx.conf
is shown above. The static
directory contains two sub trees, and the goal is to switch from version-1 to version-2.
For this demonstration I have started a containerized nginx from its official Docker image:
$ docker run -v $(realpath static):/static:ro -v $(realpath conf):/etc/nginx:ro -p 127.0.0.1:8088:80 nginx nginx -g 'daemon off;'
This mounts the ./static
directory as well as the nginx configuration file into the container, and exposes nginx listening on port 8088 on the local network interface of the host machine.
Then, in the ./static
directory one can choose the directory tree served by nginx by setting a symbolic link, and one can subsequently switch the directory tree atomically, as follows:
1) No symbolic link is set yet — leading to a 404 HTTP response (the path /static/current
does not exist in the container from nginx’ point of view):
$ curl http://localhost:8088/hello.html <html> <head><title>404 Not Found</title></head> <body> <center><h1>404 Not Found</h1></center> <hr><center>nginx/1.15.6</center> </body> </html>
2) Set the current
symlink to serve version-1:
$ cd static $ ln -s version-1 current && curl http://localhost:8088/hello.html hello 1
3) Prepare a new symlink for version-2 (but don’t switch yet):
$ ln -s version-2 newcurrent
4) Atomically switch to serving version-2:
$ mv -fT newcurrent current && curl http://localhost:8088/hello.html hello 2
In step (4) It is essential to use mv -fT
which changes the symlink with a rename()
system call. ln -sfn
would also appear to work, but it uses two system calls under the hood and therefore leaves a brief time window during which opening files can fail because the path is invalid.
Final directory layout including the symlink current
(currently pointing to version-2):
$ tree . ├── conf │ └── nginx.conf └── static ├── current -> version-2 ├── version-1 │ └── hello.html └── version-2 └── hello.html
Kudos to https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html for being a great reference.
Leave a Reply