Inside Nginx: How It Starts, Handles Requests, and Key Callbacks Explained

This article provides a comprehensive walkthrough of Nginx’s startup sequence, master‑worker process communication, critical callback configurations, and the detailed flow of HTTP request handling, supplemented with GDB debugging steps and example module development for deeper insight into its modular architecture.

Efficient Ops
Efficient Ops
Efficient Ops
Inside Nginx: How It Starts, Handles Requests, and Key Callbacks Explained

Early on I wanted to read Nginx source code, and after a busy period I finally did, finding the overall structure understandable and deciding to learn Nginx+Lua development.

This note records my observations, focusing on four main points:

Nginx startup process

Important callback function settings

Nginx HTTP request handling

Summary

1. Nginx Startup Process

Nginx is large, so a macro‑level analysis is more feasible. To debug with GDB you must compile with the -g option. Edit nginx/auto/cc/conf and change ngx_compile_opt="-c" to ngx_compile_opt="-c -g", then run ./configure && make to produce the executable in the objs directory.

Run the binary; it loads the default configuration and daemonizes. Attach GDB to the master and worker processes using their PIDs (e.g., pidof nginx then gdb -p <pid>).

The master process starts from main(), performs initialization, creates a PID file, and calls ngx_master_process_cycle(), which forks worker processes and then blocks on signals.

Signal handling is implemented via pipes: each worker has a read/write descriptor pair; the master writes a flag to the write end, workers detect the flag via epoll/kqueue and act accordingly.

Worker processes inherit the master’s stack. The startup chain is: ngx_start_worker_processes()ngx_spawn_process() (sets up pipes and forks)

Child runs ngx_worker_process_cycle(), which calls ngx_worker_process_init() to set priorities, file descriptor limits, signal masks, and module initialization.

Then it enters an infinite loop calling ngx_process_events_and_timers(), which acquires the accept lock, registers the listening socket, and dispatches events via ngx_process_events() (e.g., ngx_epoll_process_events()).

To avoid the thundering‑herd problem, workers compete for the accept lock; the winner handles the client connection while others only process already‑accepted events.

2. Important Callback Function Settings

After the master and workers are running, client requests arrive. The listening socket’s read callback is set during event module initialization in ngx_event_process_init(), where ngx_event_accept is assigned to each listenfd.

Functions ngx_add_conn and ngx_add_event are pointers stored in the ngx_event_actions structure, which varies by platform (epoll on Linux, kqueue on macOS, poll otherwise). The appropriate platform‑specific functions (e.g., ngx_epoll_add_connection, ngx_kqueue_add_event) are invoked.

When a client connects, the listenfd callback calls accept(), obtains a ngx_connection_t from the pool, registers it with the event system, and finally invokes the handler stored in ls->handler. This handler is set by the HTTP module during configuration parsing (the http directive) and points to ngx_http_init_connection(), the entry point for HTTP request processing.

3. Nginx HTTP Request Processing

The HTTP module processes a request in three high‑level steps:

Read and parse the request line

Read and parse request headers

Enter the multi‑phase handler chain

After the line and headers are parsed, the request is stored in a ngx_http_request_t structure. Nginx then runs through 11 processing phases, each with its own handler (e.g., URI rewrite, access control, content generation, logging). This design is similar to middleware pipelines in frameworks like Python’s WSGI or Go’s net/http.

To illustrate module registration, I created a minimal third‑party module:

Create a thm/foo directory with ngx_http_foo_module.c Add a simple config file

Register a handler in the NGX_HTTP_CONTENT_PHASE Recompile Nginx and load the module

Using GDB, I set a breakpoint at the content phase and observed the call chain:

Client connection triggers ngx_epoll_process_eventsngx_event_accept Connection is wrapped in ngx_connection_t and passed to

ngx_http_init_connection
ngx_http_init_connection

calls ngx_http_init_request, which invokes ngx_http_process_request_line and ngx_http_process_request_header The request is finally handled by ngx_http_handler, which runs ngx_http_core_run_phases where the registered ngx_http_foo_handler executes.

4. Summary

This note offers a macro‑level view of Nginx’s execution flow, from process startup and inter‑process communication to event‑driven request handling and the multi‑phase architecture that enables third‑party module integration. Further study of the phase handlers will deepen understanding of custom module development.

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.

Event-Driven Architecturegdb debuggingmodule developmenthttp request processing
Efficient Ops
Written by

Efficient Ops

This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.

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.