Creating Nginx Modules in C: Real-World Examples for GNU/Linux
Creating Nginx Modules in C: Real-World Examples for GNU/Linux
Ever tried to bend Nginx to your will and ended up banging your head against its config files? What if I told you that you can extend Nginx’s core with blazing-fast C modules that slot right into its event-driven, asynchronous engine? Today, we’re diving deep into creating real-world Nginx modules in C, showing you how to supercharge your server with custom logic without sacrificing performance or stability.
Let’s rip open the hood and see how Nginx modules work, how to write one from scratch, and how to deploy it on a GNU/Linux system. Ready to level up your server game? 🚀
Why Write Nginx Modules in C?
Nginx is a beast for handling massive concurrent connections with minimal resources. Config files and Lua scripts are great for many cases, but sometimes you need raw power and flexibility — that’s where C modules come in.
- Performance: Native C code runs blazingly fast, no JIT or interpreter overhead.
- Full access: Hook into Nginx internals like request processing, upstream handling, and event loops.
- Customization: Implement custom protocols, authentication schemes, or content transformations.
But beware: writing Nginx modules is yak-shaving territory. You’ll wrestle with its internal data structures, lifecycle hooks, and memory pools. Let’s break it down so you don’t shoot yourself in the foot.
Nginx Module Anatomy: The Basics
Think of an Nginx module like a plugin with a few key parts:
- Module Context: Defines callbacks for config parsing, initialization, and request handling.
- Directives: Custom config options your module exposes.
- Handlers: Functions triggered at various phases of request processing (e.g., content phase).
- Module Definition: The core struct that ties everything together.
The MMU of Your Module: Memory Pools
Nginx uses its own memory pool system to avoid malloc/free overhead. Your module gets passed an ngx_pool_t * pointer to allocate short-lived memory tied to request lifetimes. Forget malloc here — use ngx_palloc and friends.
Hello, World! A Minimal Content Handler Module
Let’s start with the classic “Hello, World!” served by Nginx from a custom module.
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
// The handler function called during the content phase
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
// Only respond to GET and HEAD
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// Discard request body, if any
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// Set Content-Type header
ngx_str_t type = ngx_string("text/plain");
r->headers_out.content_type = type;
// Prepare response body
ngx_str_t response = ngx_string("Hello, Nginx Module World!\n");
// Set Content-Length
r->headers_out.content_length_n = response.len;
r->headers_out.status = NGX_HTTP_OK;
// Send headers
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// Allocate buffer for response body
ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// Copy response data to buffer
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1; // This is the last buffer in the response
// Create a chain link for the buffer
ngx_chain_t out;
out.buf = b;
out.next = NULL;
// Send the response body
return ngx_http_output_filter(r, &out);
}
// Hook into the content phase for our custom location
static char *ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_core_loc_conf_t *clcf;
// Get core location conf to set handler
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_hello_handler;
return NGX_CONF_OK;
}
// Define our module directives
static ngx_command_t ngx_http_hello_commands[] = {
{
ngx_string("hello_world"), // Directive name
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, // Location context, no args
ngx_http_hello, // Set handler function
0, 0, NULL
},
ngx_null_command
};
// Module context (no config needed here)
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, // preconfiguration
NULL, // postconfiguration
NULL, // create main config
NULL, // init main config
NULL, // create server config
NULL, // merge server config
NULL, // create location config
NULL // merge location config
};
// 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, NGX_MODULE_V1_PADDING
};
What’s Happening Here?
- We define a directive
hello_worldyou can put inside anylocationblock. - When Nginx hits that location, it runs our
ngx_http_hello_handler. - The handler builds a simple plain-text response using Nginx’s buffer and chain API.
- We carefully set content-type, length, and status headers before sending the body.
Real-World Example: A Simple Auth Module
Say you want to restrict access based on a static token header (like a poor-man’s API key). Here’s a stripped-down example:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_token_auth_handler(ngx_http_request_t *r) {
ngx_table_elt_t *h = NULL;
ngx_list_part_t *part = &r->headers_in.headers.part;
ngx_table_elt_t *header = part->elts;
// Look for "X-Auth-Token" header
for (ngx_uint_t i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (ngx_strcasecmp(header[i].key.data, (u_char *)"X-Auth-Token") == 0) {
h = &header[i];
break;
}
}
if (h == NULL || ngx_strcmp(h->value.data, (u_char *)"SuperSecretToken") != 0) {
return NGX_HTTP_FORBIDDEN;
}
return NGX_DECLINED; // Let other handlers continue
}
static ngx_int_t ngx_http_token_auth_init(ngx_conf_t *cf) {
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
// Insert our handler into the preaccess phase
ngx_http_handler_pt *h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_token_auth_handler;
return NGX_OK;
}
static ngx_http_module_t ngx_http_token_auth_module_ctx = {
NULL,
ngx_http_token_auth_init, // postconfiguration hook
NULL, NULL, NULL, NULL, NULL, NULL
};
ngx_module_t ngx_http_token_auth_module = {
NGX_MODULE_V1,
&ngx_http_token_auth_module_ctx,
NULL,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING
};
How This Works
- We scan request headers for
X-Auth-Token. - If missing or wrong, return 403 Forbidden early in the preaccess phase.
- Otherwise, we decline and let Nginx continue processing.
Building and Loading Your Module
1. Compile Your Module as a Shared Object
Assuming Nginx source is in /usr/local/src/nginx-1.25.0 and your module is ngx_http_hello_module.c:
cd /usr/local/src/nginx-1.25.0
./configure --with-compat --add-dynamic-module=/path/to/your/module
make modules
This produces objs/ngx_http_hello_module.so.
2. Load the Module in Nginx Config
Add this to your nginx.conf top-level:
load_module modules/ngx_http_hello_module.so;
3. Use Your Directive
server {
listen 8080;
location /hello {
hello_world;
}
}
Reload Nginx and hit http://localhost:8080/hello — your C-powered response should light up your terminal.
TL;DR
- Nginx modules in C hook into request phases with custom handlers.
- Use Nginx’s memory pools and buffer chains for efficient response generation.
- Define directives to link config to your code.
- Build with
--add-dynamic-moduleand load withload_module. - Real-world modules can do auth, logging, protocol tweaks, and more — all blazing fast.
Mic Drop 💥
Writing Nginx modules in C is like wielding a double-edged sword: insanely powerful but demands respect. Next time you hit a config wall or need a custom feature at the kernel of your web stack, why not dive in and craft your own module? What’s the wildest thing you’d build inside Nginx? Drop your ideas below — let’s hack the web server beast together! ⚙️🔥