Introduction
Every second, an invisible war unfolds at the edge of the internet. Before your web application ever gets a chance to respond to a user, it must survive the gauntlet of bots, scanners, malicious payloads, and curious intruders. In this hostile landscape, web servers like NGINX have become the silent gatekeepers. But by default, NGINX is just that—a gatekeeper, not a guard. It routes, balances, and caches. It does not judge.
Now imagine if your web server could think—if it could question requests, challenge intent, and respond with the caution of a seasoned security analyst. This is where Web Application Firewall (WAF) tools like Naxsi enter the picture: an intelligent WAF designed to sit within NGINX and transform it from a passive handler of traffic into an active defender of your application.
The original NAXSI project has been archived, but the project still continues to be maintained and has moved to a new home.
Orignal Naxsi
New Naxsi
This article isn’t just a technical walkthrough—it’s an invitation to rethink the role of your web server. We'll explore how to secure NGINX using Naxsi, not as a bolt-on afterthought, but as an integrated component of a modern security-first architecture.
Available Options for WAF
We already discussed in the previous blog post what all options are available for using as a WAF - Naxsi, CrowdSec, ModSecurity etc. All can be used independently or combined with other options. While using Naxsi, we can also use CrowdSec as well, which would mean, that CrowdSec makes our outer firewall, while Naxsi becomes the inner Firewall. It surely means more system resources shall be used as both the firewalls would be intercepting every incoming request, but you will be getting in return the securtiy, that you wanted for your applications/websites.
What is Naxsi and Why Should You Use It?
Naxsi (short for Nginx Anti XSS & SQL Injection) is an open-source WAF designed specifically for NGINX. Unlike traditional signature-based WAFs, Naxsi operates on a ruleset model that detects common patterns associated with XSS, SQL injection, and other injection-based attacks. Its core strength lies in its simplicity and integration flexibility. It's lightweight, customizable, and—most importantly—it runs directly inside the NGINX engine, making it a natural extension of the web server.
To set up Naxsi with NGINX, the source code of both NGINX and Naxsi has to be downloaded; thereafter, NGINX has to be configured and compiled with Naxsi to allow NGINX to recognize Naxsi
For this blog, we are using the following
- Debian 11 (Raspbian Bullseye)
arm64
based processor- nginx
v1.18.0
(Use the latest version available.)
Preparation
To start with, update all the package links and install the updates available for the operating system. For our case, the machine can be upgraded using the following command.
sudo apt update && sudo apt upgrade -y
The next step is to install the necessary packages. Since we would need to download the source code of the relevant software and compile it, we would need to install the following dependencies
sudo apt install -y git \
build-essential \
libpcre3 \
libpcre3-dev \
libssl-dev \
zlib1g-dev \
wget \
curl \
docker.io \
docker-compose
Next would be to enable the Docker service
sudo systemctl enable --now docker
Before proceeding, it is important to back up the config and current NGINX binary. To do that, use the following commands
sudo cp -r /etc/nginx /etc/nginx.bak
sudo cp /usr/sbin/nginx /usr/sbin/nginx.bak
Compile NGINX with support for Naxsi
The next step in the process is to build the NGINX with the Naxsi module.
To do that, we first need to download the source code. We can use the following sequence of commands to do that
mkdir -p ~/build-nginx && cd ~/build-nginx
git clone https://github.com/nbs-system/naxsi.git
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -xzvf nginx-1.18.0.tar.gz
cd nginx-1.18.0
The commands above are all self-explanatory. We first created the directories and changed directory. We then cloned the naxsi's source code into the directory, also downloaded the nginx's compressed package, which we then uncompressed.
Now, configure NGINX with all the relevant flags
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/run/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_xslt_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-http_perl_module=dynamic \
--with-stream=dynamic \
--with-stream_geoip_module=dynamic \
--with-http_geoip_module=dynamic \
--with-http_dav_module \
--with-http_flv_module \
--with-compat \
--add-module=../naxsi/naxsi_src \
--add-dynamic-module=../ngx_devel_kit \
--add-dynamic-module=../lua-nginx-module \
--add-dynamic-module=../headers-more-nginx-module \
--add-dynamic-module=../echo-nginx-module \
--add-dynamic-module=../ngx_http_substitutions_filter_module \
--add-dynamic-module=../ngx-fancyindex \
--add-dynamic-module=../nchan
Use updated NGINX
Once the configuration is done, rebuild and replace the NGINX binary using the commands below
make
sudo systemctl stop nginx
sudo cp objs/nginx /usr/sbin/nginx
sudo systemctl start nginx
This step only replaces the binary — your config and sites remain intact.
You can verify the NAXSI configuration applied in the NGINX
nginx -V 2>&1 | grep naxsi
- If empty, no NAXSI module path passed at compile time.
- If something like
--add-module=/path/to/naxsi
this is present, then you can rest assured that NAXSI’s compiled in.
Download the Naxsi core rules
The next step in the process is to download the core rules for the Naxsi. To do so, use the following command
curl -o naxsi_core.rules https://raw.githubusercontent.com/nbs-system/naxsi/master/naxsi_config/naxsi_core.rules
Add NAXSI to Your Existing Site
To update the NGINX's configuration to include the naxsi, edit the existing reverse proxy config as shown below
server {
listen 80;
server_name example.com;
location / {
include /etc/nginx/naxsi_core.rules;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /RequestDenied {
return 403 "Request blocked by NAXSI";
}
}
Test and Reload
Now, you can run NGINX and test the configuration used in NGINX. To do so, simply test the nginx config and reload the config if it is okay.
sudo nginx -t
sudo systemctl reload nginx
Stop NGINX auto-upgrade
Now, it could be that the system packages get updated and upgraded on a regular basis (using some cron job or perhaps using some automation tool). But when the NGINX upgrade is available, the new binary could be auto-installed and thus replace the changes we did to the NGINX binary. So, to keep the Nginx binary with the NAXSI (the version of NGINX we compiled), simply turn off the auto-upgrade of the NGINX package and its extras package. To do so, use the following commands.
sudo apt-mark hold nginx
sudo apt-mark hold nginx-extras
Troubleshoot
Now, if everything worked fine, your NGINX is using NAXSI as the WAF. But it could also mean that your reverse-proxy could block some of the sites (in the begining) that you wanted to be accessible.
You can check the error logs of the NAXSI using the command below
sudo grep NAXSI /var/log/nginx/error.log
The logs would appear something like this
12025/xx/xx 09:36:27 [error] 1186393#0: *15014 NAXSI_FMT: ip=xxx.xxx.xxx.xx&server=xxx.xxx.xxx.xx&ri=/hello.world&vers=1.3&total_processed=82&total_blocked=1&config=drop&zone0=ARGS&id0=20&var_name0=, client: xxx.xxx.xxx.xx, server: subdomain1.example.com, request: "POST /hello.world?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1", host: "xxx.xxx.xxx.xx:443"
2025/xx/xx 05:50:31 [error] 1186393#0: *13390 NAXSI_FMT: ip=xxx.xxx.xxx.xx&server=subdomain2.example.com&uri=/wp-cron.php&vers=1.3&total_processed=2&total_blocked=1&config=block&zone0=BODY&id0=16&var_name0=, client: xxx.xxx.xxx.xx, server: subdomain2.example.com, request: "POST /wp-cron.php?doing_wp_cron=1751086230.8430469036102294921875 HTTP/1.1", host: "subdomain2.example.com"
As you can see above, the error logs show two separate blocked requests, both have a different subdomain. What is important to note is the ID of the blocking rule that got triggered. In the first one, id0=20
is marked and in the second id0=16
is marked.
To fix this, simply add the rule's id in the exception in the NGINX's config, as shown below (for subdomain1.example.com
)
server {
listen 80;
server_name subdomain1.example.com;
location / {
include /etc/nginx/naxsi_core.rules;
SecRulesEnabled;
DeniedUrl "/RequestDenied";
# Whitelist for Naxsi
BasicRule wl:20 "mz:BODY"; # <-- Provide rule ID here
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /RequestDenied {
return 403 "Request blocked by NAXSI";
}
}
When changing the nginx configuration, a reload of nginx is required, after which the issue should be resolved.
Conclusion
NGINX already offers much of the security against many common type of online attacks. Adding NAXSI to the NGINX is bolstering that security and taking it to the next level. With the help of NAXSI, you can also use NGINX in a production environment, knowing that your reverse proxy can mitigate online attacks that come its way.