Built-in Modules #

Nginx ships with many built-in modules that are already compiled directly into the standard binary file (static compilation). Most of these modules are active automatically, while some others need to be enabled through explicit compilation configuration flags. Knowing which built-in modules are available is a crucial step for web server administrators; it helps us save release time and server CPU because we don’t need to look for external third-party modules for features Nginx already officially provides.

In this article, we’ll dissect the Nginx built-in modules most often used in advanced production environments. We’ll also put together a module verification scheme and integrate those modules into our web server configuration synergistically.

Verifying Installed Modules on the Server #

Before using a specific module, we must make sure our Nginx binary has compiled that module. We can verify this using the command line with the -V parameter (capital V), which displays all version information and compilation configuration options.

Here are some CLI commands to check the presence of modules on our server:

# 1. Display the entire compilation configuration line
nginx -V 2>&1

# 2. Split the configuration line per module for easier reading
nginx -V 2>&1 | tr ' ' '\n' | grep module

# 3. Search for a specific module (example: realip)
nginx -V 2>&1 | grep -o 'with-[^ ]*realip[^ ]*'

If the commands above show the string --with-http_realip_module or similar modules in our terminal, it means that module is ready to be used in our nginx.conf file.


ngx_http_stub_status: Basic Real-Time Monitoring #

The ngx_http_stub_status module provides instant, real-time Nginx connection statistics metrics. This information is very important to integrate with external monitoring systems (like Prometheus, Datadog, or Zabbix) to observe incoming traffic load.

Secure Status Endpoint Configuration #

Because server status information is sensitive, we must secure this endpoint so it can only be accessed by our internal servers or trusted monitoring systems:

server {
    listen 8080;
    server_name localhost;

    location /nginx_status {
        stub_status;       # Enable stub_status monitoring

        # Restrict access to localhost and internal monitoring IPs only
        allow 127.0.0.1;
        allow 10.0.0.0/8;  # Example of our internal network segment
        deny all;          # Deny all other IPs

        access_log off;    # Turn off access logs so they don't dirty the disk
    }
}

Reading and Analyzing the Status Output #

We can test this endpoint using the curl utility:

curl http://localhost:8080/nginx_status

The response output received will look like this:

Active connections: 291 
server accepts handled requests
 16630948 16630948 31070465 
Reading: 6 Writing: 179 Waiting: 106 

How to read and analyze the metrics above:

  • Active connections: The total number of client connections currently open and active at Nginx (including busy connections sending data and idle connections).
  • accepts: The total number of connections accepted by Nginx since the server process was first started.
  • handled: The total number of successfully handled connections. This value should always equal accepts unless our server ever ran out of resources (drop connections) due to exceeded operating system limits.
  • requests: The total number of HTTP requests served. The ratio between requests and handled (above shows an average of $1.86$ requests per connection) proves the efficiency of our Keepalive protocol.
  • Reading: The number of active connections where Nginx is reading request headers from the client. A too-high number could indicate a Slowloris type attack or clients with very slow internet connections.
  • Writing: The number of active connections where Nginx is processing requests, reading data from upstream, or writing response data back to the client.
  • Waiting: The number of idle keepalive connections (waiting for requests). This value is directly influenced by the duration of the keepalive_timeout directive.

ngx_http_realip: Restoring the Real Client IP #

When our Nginx sits behind a load balancer, a CDN like Cloudflare, or another external reverse proxy, Nginx’s built-in $remote_addr variable by default records the proxy server’s IP address, not the real client computer’s IP address.

The ngx_http_realip module solves this problem by replacing the client IP address value at Nginx’s internal variable level based on trusted HTTP headers inserted by the proxy (like X-Forwarded-For or CF-Connecting-IP).

Integration Configuration with Cloudflare and Internal Proxies #

Here’s how to put together the realip module configuration inside the http block:

http {
    # 1. Register all our trusted proxy/CDN IP ranges
    # Example of official IP ranges owned by Cloudflare
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    # Our internal Load Balancer IP
    set_real_ip_from 10.10.1.5;

    # 2. Determine the HTTP header carrying the real IP
    real_ip_header CF-Connecting-IP; # Use X-Forwarded-For for standard proxies

    # 3. Enable recursive lookup
    real_ip_recursive on;
}

Why Is real_ip_recursive Very Important? #

If real_ip_recursive is set to off (default), Nginx only trusts the outermost proxy IP directly connected to it. However, if we turn on real_ip_recursive on, Nginx scans the IP list inside the X-Forwarded-For header from right to left, ignoring IPs matching the set_real_ip_from list, and takes the first IP that isn’t part of the trusted proxies as the real client IP. This secures our server from header manipulation (IP spoofing) by outside attackers.


ngx_http_sub_module: On-the-fly HTML Response Modification #

The ngx_http_sub_module is a response filter capable of finding and replacing certain string fragments inside the HTML document body sent by our backend application before the data reaches the client browser.

Use Case: Analytics Script Injection and Domain URL Updates #

Suppose we want to migrate a domain from an old server to a new server without having to change thousands of hardcoded code lines in the backend application database:

server {
    listen 80;
    server_name www.newdomain.com;

    location / {
        proxy_pass http://app_backend;

        # Replace all old domain references with the new domain dynamically
        sub_filter 'http://olddomain.com' 'https://www.newdomain.com';

        # Replace all occurrences of the string, not just the first one found
        sub_filter_once off;

        # Allow response modification for JSON and HTML
        sub_filter_types text/html text/css application/json;

        # Preserve the Last-Modified header from the backend (if needed)
        sub_filter_last_modified on;
    }
}

Minimizing CPU Load from sub_filter #

This filter module processes response data streams in memory. If the file being sent is very large and sub_filter_once is turned off (off), our server CPU load will increase. The best practice is to limit sub_filter_types application to only lightweight text-type files and make sure Nginx’s internal buffers are loose enough.


ngx_http_map_module: Efficient Dynamic Variable Mapping #

The ngx_http_map_module creates a new variable in Nginx whose value is automatically adjusted based on the value of another variable. This module is one of the most versatile modules for simplifying branching logic in Nginx configuration.

Example of Device Type and Maintenance Mode Mapping #

We declare the map block inside the http context (outside the server block):

http {
    # 1. Detect device type based on the User-Agent string
    map $http_user_agent $device_type {
        default         "desktop";
        ~*mobile        "mobile";
        ~*tablet        "tablet";
        ~*android       "mobile";
    }

    # 2. Maintenance mode control based on URI segments
    map $uri $maintenance_mode {
        default             0;
        ~^/api/v[1-2]/      0; # Main API stays open
        ~^/admin/           1; # Block admin panel access
        ~^/dashboard/       1; # Block user dashboards
    }

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

        location / {
            # Apply the maintenance map result
            if ($maintenance_mode = 1) {
                return 503; # Return Service Unavailable
            }

            # Forward the device type to the backend application via a custom header
            proxy_set_header X-Device-Type $device_type;
            proxy_pass http://app_backend;
        }

        # Custom maintenance page
        error_page 503 /maintenance.html;
        location = /maintenance.html {
            root /var/www/error_pages;
            internal;
        }
    }
}

Main Advantage: Lazy Evaluation #

The most special thing about Nginx’s map module is that its evaluation process is done lazily (deferred). Nginx doesn’t calculate the new variable’s value at the start of the transaction. Regex pattern calculations or text comparisons are only done by the CPU if the custom variable is actually called in the configuration of the request being processed.


ngx_http_geo_module: IP Address-Based Grouping #

The ngx_http_geo_module creates a custom variable based on the client IP address. This is very useful for determining special security logic for office networks or separating bot traffic.

IP Whitelist Classification Configuration #

http {
    # Classify client IPs into access status categories
    geo $ip_access_level {
        default         "public";
        127.0.0.1       "admin";
        192.168.1.0/24  "internal"; # Our office LAN network
        10.0.0.0/8      "internal"; # Cloud VPC network
        198.51.100.44   "trusted_partner";
    }

    server {
        listen 80;

        location /secure-endpoint/ {
            # Only admins and internal IPs are allowed in
            if ($ip_access_level = "public") {
                return 403; # Forbidden for the public
            }

            proxy_pass http://backend;
        }
    }
}

ngx_http_split_clients: Percentage Traffic Distribution (A/B Testing) #

The ngx_http_split_clients module divides client requests into several variable groups using the MurmurHash3 hashing algorithm. This method guarantees very deterministic traffic distribution based on a custom key (like IP addresses or cookie values).

Setting Up New Feature Testing (Canary Release) #

http {
    # Split client traffic: 15% to the beta v2 release, 85% to the stable v1 release
    # We use the IP and User-Agent as the hash key for consistency
    split_clients "${remote_addr}${http_user_agent}" $app_upstream {
        15.0%   "backend_beta";
        *       "backend_stable";
    }

    upstream backend_stable {
        server 10.0.2.1:8080;
    }

    upstream backend_beta {
        server 10.0.2.2:8080;
    }

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

        location / {
            proxy_pass http://$app_upstream;
            add_header X-Route-Group $app_upstream always;
        }
    }
}

ngx_http_auth_request_module: Authorization Delegation #

The ngx_http_auth_request_module delegates the client authentication and authorization process to an external authorization server before Nginx forwards the request to the main backend application. If the authorization sub-request returns an HTTP 2xx status code, Nginx continues the process. If the authorization returns HTTP 401 or 403, Nginx immediately cuts the connection and sends that error code to the client.

Here’s a sequence diagram of this module’s workflow at the network level:

sequenceDiagram
    autonumber
    actor Client as Client Browser
    participant Nginx as Nginx Web Server
    participant Auth as Auth Service (External)
    participant App as Backend Application

    Client->>Nginx: GET /protected/resource
    Note over Nginx: Detects auth_request /auth
    Nginx->>Auth: GET /auth (Internal Sub-request)
    Note over Auth: Validate Session / JWT Cookie
    alt Token Valid (HTTP 200 OK)
        Auth-->>Nginx: HTTP 200 OK + Header (X-User)
        Note over Nginx: Set variables from the response header
        Nginx->>App: GET /protected/resource + X-Auth-User
        App-->>Nginx: HTTP 200 OK (Content)
        Nginx-->>Client: HTTP 200 OK (Content)
    else Token Invalid / Expired (HTTP 401/403)
        Auth-->>Nginx: HTTP 401 Unauthorized
        Nginx-->>Client: HTTP 401 Unauthorized (Redirect to Login)
    end

Authentication Delegation Configuration with OAuth2 Proxy #

server {
    listen 443 ssl;
    server_name portal.unisbadri.com;

    location /private/ {
        # Step 1: Send an internal sub-request to the /auth location
        auth_request /auth;

        # Step 2: If auth succeeds, take the user header from the auth service
        # and send it to our main backend
        auth_request_set $user $upstream_http_x_auth_user;
        proxy_set_header X-Authenticated-User $user;

        proxy_pass http://private_backend;
    }

    # The authorization sub-request handler endpoint
    location = /auth {
        internal; # Prevents direct access from the outside internet

        proxy_pass http://auth_service.internal/validate-token;
        proxy_pass_request_body off;          # Save bandwidth: don't send the client request body
        proxy_set_header Content-Length "";  # Set empty so the auth service doesn't wait for input
        proxy_set_header X-Original-URI $request_uri;
    }

    # Handling when authorization fails
    error_page 401 = @redirect_to_login;
    location @redirect_to_login {
        return 302 https://sso.unisbadri.com/login?rd=$scheme://$host$request_uri;
    }
}

The ngx_http_secure_link_module is used to protect download files on our server so they can’t be downloaded carelessly by parties without access rights (hotlinking prevention), as well as to limit the link’s expiry time.

This module verifies the match between the digital signature parameter (MD5 hash) sent by the client in the URL and the MD5 hash dynamically calculated by our Nginx server using a shared secret and expiration time.

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

    location /files/ {
        # URL pattern: /files/filename?md5=hash-string&expires=unix-timestamp
        secure_link $arg_md5,$arg_expires;

        # Calculate the comparison MD5 hash from internal variables:
        # shared_secret + file URI + Unix expiration timestamp
        secure_link_md5 "OurServerSecretKey$uri$arg_expires";

        # Verify the hash match
        if ($secure_link = "") {
            # Hash doesn't match at all (Unauthorized)
            return 403;
        }

        if ($secure_link = "0") {
            # Hash matches but the expiration time has passed
            return 410; # Gone
        }

        # If verification passes, serve the download file
        root /var/www/downloads;
    }
}

In our backend application (e.g., Node.js or Python), we create an expiring link with the following algorithm:

  1. Calculate the Unix expiration timestamp (e.g., 3 hours from now = 1789000000).
  2. Create the input string: "OurServerSecretKey/files/document.pdf1789000000".
  3. Calculate the MD5 hash of that input string.
  4. Convert the binary MD5 hash to URL-safe Base64 format (replace + with -, / with _, and remove =).
  5. Form the final link: https://downloads.unisbadri.com/files/document.pdf?md5=HashResult&expires=1789000000.

ngx_http_addition_module: Automatic Content Combining #

The ngx_http_addition_module inserts the output of another internal URI before or after the main response body content. This module is very useful for centrally inserting navigation headers, promotional ads, or copyright footers on static pages without touching the individual HTML file code.

Header/Footer Addition Configuration #

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

    location /articles/ {
        root /var/www/blog;

        # 1. Insert a header file before the HTML file body is served
        add_before_body /includes/header.html;

        # 2. Insert a footer file after the HTML file body is done being served
        add_after_body /includes/footer.html;

        # Restrict to text/html types only
        addition_types text/html;
    }

    # Mark the includes location as internal so clients can't access it directly
    location /includes/ {
        root /var/www/blog;
        internal;
    }
}

Module Comparison and Overhead Impact Analysis #

Each module has its own impact on the CPU and RAM memory performance of our Nginx server. Here’s a summary table of the impact and characteristics of the built-in modules:

Nginx ModuleOperation TypeCPU ImpactRAM ImpactUsage Recommendation
ngx_http_stub_statusReads internal metricsVery LowVery LowAlways enable on a separate internal port for instance monitoring.
ngx_http_realipIP address rewritingVery LowVery LowMust be enabled if Nginx is behind a CDN/Load Balancer.
ngx_http_sub_moduleResponse string scanningMediumLowRestrict to small text files only to save CPU.
ngx_http_map_moduleDynamic variable evaluationLow (Lazy)LowUse as often as possible to replace heavy if branching.
ngx_http_geo_moduleRadial IP lookupLowLowVery efficient for mass IP whitelists/blacklists.
ngx_http_split_clientsMurmurHash3 hashingLowVery LowIdeal for implementing A/B testing and safe canary releases.
ngx_http_auth_requestNetwork sub-requestMedium (Network)LowMake sure the auth backend responds fast to avoid high latency.
ngx_http_secure_linkMD5 calculationLowVery LowUse for valuable static files or limiting download link validity.

Summary and Best Practices #

  • Avoid Nested ifs, Use map: The if branching structure inside Nginx location blocks often triggers unexpected behavior (if is evil). Use the map module to make clean and safe variable decisions.
  • Secure stub_status: Never leave the status monitoring endpoint open to the public. Restrict it with an IP whitelist allow/deny or use an internal port.
  • Enable real_ip_recursive: Always turn on the recursive feature on the realip module if our infrastructure uses layered CDNs to block IP header manipulation holes from outside attackers.
  • Use sub_filter Selectively: Avoid processing binary documents or mega-byte HTML using sub_filter because it triggers increased worker CPU computational load.

← Previous: Open File Cache   Next: Lua & OpenResty →

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