Backend Development 14 min read

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-&gt;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_events

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

backend developmentNginxevent-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

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