ngxhttpstat_check module

February 22, 2025 ยท View on GitHub

.. contents:: :backlinks: none

Alt-URLs for this repository:

Description

Simple nginx_ module that returns HTTP "403 Forbidden" error if stat()_ of specified path succeeds.

Intended use is trivial djbdns-style filesystem-db blacklisting by any nginx variable_ set on request, for example $remote_addr (think fail2ban).

.. _nginx: https://www.nginx.com/ .. _stat(): https://en.wikipedia.org/wiki/Stat_(system_call) .. _nginx variable: https://nginx.org/en/docs/varindex.html

Build / install

Module requires source code for same-ish nginx version as it will be loaded by::

% ver=1.16.1 # make sure it matches installed nginx version % curl -L https://nginx.org/download/nginx-$ver.tar.gz | tar -xz

% cd nginx-$ver % test -e Makefile || ./configure --with-compat --add-dynamic-module=.. % make modules

% cd objs % install -m755 -t /usr/lib/nginx/modules/ ngx_http_stat_check.so

Re-run two last bits to rebuild after any code or nginx updates.

Usage

Module adds single "stat_check" directive, which takes path template as its one argument, and is only allowed in "location" context.

::

load_module /usr/lib/nginx/modules/ngx_http_stat_check.so; ... location /my-files { alias /srv/www/my-files; autoindex on; stat_check /tmp/blacklist/$remote_addr; }

This will have module run single stat() call for every request, checking if "/tmp/blacklist/$remote_addr" path exists, returning "403 Forbidden" if that's the case.

For example, if request comes from 1.2.3.4 IP address (be sure to check docs on remote_addr first!_), path /tmp/blacklist/1.2.3.4 will be checked, and if exists, any request to /my-files location will be denied for this client.

Any other nginx variables can be used in the same way, including regexp-matched path fragments, mapped or custom-set values.

.. _check docs on remote_addr first!: https://nginx.org/en/docs/http/ngx_http_core_module.html#var_remote_addr

Why it exists

Intended use is blocking access dynamically to various unwanted http spam and bots on relatively idle servers (see also nginx-access-log-stat-block script_).

Regular nginx configuration does not allow that, as it lacks any kind of external/dynamic configuration.

It is however available in a premium Nginx Plus version via "keyval" module_ via JSON API, and maybe will be merged into open version someday, so be sure to check for that first.

While I don't think that extra per-request stat() might make a difference performance-wise, this module is not intended or tested for/in any kind of high-load environments, and might not be suitable against something like a DDoS attack - use CloudFlare, firewalls, Nginx Plus functionality or whatever dedicated solutions for that.

See "Dynamic blacklisting configuration" blog post here_ for even more info.

.. _nginx-access-log-stat-block script: https://github.com/mk-fg/fgtk#nginx-access-log-stat-block .. _premium Nginx Plus version via "keyval" module: https://docs.nginx.com/nginx/admin-guide/security-controls/blacklisting-ip-addresses/ .. _"Dynamic blacklisting configuration" blog post here: https://blog.fraggod.net/2020/01/03/dynamic-blacklisting-configuration-for-nginx-access-via-custom-module.html

Room for improvement

Items here look like an obvious improvements, mostly for flexibility and unlocking new vast use-cases, but doubt I'll get to these unless will need it myself.

  • Make HTTP error page configurable, e.g. like =403 in try_files directive.

  • Separate more complex directive that respects "root" and "alias" paths.

  • Option to invert exists/missing logic for returning errors.

  • Allow to specify $uri or @named-location to jump to on this condition.

  • Allow/use hashed paths to avoid storing plaintext request info and to allow easy nesting of these on filesystem (instead of having all nodes in one dir).