Skip to main content

NGINX / Security

NGINX NAXSI WAF: Complete Setup and Configuration Guide

by ,


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

Most web application firewalls work like antivirus software. They maintain massive rule databases of known attack signatures and try to match incoming requests against them. The NAXSI WAF takes the opposite approach entirely.

NAXSI (NGINX Anti XSS & SQL Injection) is a lightweight, high-performance NAXSI WAF module for NGINX. Instead of relying on thousands of regularly updated signatures, the NAXSI WAF ships with a small set of simple rules. These rules flag suspicious patterns — characters like <, ', SELECT, and .. that rarely appear in legitimate requests. You then whitelist the specific parameters and URLs where those characters are expected.

This whitelist-based approach means the NAXSI WAF can block unknown zero-day attacks that signature-based firewalls miss. If an attack uses suspicious characters (and virtually all do), NAXSI catches it — no signature update required.

In this guide, you will learn how to install, configure, and deploy the NAXSI WAF on NGINX for Rocky Linux, AlmaLinux, and RHEL systems.

NAXSI vs. ModSecurity: Why Choose the NAXSI WAF?

Before diving into setup, it helps to understand where NAXSI fits compared to ModSecurity, the other popular open-source WAF for NGINX:

Feature NAXSI ModSecurity
Approach Whitelist (allow known-good) Blacklist (block known-bad)
Core rules ~95 simple patterns 100,000+ OWASP CRS signatures
Zero-day protection Strong (blocks unknown patterns) Weak until signature update
Performance impact Minimal Moderate to high
False positives Higher initially (requires tuning) Lower out-of-box
Learning mode Built-in auto-learning Not available
Configuration Simple, readable syntax Complex SecLang rules
Memory usage Very low Higher (large rule sets)

Choose NAXSI when you want a lightweight WAF with strong zero-day protection. It does require time investment in the initial learning phase. Choose ModSecurity when you need broad out-of-box coverage with minimal tuning.

How the NAXSI WAF Works

Understanding NAXSI internals helps you configure it effectively.

The Scoring System

When a request arrives, NAXSI inspects four zones:

  • URL — the request path
  • ARGS — query string parameters (GET data)
  • BODY — POST data, JSON payloads, and multipart form data
  • HEADERSHTTP headers, including cookies

Each zone is checked against the core rules. When a rule matches, it does not immediately block the request. Instead, it adds points to one or more score categories:

  • $SQL — SQL injection indicators
  • $XSS — cross-site scripting indicators
  • $RFI — remote file inclusion indicators
  • $TRAVERSAL — directory traversal indicators
  • $EVADE — encoding evasion tricks
  • $UPLOAD — dangerous file upload indicators

After all rules have been evaluated, NAXSI compares the scores against your thresholds (defined with CheckRule). Only when a threshold is exceeded does NAXSI block the request.

For example, a single apostrophe ' in a query parameter scores $SQL:4 and $XSS:8. A semicolon ; adds another $SQL:4 and $XSS:8. With a threshold of $SQL >= 8, the apostrophe alone would not trigger a block. However, the apostrophe combined with a semicolon pushes SQL to 8 and triggers the block.

Request Processing Flow

Here is how NAXSI processes each request:

  1. The request enters the NGINX rewrite phase (very early)
  2. NAXSI checks if the module is enabled for this location
  3. For POST/PUT requests, NAXSI waits for the request body
  4. All four zones are scanned against the loaded rules
  5. Matched rules accumulate scores by category
  6. Whitelists suppress scoring for specific rule/zone combinations
  7. CheckRule thresholds determine the final action
  8. Blocked requests redirect to the DeniedUrl location

Installation

Install the NAXSI WAF module from the GetPageSpeed repository:

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-naxsi

Load the module by adding this line at the top of /etc/nginx/nginx.conf, before any other directives:

load_module modules/ngx_http_naxsi_module.so;

Verify the module loads correctly:

nginx -t

You should see:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

The installation also places the core rules file at /usr/share/nginx/naxsi/naxsi_core.rules. This file contains the default detection patterns.

Basic Configuration

A minimal NAXSI WAF setup requires three things. You must load the core rules, enable NAXSI in a location, and define what happens to blocked requests.

Step 1: Load Core Rules

Add the core rules include inside the http {} block. This must appear before any server {} blocks that use NAXSI:

http {
    # Load NAXSI core rules (must be at http level)
    include /usr/share/nginx/naxsi/naxsi_core.rules;

    # ... your server blocks ...
}

A practical approach is to create a file that loads first alphabetically:

# /etc/nginx/conf.d/00-naxsi-core.conf
include /usr/share/nginx/naxsi/naxsi_core.rules;

Step 2: Enable NAXSI in a Location

Add the NAXSI directives inside the location blocks you want to protect:

server {
    listen 80;
    server_name example.com;

    location / {
        # Enable NAXSI WAF
        SecRulesEnabled;

        # Where to redirect blocked requests
        DeniedUrl "/RequestDenied";

        # Score thresholds — block when exceeded
        CheckRule "$SQL >= 8" BLOCK;
        CheckRule "$RFI >= 8" BLOCK;
        CheckRule "$TRAVERSAL >= 4" BLOCK;
        CheckRule "$EVADE >= 4" BLOCK;
        CheckRule "$XSS >= 8" BLOCK;
        CheckRule "$UPLOAD >= 8" BLOCK;

        proxy_pass http://backend;
    }

    # Handler for blocked requests
    location /RequestDenied {
        internal;
        return 403 "Blocked by NAXSI WAF";
    }
}

The DeniedUrl directive is required when NAXSI is enabled. Without it, NGINX refuses to start with: Missing DeniedURL, abort.

Step 3: Test the Configuration

Test that NGINX accepts the configuration:

nginx -t

Then reload NGINX:

systemctl reload nginx

Verify NAXSI blocks a SQL injection attempt:

curl -o /dev/null -w "%{http_code}" "http://localhost/?id=1 UNION SELECT * FROM users"

You should see 403 — the request was blocked. A legitimate request returns 200:

curl -o /dev/null -w "%{http_code}" "http://localhost/"

Understanding the Core Rules

The core rules file (/usr/share/nginx/naxsi/naxsi_core.rules) contains about 95 rules organized by attack category. Here is what each category detects.

SQL Injection (IDs 1000–1099)

These rules detect SQL keywords, special characters, and hex encoding. For example:

MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex|drop"
         "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie"
         "s:$SQL:4" id:1000;

This rule uses a regular expression (rx:) to match SQL keywords. It checks the request body, URL, query arguments, and cookies. Each match adds 4 points to the $SQL score.

Cross-Site Scripting (IDs 1300–1399)

XSS rules detect HTML tags (<, >), JavaScript syntax (`, [, ]), and double-encoding. The angle brackets score $XSS:8 each. A single <script> tag immediately exceeds a threshold of 8.

Directory Traversal (IDs 1200–1299)

These rules catch path traversal sequences like .., /etc/passwd, and c:\\. They also detect encoded bypass variants like /.%2e/ and /..;/.

Remote File Inclusion (IDs 1100–1199)

RFI rules detect URL scheme injections: http://`,https://`, ftp://`,php://,file://,phar://,data://`, and other dangerous protocol handlers.

File Upload (IDs 1500–1600)

Upload rules check filenames in multipart form data. They flag dangerous extensions (.php, .asp, .jsp, .ht) and non-printable ASCII characters.

Learning Mode: Training NAXSI for Your Application

The most important NAXSI WAF feature is learning mode. When enabled, NAXSI logs violations but does not block requests. This lets you observe false positives and build whitelists.

Enable Learning Mode

Add the LearningMode directive to your location:

location / {
    SecRulesEnabled;
    LearningMode;

    DeniedUrl "/RequestDenied";

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$RFI >= 8" BLOCK;
    CheckRule "$TRAVERSAL >= 4" BLOCK;
    CheckRule "$EVADE >= 4" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;
    CheckRule "$UPLOAD >= 8" BLOCK;

    proxy_pass http://backend;
}

NAXSI still evaluates all rules and logs violations. However, it lets every request through. Log entries show config=learning instead of config=block:

NAXSI_FMT: ip=192.168.1.50&server=example.com&uri=/search&config=learning
&rid=6c0da6921b75285443d1f0e4bbf625d3&cscore0=$SQL&score0=12
&zone0=ARGS&id0=1000&var_name0=q

This log entry tells you:

  • ip: the client IP address
  • uri: the request path (/search)
  • id0: rule 1000 (SQL keywords) matched
  • zone0: the match was in ARGS (query string)
  • var_name0: specifically the parameter named q
  • score0: the SQL score reached 12

Analyze Learning Logs

Run your application normally while learning mode is active. Browse pages, submit forms, use search, and upload files. Then analyze the NAXSI log entries:

grep "NAXSI_FMT" /var/log/nginx/error.log | grep "config=learning"

For each false positive, create a whitelist rule. For example, if the search parameter q legitimately contains SQL-like words:

BasicRule wl:1000 "mz:$ARGS_VAR:q";

This tells NAXSI: “Skip rule 1000 when checking the parameter named q.”

  1. Deploy with LearningMode on a staging environment
  2. Exercise the application — every form, endpoint, and workflow
  3. Collect logs for at least 24–48 hours
  4. Generate whitelists from the log analysis
  5. Test whitelists with learning mode still on
  6. Remove LearningMode to switch to active blocking
  7. Monitor logs closely for the first few days

Whitelisting Rules

Whitelists are the core of NAXSI WAF tuning. They tell NAXSI which matches to ignore for specific parameters, URLs, or zones.

Whitelist Syntax

The basic syntax is:

BasicRule wl:<rule_ids> "mz:<match_zone>";
  • wl: — comma-separated rule IDs to whitelist (use wl:0 for all rules)
  • mz: — the match zone where the whitelist applies

Whitelist by Parameter Name

Whitelist specific rules for a named parameter:

# Allow SQL keywords in the "query" GET parameter
BasicRule wl:1000 "mz:$ARGS_VAR:query";

# Allow double quotes and commas in the "data" POST parameter
BasicRule wl:1001,1015 "mz:$BODY_VAR:data";

# Allow HTML tags in "content" POST parameter (rich text editors)
BasicRule wl:1302,1303 "mz:$BODY_VAR:content";

# Allow special characters in the Cookie header
BasicRule wl:1001,1015 "mz:$HEADERS_VAR:Cookie";

Whitelist by URL

Apply whitelists only for specific URLs:

# Allow SQL keywords in ARGS for the /api/search endpoint
BasicRule wl:1000 "mz:$URL:/api/search|ARGS";

# Allow all rules for POST body on the /webhook endpoint
BasicRule wl:0 "mz:$URL:/webhook|BODY";

Whitelist with Regex Patterns

Use regex match zones for broader patterns:

# Allow SQL keywords in ARGS for URLs starting with /api/
BasicRule wl:1000 "mz:$URL_X:^/api/|ARGS";

# Allow special chars in POST parameters starting with "json_"
BasicRule wl:1001,1010,1011,1015 "mz:$BODY_VAR_X:^json_";

Whitelist by Zone

Apply whitelists broadly across an entire zone:

# Allow double quotes anywhere in the request body
BasicRule wl:1001 "mz:BODY";

# Allow all rules for entire ARGS zone (not recommended)
BasicRule wl:0 "mz:ARGS";

WordPress Whitelists

Running WordPress behind NAXSI requires whitelists for common operations. Here is a practical starting set:

# WordPress admin AJAX
BasicRule wl:1000,1001,1005,1008,1009,1010,1011,1013,1015,1302,1303,1310,1311,1315 "mz:$URL:/wp-admin/admin-ajax.php|BODY";

# WordPress post editor (Gutenberg sends JSON with HTML)
BasicRule wl:1001,1005,1008,1010,1011,1013,1015,1302,1303,1310,1311,1314,1315 "mz:$URL_X:^/wp-json/|BODY";
BasicRule wl:1001,1005,1008,1010,1011,1013,1015,1302,1303,1310,1311,1314,1315 "mz:$URL_X:^/wp-json/|ARGS";

# WordPress login
BasicRule wl:1001,1009,1010,1011,1013 "mz:$URL:/wp-login.php|BODY";

# WordPress search
BasicRule wl:1000,1008,1009,1010,1011,1302,1303 "mz:$ARGS_VAR:s";

Test these with your specific setup. Plugins and themes may need more whitelists.

Advanced Features

Libinjection Integration

NAXSI integrates libinjection for advanced SQL injection and XSS detection. The core rules use simple pattern matching. Libinjection uses tokenization, which is harder to evade.

Enable libinjection in your location block:

location / {
    SecRulesEnabled;
    LibInjectionSql;
    LibInjectionXss;

    DeniedUrl "/RequestDenied";

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;
    CheckRule "$LIBINJECTION_SQL >= 8" BLOCK;
    CheckRule "$LIBINJECTION_XSS >= 8" BLOCK;

    proxy_pass http://backend;
}

Libinjection matches use internal rule IDs 17 (SQL) and 18 (XSS). They score against $LIBINJECTION_SQL and $LIBINJECTION_XSS respectively.

IP-Based Bypass

Exempt trusted IP addresses from all NAXSI checks:

location / {
    SecRulesEnabled;

    # Bypass for monitoring and trusted networks
    IgnoreIP "10.0.0.50";
    IgnoreCIDR "192.168.1.0/24";

    DeniedUrl "/RequestDenied";

    CheckRule "$SQL >= 8" BLOCK;
    CheckRule "$XSS >= 8" BLOCK;

    proxy_pass http://backend;
}

Requests from whitelisted IPs skip all NAXSI rule processing.

JSON Logging

For easier log parsing and SIEM integration, enable JSON-formatted logs:

server {
    set $naxsi_json_log 1;

    location / {
        SecRulesEnabled;
        DeniedUrl "/RequestDenied";

        CheckRule "$SQL >= 8" BLOCK;
        CheckRule "$XSS >= 8" BLOCK;

        proxy_pass http://backend;
    }
}

JSON logs work well with jq, Elasticsearch, and Splunk:

{"ip":"192.168.1.50","server":"example.com","uri":"/","config":"block",
"rid":"51f332855b2f19204295b320f961e1bc","cscore0":"$SQL","score0":"12",
"zone0":"ARGS","id0":"1000","var_name0":"id"}

Runtime Variable Overrides

NAXSI supports runtime variables for per-request control. Use NGINX set or map directives:

Variable Purpose
$naxsi_flag_enable Enable/disable NAXSI per-request
$naxsi_flag_learning Enable learning mode per-request
$naxsi_extensive_log Enable verbose logging
$naxsi_json_log Enable JSON log format
$naxsi_flag_libinjection_sql Enable libinjection SQL per-request
$naxsi_flag_libinjection_xss Enable libinjection XSS per-request

For example, enable learning mode only for internal networks:

geo $naxsi_flag_learning {
    default 0;
    192.168.0.0/16 1;   # Learning mode for internal network
    10.0.0.0/8 1;       # Learning mode for VPN
}

server {
    location / {
        SecRulesEnabled;
        DeniedUrl "/RequestDenied";

        CheckRule "$SQL >= 8" BLOCK;
        CheckRule "$XSS >= 8" BLOCK;

        proxy_pass http://backend;
    }
}

NAXSI Variables for Custom Logging

NAXSI exports variables for use in log_format or backend headers:

Variable Description
$naxsi_block 1 if blocked, 0 otherwise
$naxsi_score Current cumulative score
$naxsi_match Matched rule details
$naxsi_attack_family Attack category (SQL, XSS, etc.)
$naxsi_attack_action Action taken (BLOCK, LOG, ALLOW)
$naxsi_request_id Unique 32-character hex identifier
$naxsi_server Server name from request
$naxsi_uri URL-encoded request URI
$naxsi_learning 1 if learning mode, 0 otherwise
$naxsi_total_processed Total requests processed
$naxsi_total_blocked Total requests blocked

Use these in a custom log format for WAF monitoring:

log_format waf_log '$remote_addr - $request '
                   'naxsi_block=$naxsi_block '
                   'naxsi_score=$naxsi_score '
                   'naxsi_attack=$naxsi_attack_family '
                   'naxsi_rid=$naxsi_request_id';

server {
    access_log /var/log/nginx/waf.log waf_log;
    # ...
}

Disabling NAXSI for Specific Locations

Some locations should not use WAF protection. Health checks are a good example. Use SecRulesDisabled:

location /health {
    SecRulesDisabled;
    return 200 "OK";
}

Alternatively, disable via a runtime variable:

location /internal-api {
    set $naxsi_flag_enable 0;
    proxy_pass http://internal-backend;
}

Fail2ban Integration

Combine the NAXSI WAF with Fail2ban to ban persistent attackers. NAXSI blocks individual malicious requests. Fail2ban bans IPs at the firewall level after repeated violations.

Create a Fail2ban filter at /etc/fail2ban/filter.d/naxsi.conf:

[Definition]
failregex = NAXSI_FMT: ip=<HOST>&server=.*&config=block
ignoreregex =

Add a jail in /etc/fail2ban/jail.local:

[naxsi]
enabled = true
port = http,https
filter = naxsi
logpath = /var/log/nginx/error.log
maxretry = 6
findtime = 300
bantime = 3600

This bans any IP that triggers 6 blocks within 5 minutes for 1 hour.

Performance Considerations

NAXSI is designed for minimal performance overhead:

  • No rule updates needed — the core rules rarely change
  • Simple pattern matching — most rules use substring search (str:)
  • Small memory footprint — only ~95 rules loaded into memory
  • Early processing — runs in the rewrite phase
  • No disk I/O — everything happens in memory

NAXSI typically adds less than 1 ms of latency per request. ModSecurity with the full OWASP CRS can add 5–10 ms due to its larger rule set.

For maximum performance:

  • Use str: rules over rx: rules in custom rules
  • Keep whitelist rules specific to reduce zone scanning
  • Use IgnoreIP for monitoring and health check systems

Troubleshooting

NGINX Fails to Start: “Missing DeniedURL, abort”

This error means SecRulesEnabled is set but DeniedUrl is missing. Every location with NAXSI enabled must have DeniedUrl.

Legitimate Requests Are Blocked

Check the NAXSI logs to find which rules triggered:

grep "NAXSI_FMT" /var/log/nginx/error.log | tail -20

Each log line shows the rule ID (id0), zone (zone0), and parameter name (var_name0). Create targeted whitelist rules from this data.

Everything Is Blocked After Enabling NAXSI

Start with learning mode and analyze traffic before setting thresholds. Common starting values:

CheckRule "$SQL >= 8" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;

If too aggressive, increase the values (e.g., $SQL >= 16).

NAXSI Rules Not Applied

Verify these four things:

  1. Module is loaded (load_module modules/ngx_http_naxsi_module.so;)
  2. Core rules included at the http {} level
  3. SecRulesEnabled present in the location block
  4. NGINX reloaded after changes (systemctl reload nginx)

Debugging with Extensive Logging

Enable verbose logging for detailed rule evaluation:

set $naxsi_extensive_log 1;

This produces NAXSI_EXLOG entries showing every rule check, not just threshold-exceeding matches.

Complete Directive Reference

Directive Context Description
MainRule http Define global rules (core rules file)
BasicRule http, server, location Location-specific rules or whitelists
CheckRule http, server, location Score thresholds and actions
DeniedUrl http, server, location Blocked request handler URL
SecRulesEnabled http, server, location Enable NAXSI
SecRulesDisabled http, server, location Disable NAXSI
LearningMode http, server, location Enable log-only mode
LibInjectionSql http, server, location Libinjection SQL detection
LibInjectionXss http, server, location Libinjection XSS detection
IgnoreIP http, server, location Bypass NAXSI for an IP
IgnoreCIDR http, server, location Bypass NAXSI for CIDR range
NaxsiLogFile http, server, location Custom NAXSI log file

All directives support lowercase underscore names too: main_rule, basic_rule, check_rule, denied_url, rules_enabled, rules_disabled, learning_mode, libinjection_sql, libinjection_xss, ignore_ip, ignore_cidr.

Conclusion

The NAXSI WAF provides a fundamentally different approach to web application security. Its whitelist-based architecture catches unknown attacks that signature-based WAFs miss. The learning mode makes initial deployment practical for complex applications. And the minimal performance overhead makes it suitable for high-traffic servers.

For most NGINX deployments on Rocky Linux and RHEL, NAXSI is a strong choice. Combine it with security headers and Fail2ban for defense in depth.

The NAXSI WAF module is maintained and available through the GetPageSpeed repository. The source code is on GitHub.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.