Build Your First Custom Nginx Module from Scratch – A Step‑by‑Step Guide
This article walks you through why learning Nginx module development matters, explains the core architecture and lifecycle, shows how to set up the build environment, create a Hello World module with full source code, compile, install, configure, and extend it with advanced features and performance optimizations.
Why Learn Nginx Module Development?
Operations engineers often need custom request handling, custom metrics, third‑party API integration, or complex load‑balancing that existing modules cannot provide, making custom module development the ideal solution.
Nginx Module Architecture Deep Dive
Core Architecture Overview
Nginx uses a modular design with several module types:
├── Core Modules (Core Modules)
│ ├── ngx_core_module
│ ├── ngx_errlog_module
│ └── ngx_conf_module
├── Event Modules (Event Modules)
│ ├── ngx_events_module
│ └── ngx_epoll_module
├── HTTP Modules (HTTP Modules)
│ ├── ngx_http_core_module
│ ├── ngx_http_access_module
│ └── Custom HTTP Module
└── Other Modules (Other Modules)Module Lifecycle
Configuration Parsing Phase : Parse directives in nginx.conf Initialization Phase : Allocate memory and initialize the module
Request Processing Phase : Handle specific HTTP requests
Cleanup Phase : Release resources and clean up memory
Development Environment Setup
Preparation
# 1. Download Nginx source
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 2. Install required build tools
yum install -y gcc gcc-c++ pcre-devel zlib-devel openssl-devel
# 3. Create module directory
mkdir -p /opt/nginx-modules/ngx_hello_moduleDirectory Structure
ngx_hello_module/
├── config # module configuration file
├── ngx_http_hello_module.c # main source file
└── README.md # documentationFirst Nginx Module: Hello World
We start with a classic Hello World example to create a simple yet functional Nginx module.
1. Create module configuration file (config)
# config file content
ngx_addon_name=ngx_http_hello_module
if test -n "$ngx_module_link"; then
ngx_module_type=HTTP
ngx_module_name=ngx_http_hello_module
ngx_module_srcs="$ngx_addon_dir/ngx_http_hello_module.c"
. auto/module
else
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"
fi2. Core source file
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
// Module context structure
typedef struct {
ngx_str_t hello_string;
ngx_flag_t hello_counter;
} ngx_http_hello_loc_conf_t;
// Function declarations
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r);
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
// Module directives
static ngx_command_t ngx_http_hello_commands[] = {
{ ngx_string("hello_string"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_hello_string, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_hello_loc_conf_t, hello_string), NULL },
{ ngx_string("hello_counter"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_http_hello_counter, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_hello_loc_conf_t, hello_counter), NULL },
ngx_null_command
};
// Module context
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, NULL, // preconfiguration, postconfiguration
NULL, NULL, // create main conf, init main conf
NULL, NULL, // create server conf, merge server conf
ngx_http_hello_create_loc_conf, // create location conf
ngx_http_hello_merge_loc_conf // merge location conf
};
// Module definition
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx, // module context
ngx_http_hello_commands, // module directives
NGX_HTTP_MODULE, // module type
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
// Request handler
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t *alcf;
u_char *hello_string;
alcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
r->headers_out.content_type_len = sizeof("text/plain") - 1;
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char *)"text/plain";
if (r->method == NGX_HTTP_HEAD) {
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = alcf->hello_string.len;
return ngx_http_send_header(r);
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
out.buf = b;
out.next = NULL;
hello_string = ngx_pnalloc(r->pool, alcf->hello_string.len);
if (hello_string == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(hello_string, alcf->hello_string.data, alcf->hello_string.len);
b->pos = hello_string;
b->last = hello_string + alcf->hello_string.len;
b->memory = 1;
b->last_buf = 1;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = alcf->hello_string.len;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
return ngx_http_output_filter(r, &out);
}
// Create location configuration
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf) {
ngx_http_hello_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->hello_counter = NGX_CONF_UNSET;
return conf;
}
// Merge location configuration
static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
ngx_http_hello_loc_conf_t *prev = parent;
ngx_http_hello_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "Hello, Nginx Module!");
ngx_conf_merge_value(conf->hello_counter, prev->hello_counter, 0);
return NGX_CONF_OK;
}
// hello_string directive handler
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_hello_loc_conf_t *alcf = conf;
ngx_str_t *value = cf->args->elts;
alcf->hello_string = value[1];
ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_hello_handler;
return NGX_CONF_OK;
}
// hello_counter directive handler
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_hello_loc_conf_t *alcf = conf;
ngx_str_t *value = cf->args->elts;
if (ngx_strcasecmp(value[1].data, (u_char *)"on") == 0) {
alcf->hello_counter = 1;
} else if (ngx_strcasecmp(value[1].data, (u_char *)"off") == 0) {
alcf->hello_counter = 0;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid value \"%s\" in \"%s\" directive, it must be \"on\" or \"off\"",
value[1].data, cmd->name.data);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}Compile and Install
Compilation Steps
# 1. Enter Nginx source directory
cd /path/to/nginx-1.24.0
# 2. Configure build parameters
./configure --prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--add-module=/opt/nginx-modules/ngx_hello_module
# 3. Build and install
make && make installConfiguration Usage
Add the following to nginx.conf:
server {
listen 80;
server_name localhost;
location /hello {
hello_string "Hello from custom Nginx module!";
hello_counter on;
}
location /api/hello {
hello_string "API Response: Custom module working!";
}
}Advanced Techniques: Practical Extensions
1. Add Request Counting
// Global counter
static ngx_atomic_t request_counter = 0;
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
ngx_http_hello_loc_conf_t *alcf;
ngx_atomic_int_t current_count;
alcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (alcf->hello_counter) {
current_count = ngx_atomic_fetch_add(&request_counter, 1);
ngx_table_elt_t *h = ngx_list_push(&r->headers_out.headers);
if (h != NULL) {
h->hash = 1;
ngx_str_set(&h->key, "X-Request-Count");
h->value.len = ngx_sprintf(h->value.data, "%uA", current_count + 1) - h->value.data;
}
}
// ... rest of handler logic
return NGX_OK;
}2. Integrate External API Calls
// Asynchronous subrequest example
static ngx_int_t ngx_http_hello_subrequest_handler(ngx_http_request_t *r) {
ngx_http_request_t *sr;
ngx_str_t uri = ngx_string("/internal/api");
ngx_str_t args = ngx_string("param=value");
ngx_int_t rc = ngx_http_subrequest(r, &uri, &args, &sr, NULL, 0);
if (rc != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_DONE;
}Performance Optimization Best Practices
1. Memory Management Optimization
// Use memory pool for efficient allocation
static ngx_int_t optimized_memory_handler(ngx_http_request_t *r) {
ngx_pool_t *pool = ngx_create_pool(4096, r->connection->log);
if (pool == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
void *data = ngx_pnalloc(pool, 1024);
// ... use data
ngx_destroy_pool(pool);
return NGX_OK;
}2. Cache Mechanism Implementation
// Simple LRU cache skeleton
typedef struct {
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
ngx_queue_t lru_queue;
size_t max_size;
size_t current_size;
} ngx_http_hello_cache_t;
static ngx_http_hello_cache_t *cache;
static ngx_str_t *ngx_http_hello_cache_get(ngx_str_t *key) {
uint32_t hash = ngx_crc32_short(key->data, key->len);
ngx_rbtree_node_t *node = cache->rbtree.root;
ngx_rbtree_node_t *sentinel = cache->rbtree.sentinel;
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
} else if (hash > node->key) {
node = node->right;
} else {
// cache hit – move to front of LRU queue
return &((ngx_http_hello_cache_node_t *)node)->value;
}
}
return NULL;
}Debugging and Testing
1. Debugging Tips
// Detailed logging macro
#define ngx_http_hello_log_error(level, r, fmt, ...) \
ngx_log_error(level, (r)->connection->log, 0, "hello module: " fmt, ##__VA_ARGS__)
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
ngx_http_hello_log_error(NGX_LOG_DEBUG, r, "processing request for URI: %V", &r->uri);
// ... handler logic
ngx_http_hello_log_error(NGX_LOG_INFO, r, "request processed successfully");
return NGX_OK;
}2. Unit Test Script
#!/bin/bash
# Basic functionality test
curl -i http://localhost/hello
echo "Expected: Custom hello message"
# Counter test
for i in {1..5}; do
curl -s -I http://localhost/hello | grep "X-Request-Count"
done
# Performance test
ab -n 1000 -c 10 http://localhost/helloMonitoring and Maintenance
Add Metrics Collection
// Custom metrics structure
typedef struct {
ngx_atomic_t requests_total;
ngx_atomic_t requests_success;
ngx_atomic_t requests_error;
ngx_atomic_t response_time_total;
} ngx_http_hello_metrics_t;
static ngx_http_hello_metrics_t *metrics;
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
ngx_time_t *tp = ngx_timeofday();
ngx_msec_int_t start = (ngx_msec_int_t)(tp->sec * 1000 + tp->msec);
ngx_atomic_fetch_add(&metrics->requests_total, 1);
// ... request handling
tp = ngx_timeofday();
ngx_msec_int_t elapsed = (ngx_msec_int_t)(tp->sec * 1000 + tp->msec) - start;
ngx_atomic_fetch_add(&metrics->response_time_total, elapsed);
ngx_atomic_fetch_add(&metrics->requests_success, 1);
return NGX_OK;
}Conclusion
By following this guide you have mastered the core skills of Nginx module architecture, the complete development workflow, configuration directive definition and handling, request processing logic, as well as advanced capabilities such as performance tuning, memory management, caching, monitoring, and debugging.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
