HTTP Request Lifecycle¶
Every HTTP request in NGINX passes through a series of phases. Understanding this lifecycle is essential for module development and debugging.
Request Flow Overview¶
Client NGINX Backend
│ │ │
│──── TCP Connect ────────────►│ │
│ │ │
│──── HTTP Request ───────────►│ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ Parse Headers │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ 11 HTTP Phases │ │
│ └─────────┬─────────┘ │
│ │ │
│ │──── Proxy Request ───────────►│
│ │ │
│ │◄─── Proxy Response ───────────│
│ │ │
│ ┌─────────┴─────────┐ │
│ │ Response Filters │ │
│ └─────────┬─────────┘ │
│ │ │
│◄──── HTTP Response ──────────│ │
│ │ │
The 11 HTTP Phases¶
NGINX processes HTTP requests in 11 distinct phases:
┌──────────────────────────────────────────────────────────────┐
│ HTTP Phases │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. NGX_HTTP_POST_READ_PHASE │
│ └── After reading request headers │
│ │
│ 2. NGX_HTTP_SERVER_REWRITE_PHASE │
│ └── Server-level rewrite directives │
│ │
│ 3. NGX_HTTP_FIND_CONFIG_PHASE [internal] │
│ └── Find matching location block │
│ │
│ 4. NGX_HTTP_REWRITE_PHASE │
│ └── Location-level rewrite directives │
│ │
│ 5. NGX_HTTP_POST_REWRITE_PHASE [internal] │
│ └── Redirect to new location after rewrite │
│ │
│ 6. NGX_HTTP_PREACCESS_PHASE │
│ └── Pre-access checks (limit_req, limit_conn) │
│ │
│ 7. NGX_HTTP_ACCESS_PHASE │
│ └── Access control (allow/deny, auth_basic) │
│ │
│ 8. NGX_HTTP_POST_ACCESS_PHASE [internal] │
│ └── Access check result processing │
│ │
│ 9. NGX_HTTP_PRECONTENT_PHASE │
│ └── try_files, mirror │
│ │
│ 10. NGX_HTTP_CONTENT_PHASE │
│ └── Content generation (proxy_pass, fastcgi, static) │
│ │
│ 11. NGX_HTTP_LOG_PHASE │
│ └── Request logging │
│ │
└──────────────────────────────────────────────────────────────┘
Phase Details¶
| # | Phase | Modules | Can Decline |
|---|---|---|---|
| 1 | POST_READ |
realip | Yes |
| 2 | SERVER_REWRITE |
rewrite | Yes |
| 3 | FIND_CONFIG |
(core) | No |
| 4 | REWRITE |
rewrite | Yes |
| 5 | POST_REWRITE |
(core) | No |
| 6 | PREACCESS |
limit_req, limit_conn | Yes |
| 7 | ACCESS |
access, auth_basic, auth_request | Yes |
| 8 | POST_ACCESS |
(core) | No |
| 9 | PRECONTENT |
try_files, mirror | Yes |
| 10 | CONTENT |
static, proxy, fastcgi | No |
| 11 | LOG |
log | Yes |
Phase Handler Return Values¶
NGX_OK /* Handler completed successfully */
NGX_DECLINED /* Pass to next handler in phase */
NGX_AGAIN /* Not ready, call again later */
NGX_DONE /* Request will be finalized elsewhere */
NGX_ERROR /* Error occurred */
NGX_HTTP_... /* Return specific HTTP status code */
Handler Behavior¶
static ngx_int_t
ngx_http_mymodule_handler(ngx_http_request_t *r)
{
/* Check if we should handle this request */
if (!should_handle(r)) {
return NGX_DECLINED; /* Pass to next handler */
}
/* Check for errors */
if (error_condition) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* Async operation pending */
if (waiting_for_backend) {
return NGX_AGAIN;
}
/* Request handled, response sent elsewhere */
if (subrequest_started) {
return NGX_DONE;
}
/* Successfully handled */
return NGX_OK;
}
Request Structure¶
The ngx_http_request_t structure holds all request state:
struct ngx_http_request_t {
/* Connection */
ngx_connection_t *connection;
/* Request line */
ngx_uint_t method; /* NGX_HTTP_GET, etc. */
ngx_uint_t http_version; /* NGX_HTTP_VERSION_11 */
ngx_str_t request_line;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t exten; /* File extension */
ngx_str_t unparsed_uri;
/* Headers */
ngx_http_headers_in_t headers_in;
ngx_http_headers_out_t headers_out;
/* Body */
ngx_http_request_body_t *request_body;
/* Response */
off_t headers_out.content_length_n;
ngx_str_t headers_out.content_type;
/* State */
ngx_pool_t *pool;
ngx_http_request_t *main; /* Main request */
ngx_http_request_t *parent; /* Parent (subrequest) */
/* Configuration */
void **ctx; /* Module contexts */
void **main_conf;
void **srv_conf;
void **loc_conf;
/* Flags */
unsigned internal:1;
unsigned header_only:1;
unsigned keepalive:1;
unsigned chunked:1;
/* ... many more ... */
};
Detailed Phase Walkthrough¶
1. Connection Accept¶
/* New connection callback */
static void
ngx_http_init_connection(ngx_connection_t *c)
{
/* Set read handler to wait for request */
c->read->handler = ngx_http_wait_request_handler;
/* Add read timer */
ngx_add_timer(c->read, cscf->client_header_timeout);
/* Enable read event */
ngx_handle_read_event(c->read, 0);
}
2. Parse Request Line¶
GET /path/to/resource?arg=value HTTP/1.1\r\n
│ │ │ │
│ │ │ └── http_version
│ │ └── args
│ └── uri
└── method
3. Parse Headers¶
Host: example.com\r\n
User-Agent: Mozilla/5.0\r\n
Accept: text/html\r\n
\r\n
4. Run Phases¶
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
ph = cmcf->phase_engine.handlers;
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
5. Content Generation¶
Content Handler Options:
├── ngx_http_static_module → Serve static file
├── ngx_http_index_module → Generate index
├── ngx_http_proxy_module → Proxy to backend
├── ngx_http_fastcgi_module → FastCGI backend
├── ngx_http_uwsgi_module → uWSGI backend
├── ngx_http_grpc_module → gRPC backend
└── Custom module handler → Your code
6. Response Filters¶
Response passes through filter chain:
┌──────────────────┐
│ Content Handler │
└────────┬─────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Filter Chain │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ gzip │ → │ SSI │ → │ chunked │ → │
│ │ filter │ │ filter │ │ filter │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ headers │ → │ range │ → │ write │ → │
│ │ filter │ │ filter │ │ filter │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐
│ Send to Client │
└──────────────────┘
Subrequests¶
NGINX can create internal subrequests:
ngx_int_t
ngx_http_subrequest(ngx_http_request_t *r,
ngx_str_t *uri, ngx_str_t *args,
ngx_http_request_t **psr,
ngx_http_post_subrequest_t *ps,
ngx_uint_t flags);
Subrequest Flow¶
Main Request Subrequest
│ │
│ ngx_http_subrequest() │
│ ─────────────────────────────► │
│ │
│ ┌─────┴─────┐
│ │ Run phases│
│ └─────┬─────┘
│ │
│ ◄─────────────────────────── │
│ Callback (ps->handler) │
│ │
▼ ▼
Example: Auth Subrequest¶
/* Create subrequest to /auth endpoint */
ngx_str_t uri = ngx_string("/auth");
ngx_http_post_subrequest_t *ps;
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
ps->handler = ngx_http_auth_done;
ps->data = r;
ngx_http_subrequest(r, &uri, NULL, &sr, ps, NGX_HTTP_SUBREQUEST_WAITED);
Internal Redirects¶
Location changes without client round-trip:
/* Internal redirect to new URI */
ngx_http_internal_redirect(r, &new_uri, &r->args);
/* Named location redirect */
ngx_http_named_location(r, &name);
try_files Implementation¶
location / {
try_files $uri $uri/ /index.php;
}
Translates to:
1. Try: /path/to/file
└── Not found, continue
2. Try: /path/to/file/
└── Not found, continue
3. Internal redirect: /index.php
└── Restart phase processing
Request Finalization¶
void
ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
/* Handle special return codes */
if (rc == NGX_DONE) {
/* Request already finalized elsewhere */
return;
}
if (rc == NGX_DECLINED) {
/* Pass to next phase handler */
r->phase_handler++;
ngx_http_core_run_phases(r);
return;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
/* Generate error page */
ngx_http_finalize_request_error(r, rc);
return;
}
/* Send response and close or keep-alive */
ngx_http_finalize_connection(r);
}
Debugging Request Flow¶
Enable Debug Logging¶
error_log /var/log/nginx/debug.log debug;
Debug Output Example¶
[debug] http request line: "GET /index.html HTTP/1.1"
[debug] http uri: "/index.html"
[debug] http args: ""
[debug] http exten: "html"
[debug] generic phase: 0 # POST_READ
[debug] rewrite phase: 1 # SERVER_REWRITE
[debug] find config phase: 2 # FIND_CONFIG
[debug] using configuration "/"
[debug] rewrite phase: 3 # REWRITE
[debug] post rewrite phase: 4 # POST_REWRITE
[debug] generic phase: 5 # PREACCESS
[debug] generic phase: 6 # ACCESS
[debug] post access phase: 7 # POST_ACCESS
[debug] generic phase: 8 # PRECONTENT
[debug] content phase: 9 # CONTENT
[debug] http filename: "/var/www/html/index.html"
[debug] HTTP/1.1 200 OK
[debug] http output filter
[debug] http write filter
[debug] http log handler
Best Practices¶
Request Processing Tips
- Understand phase order - Critical for module placement
- Return
NGX_DECLINED- If your handler shouldn't process the request - Don't block - Use async operations for I/O
- Use subrequests - For auth checks, SSI includes
- Leverage filters - For response transformation
- Log at debug level - For troubleshooting flow issues