Posted on 2 minutes read

TLDR

Nginx explorer is a minimal web interface for file download/upload using only nginx builtin modules.

nginx explorer interface

git clone https://github.com/izissise/nginx-explorer.git
nginx-explorer/ngxp.sh download_icons
nginx-explorer/ngxp.sh servethis

Genesis

Sharing files across devices can be a hassle. Luckily, most devices support HTTP through a browser, which enables both file download and upload.

There are existing options:

  • python -m SimpleHTTPServer, but performs poorly with large files.
  • miniserve with good performance and a lot of features.

But today I want to write on nginx, it has a builtin file listing module and it is a battle tested webserver with great performances!

Nginx

We configure nginx to list files using autoindex module:

charset UTF-8;
server_tokens off;

server {
    listen 8080;
    location / {
        root /home/user/downloads;
        autoindex on;
        autoindex_format html;
    }
}

Now let's add some personalization, nginx autoindex start the response with an <html> tag, we can use nginx sub_filter to replace the tag and inject our html:

...
map $uri $cond_filter {
    ~/$ "<!DOCTYPE html><html><link rel='stylesheet' href='/___ngxp/main.css' /><script src='/___ngxp/main.js'></script>";
    default "<html>";
}

server {
    ...
    location / {
        ...
        sub_filter_once on;
        sub_filter "<html>" $cond_filter;
    }
}

There are multiple things happening there:

  1. we want to replace the tag only once sub_filter_once on;
  2. we don't want files download to be modified, using map $uri we inject html only for directories (when the uri ends with a '/')
  3. for some reasons autoindex does not output <!DOCTYPE html>, so we add it.

For main.css and main.js, we set up a separate path to exclude them from the file listing:

...
server {
    ...
    location ^~ /___ngxp/ {
        alias /var/www/ngxp/;
    }
}

We use alias because we don't want to keep the /___ngxp/ part to find app files.

Last thing is to configure nginx for large files. With the sendfile directive nginx will tell the kernel to directly copy the data from the file to the tcp socket. gzip on; will compress text based files, for faster transfer of app files.

gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/javascript text/js text/css text/xml text/plain application/javascript application/xml application/x-javascript image/svg+xml;

sendfile           on;
sendfile_max_chunk 2m;
tcp_nopush         on;
tcp_nodelay        on;
keepalive_timeout  65;

To run this in a container, mount the configuration and directories:

docker run \
        --rm -it --log-driver=none \
        --user="$(id -u):$(id -g)" \
        --userns=keep-id --cap-drop=ALL \
        --tmpfs=/tmp:rw,noexec,nosuid,size=70m \
        --expose=8080 -p 8080:8080 \
        -v "$PWD/docker_nginx.conf:/etc/nginx/nginx.conf:ro" \
        -v "$PWD/nginx-explorer.conf:/etc/nginx/conf.d/default.conf:ro" \
        -v "$PWD/main.css:/var/www/ngxp/main.css:ro" \
        -v "$PWD/main.js:/var/www/ngxp/main.js:ro" \
        -v "$HOME:/home/user/downloads:ro" \
        nginx

docker_nginx.conf configures nginx for a read-only container[1]. nginx-explorer.conf the configuration we built. $HOME is the path that will be listed.

Next post we'll see how to use cookies to have a per directories authentication.

Happy files transfer!


  1. It puts the pid file in /tmp ↩