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 equalacceptsunless 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 betweenrequestsandhandled(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 thekeepalive_timeoutdirective.
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)
endAuthentication 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;
}
}
ngx_http_secure_link_module: Securing Download Links #
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.
Secure Link Configuration in Nginx #
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;
}
}
Link Generation from the Backend Application #
In our backend application (e.g., Node.js or Python), we create an expiring link with the following algorithm:
- Calculate the Unix expiration timestamp (e.g., 3 hours from now =
1789000000). - Create the input string:
"OurServerSecretKey/files/document.pdf1789000000". - Calculate the MD5 hash of that input string.
- Convert the binary MD5 hash to URL-safe Base64 format (replace
+with-,/with_, and remove=). - 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 Module | Operation Type | CPU Impact | RAM Impact | Usage Recommendation |
|---|---|---|---|---|
ngx_http_stub_status | Reads internal metrics | Very Low | Very Low | Always enable on a separate internal port for instance monitoring. |
ngx_http_realip | IP address rewriting | Very Low | Very Low | Must be enabled if Nginx is behind a CDN/Load Balancer. |
ngx_http_sub_module | Response string scanning | Medium | Low | Restrict to small text files only to save CPU. |
ngx_http_map_module | Dynamic variable evaluation | Low (Lazy) | Low | Use as often as possible to replace heavy if branching. |
ngx_http_geo_module | Radial IP lookup | Low | Low | Very efficient for mass IP whitelists/blacklists. |
ngx_http_split_clients | MurmurHash3 hashing | Low | Very Low | Ideal for implementing A/B testing and safe canary releases. |
ngx_http_auth_request | Network sub-request | Medium (Network) | Low | Make sure the auth backend responds fast to avoid high latency. |
ngx_http_secure_link | MD5 calculation | Low | Very Low | Use for valuable static files or limiting download link validity. |
Summary and Best Practices #
- Avoid Nested ifs, Use map: The
ifbranching structure inside Nginx location blocks often triggers unexpected behavior (if is evil). Use themapmodule 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/denyor 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_filterbecause it triggers increased worker CPU computational load.