Backend Development 25 min read

Understanding Nginx Startup Process and Core Design Principles

This article explores Nginx’s elegant architecture, detailing its master‑worker process model, modular design, event‑driven engine, and the step‑by‑step initialization sequence—including command‑line parsing, cycle creation, container setup, configuration parsing, socket inheritance, and listening socket creation—based on version 1.15.8.1.

Xueersi Online School Tech Team
Xueersi Online School Tech Team
Xueersi Online School Tech Team
Understanding Nginx Startup Process and Core Design Principles

The author introduces Nginx, describing how personal experience with using the server led to a deeper study of its source code, aiming to share insights on its design and implementation.

Process Model : Nginx employs a master process that manages multiple equal worker processes. Workers handle client requests, share memory, and use atomic operations for load balancing, leveraging SMP multi‑core architectures for robustness.

Modular Design : The core provides minimal code; most functionality resides in modules that follow a common interface and data structures. Modules are categorized (core, configuration, event, HTTP, mail) and register hooks for the 11 request processing phases, offering extensibility and reliability.

Event‑Driven Engine : Nginx processes network and disk events (read/write) using an event module that employs efficient data structures like red‑black trees for timers. It supports various OS event models (epoll, poll, select, kqueue, eventport), with epoll being the primary mechanism on Linux 2.6+ for handling massive concurrent connections.

Startup Flow – Command‑Line Parsing : When executing /home/nginx/sbin/nginx -c /home/nginx/conf/nginx.conf , Nginx parses command‑line options via ngx_get_options(argc, argv) , setting global flags such as ngx_show_version for version queries.

Initialization (ngx_*_init) : Nginx initializes several subsystems, including time ( ngx_time_init() ), regular expressions ( ngx_regex_init() ), and logging ( ngx_log_init() ), preparing global structures and memory pools.

Socket Inheritance : For graceful upgrades, the old master stores listening socket descriptors in the NGINX environment variable (e.g., NGINX=fd1;fd2;fd3 ). The new master parses this variable, populates ngx_listening_t entries, and inherits socket settings.

ngx_init_cycle : This crucial step creates a new ngx_cycle_t structure, copying essential fields (log, prefixes, config file paths) from a temporary old cycle. Sample code: ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle) { log = old_cycle->log; pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); if (pool == NULL) { return NULL; } pool->log = log; cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)); if (cycle == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->pool = pool; cycle->log = log; cycle->old_cycle = old_cycle; // copy prefixes, config file, etc. ... return cycle; }

Container Initialization : Nginx initializes dynamic arrays and lists for paths, open files, shared memory, and listening sockets using functions like ngx_array_init and ngx_list_init . Example snippet: n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10; if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; }

Core Module Configuration Creation : Nginx iterates over all modules, invoking create_conf for each core module to allocate configuration structures and store them in conf_ctx . for (i = 0; cycle->modules[i]; i++) { if (cycle->modules[i]->type != NGX_CORE_MODULE) { continue; } module = cycle->modules[i]->ctx; if (module->create_conf) { rv = module->create_conf(cycle); if (rv == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->conf_ctx[cycle->modules[i]->index] = rv; } }

Configuration File Parsing : After creating configuration contexts, Nginx parses nginx.conf via ngx_conf_parse , dispatching each directive to the appropriate module’s handler, which stores values in the corresponding conf_ctx entry.

Core Module Initialization (init_conf) : Post‑parsing, Nginx calls init_conf for core modules to set default values for unspecified directives (e.g., daemon mode, worker_processes).

Directory and File Creation : Nginx creates necessary directories and files (temporary client body storage, proxy caches, access and error logs) based on configuration, ensuring proper permissions.

Socket Creation and Listening : Using the populated listening array, Nginx opens sockets, binds them, and calls listen with a backlog, retrying up to five times. Sample code: ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle) { for (tries = 5; tries; tries--) { for (i = 0; i < cycle->listening.nelts; i++) { s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0); if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) { ... } if (listen(s, ls[i].backlog) == -1) { ... } } } return NGX_OK; }

Finalization : Nginx invokes init_module for all modules, releases temporary resources, closes old master sockets, and completes the startup, leaving a fully initialized ngx_cycle_t to manage the server’s lifetime.

The article concludes with a summary of the design concepts and a preview of upcoming chapters covering configuration parsing and process work modes.

BackendC++nginxevent-drivenstartupprocess model
Xueersi Online School Tech Team
Written by

Xueersi Online School Tech Team

The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.

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.