Third-Party Modules #

Although Nginx comes with many powerful built-in modules, Nginx’s true flexibility shines through the third-party modules ecosystem. Developed by the global developer community and well-known tech companies, these modules allow us to insert cutting-edge features — like advanced compression algorithms, web application firewalls (WAF), detailed per-virtual-host traffic monitoring, and automatic image optimization.

In this article, we’ll discuss the most popular third-party modules that provide real value in production environments. We’ll also examine a self-compilation guide from source code using compatibility flags so modules can be loaded dynamically without obstacles.

ngx_brotli: Google’s Brotli Compression #

Brotli is a modern lossless data compression algorithm developed by Google. Compared to traditional Gzip, Brotli can produce HTML, CSS, and JavaScript file sizes about 15% to 25% smaller, which directly speeds up web page rendering time in users’ browsers and saves our server bandwidth.

All modern internet browsers currently support Brotli natively. In Nginx, we integrate the ngx_brotli module to enable this compression.

Configuring Brotli Alongside Gzip #

We can configure Brotli to run alongside Gzip. Client browsers supporting Brotli will automatically choose Brotli (via the Accept-Encoding: br header), while older browsers will be served using Gzip.

# Load the dynamic Brotli module in the main context
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

http {
    # 1. Enable Dynamic Brotli Compression
    brotli on;
    brotli_comp_level 6; # Sweet spot: compression level 6 (CPU-efficient) vs level 11 (CPU-bound)
    
    # 2. Enable Static Brotli Serving (.br pre-compressed files)
    brotli_static on;    # If there's an index.html.br file on disk, serve it directly without on-the-fly compression

    # 3. Determine the text file types that need compressing
    brotli_types
        text/html
        text/css
        text/plain
        text/xml
        application/javascript
        application/json
        image/svg+xml
        application/xml+rss;

    # 4. The Gzip fallback stays on for backward compatibility
    gzip on;
    gzip_comp_level 6;
    gzip_types text/html text/css application/javascript application/json;
}

headers-more-nginx-module: Advanced Header Manipulation #

Nginx’s built-in module only provides the add_header directive, which has several important limitations:

  • Can’t remove response headers generated by backend servers (like PHP-FPM, Tomcat, or Node.js).
  • Adds a new header with the same name instead of overwriting the existing one, triggering duplicate headers in client browsers.
  • The inheritance rules in nested location blocks often cancel out headers already declared at the global http level.

The headers-more-nginx-module solves all these problems by providing full control over both request and response headers.

Securing Server Information with headers-more #

We can use this module to hide our backend technology (reconnaissance hardening) and arrange CORS rules more neatly:

# Load the headers-more module
load_module modules/ngx_http_headers_more_filter_module.so;

http {
    # Change the server identity globally (default: "Server: nginx/1.x.x")
    more_set_headers "Server: Enterprise-Web-Server";

    server {
        listen 80;
        server_name app.unisbadri.com;

        location / {
            # Remove backend headers leaking framework/programming language details
            more_clear_headers "X-Powered-By";
            more_clear_headers "X-AspNet-Version";
            more_clear_headers "X-Runtime";

            proxy_pass http://node_backend;
        }

        location /api/ {
            # Replace or set response headers deterministically
            more_set_headers "Access-Control-Allow-Origin: *";
            more_set_headers "Access-Control-Allow-Methods: GET, POST, OPTIONS";
            more_set_headers -t "application/json" "Cache-Control: no-store, must-revalidate";
            
            proxy_pass http://node_backend;
        }
    }
}

nginx-module-vts: Per-Virtual-Host Traffic Monitoring #

The built-in stub_status module only serves Nginx server global connection statistics. In multi-tenant server environments or shared hosting, we need separate statistics per domain (virtual host).

The nginx-module-vts (Virtual Host Traffic Status) module provides detailed metrics per server name, per upstream backend, and per cache zone. This module also provides an interactive HTML monitoring dashboard and native JSON output format readable by Prometheus for visualization in Grafana.

VTS Dashboard Configuration #

# Load the VTS module
load_module modules/ngx_http_vhost_traffic_status_module.so;

http {
    # Enable global VTS traffic data collection
    vhost_traffic_status_zone;
    
    # Limit maximum monitoring memory to 10MB
    vhost_traffic_status_dump_file /var/log/nginx/vts.db;

    server {
        listen 8080;
        server_name localhost;

        # HTML visual dashboard endpoint
        location /status {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format html;
            
            allow 127.0.0.1;
            allow 10.0.0.0/8;
            deny all;
        }

        # Prometheus endpoint to be scraped by the Prometheus server
        location /metrics {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format prometheus;
            
            allow 127.0.0.1;
            allow 10.0.0.0/8;
            deny all;
        }
    }
}

ModSecurity: Web Application Firewall (WAF) #

ModSecurity is the industry-standard open-source Web Application Firewall engine. ModSecurity version 3 (libmodsecurity) can be integrated into Nginx to filter incoming request traffic against common security exploit threats — like SQL Injection attacks, Cross-Site Scripting (XSS), arbitrary file reading (path traversal), and Session Hijacking attacks.

We combine ModSecurity with the OWASP Core Rule Set (CRS) rules library for high-level protection right at the Nginx web server entry gate.

ModSecurity WAF Configuration in Nginx #

# Load the ModSecurity Nginx connector module
load_module modules/ngx_http_modsecurity_module.so;

http {
    # Enable ModSecurity globally
    modsecurity on;
    
    # Point to the main rules configuration file
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

    server {
        listen 80;
        server_name portal.unisbadri.com;

        location / {
            proxy_pass http://portal_backend;
        }
    }
}

CRS Rules Configuration /etc/nginx/modsec/main.conf #

# Load basic ModSecurity settings
Include /etc/nginx/modsec/modsecurity.conf

# Install the OWASP Core Rule Set
Include /etc/nginx/modsec/coreruleset/crs-setup.conf
Include /etc/nginx/modsec/coreruleset/rules/*.conf

Migration Strategy: DetectionOnly vs Enforcement #

Turning on the WAF directly in blocking mode (SecRuleEngine On) carries a high risk of blocking legitimate client traffic due to false positives. The release recommendation is:

  1. Start by setting SecRuleEngine DetectionOnly in the modsecurity.conf file. In this mode, suspicious requests are only recorded in /var/log/nginx/modsec_audit.log without blocking client connections.
  2. Analyze the audit log for a few weeks to recognize false positive anomalies.
  3. Write rule exclusions to legalize legitimate requests.
  4. Change the setting to SecRuleEngine On to start actively blocking attacks.

1. Analyzing the ModSecurity Audit Log #

When a client request is blocked or detected as suspicious, ModSecurity records a detailed log entry. Critical lines in the log usually contain information like:

[file "/usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "37"] [id "941100"] [rev "2"] [msg "Libinjection-XSS detection"] [data "Matched Data: <script> found within ARGS:search"] [severity "CRITICAL"]

From the log entry above, we can extract the following important information:

  • id "941100": The specific rule ID that detected the request.
  • msg "Libinjection-XSS detection": The description of the detected attack type.
  • data "Matched Data: ...": The specific client parameter or payload that triggered the detection.

2. Writing Rule Exclusions #

If the search data on our application form is legitimate (e.g., an admin entering HTML syntax in an editor dashboard) but is detected as an XSS attack by rule 941100, we must not turn off the entire WAF. Instead, we write a focused exclusion rule:

# In /etc/nginx/modsec/main.conf (before CRS rules are loaded)
# Turn off rule 941100 specifically for the 'search' parameter on the '/admin/editor/' URL
SecRuleUpdateTargetById 941100 "!ARGS:search"

Or if we want to disable a certain rule for all our trusted APIs:

# Disable the SQLi detection rule (942100) and XSS (941100) for /api/trusted/
SecRule REQUEST_URI "@beginsWith /api/trusted/" \
    "id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=941100,ctl:ruleRemoveById=942100"

With the tactic above, our server stays protected from cyber attacks without breaking the normal functionality of our web application.


ngx_http_geoip2_module: Integrated Geolocation #

The ngx_http_geoip2_module matches client IP addresses against the MaxMind GeoIP2 geolocation database (.mmdb format) to detect the client’s country of origin, city, ISP name, or geographic coordinates.

We can use this geolocation data to automatically switch website languages, do regional routing to the nearest upstream, or block traffic from countries with high cyber attack rates.

Country Access Restriction Configuration #

# Load the GeoIP2 module
load_module modules/ngx_http_geoip2_module.so;

http {
    # Register the MaxMind Country MMDB database
    geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
        auto_reload 5m; # Check the database file every 5 minutes for automatic updates
        $geoip2_country_code country iso_code;
    }

    # Block access from certain countries (example: CN and RU)
    map $geoip2_country_code $access_allowed {
        default     1; # Allow all countries
        CN          0; # Block China
        RU          0; # Block Russia
    }

    server {
        listen 443 ssl;
        server_name core-api.unisbadri.com;

        location / {
            if ($access_allowed = 0) {
                return 403; # Return Forbidden
            }

            # Send geolocation info to the main backend via an HTTP header
            proxy_set_header X-Client-Country $geoip2_country_code;
            proxy_pass http://api_backend;
        }
    }
}

Guide to Compiling Third-Party Modules Yourself (Dynamic Module) #

If our Linux distribution doesn’t provide ready-made binary packages for the third-party module we want, we must compile the module ourselves from source code.

The key to successfully compiling a module without needing to overwrite our entire running Nginx system is using the --with-compat flag during configuration. This ensures the compiled .so shared object file is binary-compatible with our running Nginx.

Here’s a flow diagram of the third-party module compilation steps:

flowchart TD
    CheckVersion["1. Check the Actual Nginx Version<br/>(nginx -v)"] --> GetSource["2. Download the Nginx Source of That Version<br/>(wget nginx.org/download/...)"]
    GetSource --> GetModule["3. Clone the Third-Party Module Source<br/>(git clone & update submodules)"]
    GetModule --> Configure["4. Configure Compilation Options<br/>(./configure --with-compat --add-dynamic-module=...)"]
    Configure --> Compile["5. Compile the Shared Object Module<br/>(make modules)"]
    Compile --> Deploy["6. Copy the .so File to Nginx Modules<br/>(cp objs/*.so /etc/nginx/modules/)"]
    Deploy --> LoadConfig["7. Load the Module in nginx.conf<br/>(load_module in the main context)"]

    classDef default fill:#f9f9f9,stroke:#d1d5db,stroke-width:1px,color:#111827;
    classDef steps fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a;
    class CheckVersion,GetSource,GetModule,Configure,Compile,Deploy,LoadConfig steps;

Executing the ngx_brotli Module Compilation Step-by-Step #

Run the following steps in our server test environment:

# Step 1: Note our running Nginx version
nginx -v
# Example output: nginx version: nginx/1.24.0

# Step 2: Install the compiler development tools
sudo apt install build-essential git libpcre3-dev zlib1g-dev libssl-dev -y

# Step 3: Download the Nginx source code with the exact same version
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz

# Step 4: Clone the ngx_brotli module source repository
git clone https://github.com/google/ngx_brotli.git
cd ngx_brotli
git submodule update --init --recursive
cd ..

# Step 5: Go into the Nginx source folder and run the compatibility configuration
cd nginx-1.24.0
./configure --with-compat --add-dynamic-module=../ngx_brotli

# Step 6: Run the module-only compilation (no need to install all of Nginx)
make modules

# Step 7: Copy the .so files to our Nginx module working directory
sudo cp objs/ngx_http_brotli_filter_module.so /etc/nginx/modules/
sudo cp objs/ngx_http_brotli_static_module.so /etc/nginx/modules/

After the .so files are copied, we just add the load_module directive at the top lines of our nginx.conf file to dynamically enable the module.


[NEW] ngx_pagespeed: Automatic Front-End Asset Optimization #

The ngx_pagespeed module was developed by Google to automate front-end asset optimization best practices directly on the server side. Some of the automatic optimizations run by this module include:

  • Compressing and converting regular image formats (JPEG/PNG) to WebP dynamically.
  • Concatenating and minifying CSS and JavaScript file sizes.
  • Inlining critical CSS to avoid render-blocking resources.

When Should You Use ngx_pagespeed? #

This module is very beneficial for traditional CMS-based websites (like WordPress, Drupal, or old PHP servers) whose assets aren’t well optimized at the development level.

However, if we use a modern frontend framework (like React, Vue, Next.js, or Vite) whose build flow already optimizes and compresses assets to the maximum before deploying, using ngx_pagespeed becomes less recommended because this module consumes very large server CPU and RAM resources for dynamic calculations.


Summary and Best Practices #

  • Use –with-compat for Portability: Never skip the --with-compat flag when compiling standalone modules. Without this flag, our module is guaranteed to be rejected by the running Nginx due to memory data structure inconsistency issues.
  • Test the WAF Gradually: Don’t directly turn on active blocking on the ModSecurity WAF. Run DetectionOnly mode first to filter legitimate client request anomalies so our site doesn’t experience functional downtime.
  • Update the GeoIP2 Database Periodically: IP address data in the world always changes dynamically. Create a monthly cron job to download the latest .mmdb database from MaxMind so our location filtering stays accurate.
  • Prioritize Brotli for Text Assets: Enable Brotli on static asset servers. The 20% payload size savings compared to gzip greatly impacts our website’s Core Web Vitals (LCP) optimization score.

← Previous: Lua & OpenResty   Next: Dynamic Module →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact