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.
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
-goption. Edit
nginx/auto/cc/confand change
ngx_compile_opt="-c"to
ngx_compile_opt="-c -g", then run
./configure && maketo produce the executable in the
objsdirectory.
Run the binary; it loads the default configuration and daemonizes. Attach GDB to the master and worker processes using their PIDs (e.g.,
pidof nginxthen
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_acceptis assigned to each
listenfd.
Functions
ngx_add_connand
ngx_add_eventare pointers stored in the
ngx_event_actionsstructure, 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
listenfdcallback calls
accept(), obtains a
ngx_connection_tfrom 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
httpdirective) 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_tstructure. 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/foodirectory with
ngx_http_foo_module.cAdd a simple
configfile
Register a handler in the
NGX_HTTP_CONTENT_PHASERecompile 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_events→
ngx_event_acceptConnection is wrapped in
ngx_connection_tand passed to
ngx_http_init_connection ngx_http_init_connectioncalls
ngx_http_init_request, which invokes
ngx_http_process_request_lineand
ngx_http_process_request_headerThe request is finally handled by
ngx_http_handler, which runs
ngx_http_core_run_phaseswhere the registered
ngx_http_foo_handlerexecutes.
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.
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.
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.