Yuvraj Singh Chauhan
Back to blog

What I Learned Reading nginx.c

August 7, 20263 min read
SWE: C & Systems

I've been working on a simple HTTP server in C - the kind that serves static files from a dist/ folder. It works, but it's naive. I wanted to see how the pros do it, so I started reading the nginx source code, where I began with src/core/nginx.c.


The Sentinel Pattern

Nginx relies heavily on static arrays for configuration commands, module definitions, and enum values. None of these structs carry an explicit .length field. Instead, Nginx relies on sentinel values to mark the end of arrays.

Here is a real example from nginx.c. The ngx_debug_points array maps config-file strings to enum values:

static ngx_conf_enum_t  ngx_debug_points[] = {
    { ngx_string("stop"),  NGX_DEBUG_POINTS_STOP  },
    { ngx_string("abort"), NGX_DEBUG_POINTS_ABORT },
    { ngx_null_string, 0 }   // <- the sentinel
};

Why I Liked It

In C, arrays don't carry their length at runtime. You usually have to, pass a separate length variable everywhere which is easy to get out of sync.

The sentinel is a contract: "if the name is empty/zero, you've reached the end." It's simple, it's hard to mess up, and it avoids a whole class of off-by-one bugs.

It's a small change but it makes the array self-contained.


Nginx's Memory Pool (ngx_pool_t)

This was the bigger revelation. My server uses malloc() and free() like a typical C program:

char *content = read_file(path, &size);
// ... send response ...
free(content);  // <- what if I forget this? what if I return early?

nginx doesn't do this. Almost nowhere in the codebase will you find a raw free(). Instead, nginx uses memory pools (ngx_pool_t).

How the pool works

The model is clean and efficient:

  1. Create a pool with ngx_create_pool(size, log).
  2. Allocate from it using ngx_palloc(pool, size) (or ngx_pcalloc() for zeroed memory).
  3. Never call free() on individual allocations.
  4. When request processing completes, destroy the pool with ngx_destroy_pool(pool). Every block allocated inside that pool is freed at once.
// nginx style - no individual frees needed
ngx_pool_t *pool = ngx_create_pool(1024, log);

char *buf1 = ngx_palloc(pool, 256);
char *buf2 = ngx_pcalloc(pool, 512);
ngx_array_t *arr = ngx_array_create(pool, 10, sizeof(int));

// ... do work ...

ngx_destroy_pool(pool);  // ONE call frees buf1, buf2, arr, and associated memory

Why This Matters

In a server that handles thousands of requests, you have two failure modes with traditional malloc/free:

  • Memory leak: you allocate in a code path but forget to free().
  • Double free / use-after-free: you free() something and then accidentally use it, or free() it twice.

The pool eliminates both. You can't forget to free something that was allocated from a pool - the pool owns it. And you can't double-free because you never call free() at all.


Cool stuff.