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.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Build Your First Custom Nginx Module from Scratch – A Step‑by‑Step Guide

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_module

Directory Structure

ngx_hello_module/
├── config              # module configuration file
├── ngx_http_hello_module.c  # main source file
└── README.md           # documentation

First 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"
fi

2. 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 install

Configuration 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/hello

Monitoring 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

C programmingmodule developmentcustom module
MaGe Linux Operations
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.