nginx/nginx
Request lifecycle
Active contributors: Sergey Kandaurov, Maxim Dounin, Roman Arutyunyan
The state machine that every HTTP request, regardless of protocol version, runs through. Understanding it is the single highest-leverage thing to know about the HTTP code.
The 11 phases
src/http/ngx_http_core_module.h defines the enum:
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_PRECONTENT_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;Each phase has a ngx_http_phase_handler_t array with checker + handler function pointers. checker decides whether to call handler and what to do with its return value; handler is the actual hook.
How modules attach to phases
In postconfiguration, an HTTP module appends a handler:
static ngx_int_t
ngx_http_my_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_my_access_handler;
return NGX_OK;
}After all modules have registered, ngx_http_init_phase_handlers() flattens these arrays into a single cmcf->phase_engine.handlers[] array with explicit "next phase" indexes. At request time, the engine just walks this array.
What each phase does
POST_READ
Earliest hook after headers are parsed. The classic user is realip, which rewrites r->connection->sockaddr based on X-Forwarded-For / X-Real-IP so subsequent phases see the real client IP.
SERVER_REWRITE
Rewrites at server scope. The rewrite module attaches here for rewrite ... break; directives that aren't tied to a location.
FIND_CONFIG (core, implicit)
Walks the location tree to pick a matching location block. There's no module hook here — this phase is hard-coded in the engine. After this phase, r->loc_conf points at the chosen location's config. Modules' per-location configs become accessible.
The location tree is precomputed in a static (regex blocks aside) tree-of-arrays optimized for prefix matching with optional regex fallback. Code: ngx_http_init_locations, ngx_http_init_static_location_trees, ngx_http_core_find_location, all in ngx_http_core_module.c.
REWRITE
Rewrites at location scope. Combined with POST_REWRITE, this is where the rewrite module's compiled scripts (variable assignments, if blocks, rewrite directives) actually execute.
POST_REWRITE
If a rewrite changed r->uri, jumps back to FIND_CONFIG to re-pick a location. Limited to 10 internal redirects (r->internal_redirect_count) to prevent loops.
PREACCESS
Before access checks. limit_conn, limit_req, and degradation attach here. These can short-circuit with NGX_HTTP_SERVICE_UNAVAILABLE (or 429) without consulting any auth modules.
ACCESS
Authorization. access (allow/deny by IP), auth_basic, and auth_request attach here. The phase's checker understands the satisfy directive: with satisfy all, every handler must pass; with satisfy any, one passing handler is enough and the rest of the phase is skipped.
POST_ACCESS
Bookkeeping for satisfy any — finalizes whether any access handler passed.
PRECONTENT
Before the content handler. try_files and mirror attach here. try_files walks a list of file names and may set r->uri and short-circuit; mirror issues subrequests to the configured mirror locations. (PRECONTENT was once called TRY_FILES — same idea.)
CONTENT
The phase that produces the response body. Either a content handler is attached to the location (r->content_handler non-NULL, set by proxy_pass, fastcgi_pass, dav, etc.) or modules attached to the phase get called in order.
The handler returns a status code: NGX_HTTP_OK (we sent the response), NGX_HTTP_NOT_FOUND (try the next), NGX_DECLINED (not us, try next), NGX_DONE (we're handling it asynchronously, leave us alone), NGX_AGAIN (retry later). The engine maps these into either continuing or finalizing.
LOG
Per-request bookkeeping, run after the response has been sent. The access log module (ngx_http_log_module) attaches here to write a log line.
Read path: connection → request
sequenceDiagram
participant E as event loop
participant L as listener handler
participant C as ngx_http_init_connection
participant W as wait_request_handler
participant P as parse_request_line
participant H as parse_request_headers
participant R as ngx_http_process_request
E->>L: socket readable on listen fd
L->>C: ngx_event_accept dispatched to per-protocol init
C->>E: arm c->read for first byte
E->>W: bytes available
W->>P: read into recv_buf, parse first line
P->>H: parsed; arm to read more headers
H->>R: \r\n\r\n reached
R->>R: ngx_http_init_phase_handlers walkngx_http_init_connection is in src/http/ngx_http_request.c. It:
- Allocates an
ngx_http_connection_t(per-connection HTTP state). - Sets
c->read->handler = ngx_http_wait_request_handler. - Arms a timer (
client_header_timeout). - Returns to the event loop.
When the first byte arrives, wait_request_handler allocates an ngx_http_request_t from r->pool, sets the read handler to ngx_http_process_request_line, and continues. Each handler is just an event handler; the event loop drives the whole thing one ready event at a time.
Body reading
For requests with a body (Content-Length or Transfer-Encoding: chunked), the body is not read during phase processing by default. The content handler decides:
- Discard the body —
ngx_http_discard_request_body(r)reads and tosses. - Read all of it into memory/temp file —
ngx_http_read_client_request_body(r, post_handler). This buffers the body usingclient_body_buffer_sizeand possiblyclient_body_temp_path. - Stream it through — used by
proxy_passwithproxy_request_buffering off;and byfastcgi_pass. The body is forwarded chunk by chunk as it arrives.
Code: src/http/ngx_http_request_body.c.
Output
After the content handler decides what to send, output flows through filters (see index) and ends in ngx_http_write_filter, which calls c->send_chain (typically sendfile-backed via ngx_linux_sendfile_chain or equivalent on other OSes).
If the socket is full, ngx_http_write_filter returns NGX_AGAIN and the request is paused until the write event fires. The engine handles this transparently — content handlers can ignore NGX_AGAIN from ngx_http_send_header and ngx_http_output_filter by checking r->buffered.
Finalize
Every code path ends in ngx_http_finalize_request(r, rc):
rc == NGX_OK/ 200-class — flush remaining output, log, possibly enter keepaliverc == NGX_HTTP_*— send canned error page (or runerror_pageredirection)rc == NGX_ERROR— close the connectionrc == NGX_DONE— already handled, just clean uprc == NGX_DECLINED— re-enter the phase engine
Subrequests finalize differently: their parents are notified via r->parent->postponed. The postpone filter merges output back in order.
Internal redirects
X-Accel-Redirect from a backend, error_page = /uri;, and try_files all use internal redirects: ngx_http_internal_redirect(r, &uri, &args) resets r->uri, increments r->internal_redirect_count, and re-enters the phase engine at FIND_CONFIG. This is bounded at 10 to prevent infinite loops.
Keep-alive
After a successful response, if Connection: keep-alive and keepalive_timeout allows it, the engine arms a new ngx_http_wait_request_handler and goes back to sleep. The same ngx_connection_t and c->pool are reused; r->pool is destroyed and a new one is created on the next request.
HTTP/2 and HTTP/3 streams
H2 and H3 multiplex many requests on one connection. The framing layer creates one ngx_http_request_t per stream and feeds it into the same phase engine. The differences:
- Headers come from a frame parser (
ngx_http_v2_state_*,ngx_http_v3_parse_*), not fromngx_http_parse_request_line. - Body bytes come from
DATAframes, not from raw socket reads. - Output goes through
ngx_http_v2_filter_module/ngx_http_v3_filter_moduleinstead of the chunked filter.
The application logic (phase handlers, filters, content handlers) is identical between H1, H2, and H3.
Common pitfalls
- Calling
ngx_http_finalize_requesttwice — the second call is usually a bug and may double-free. User->main->countto check. - Forgetting to discard the body — if a content handler returns without reading or discarding, the connection state can corrupt on the next pipelined request.
- Mixing subrequest output with parent output without the postpone filter — bypass it and you'll see interleaved bytes. Always go through
ngx_http_output_filter. - Ignoring
NGX_DECLINEDvsNGX_OK— phase handlers must return the right code for the engine to advance.
For lower-level changes: the engine itself is in src/http/ngx_http_core_module.c's ngx_http_core_run_phases. Modify with extreme care; every HTTP module relies on its semantics.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.