yum upgrades for production use, this is the repository for you.
Active subscription is required.
NGINX Directive Execution Order: The 11 Request Phases
Problem Statement
NGINX processes every request through a fixed pipeline of eleven phases. A directive does not run where it is written in the configuration file—it runs when its phase comes up in the pipeline. This explains common configuration “bugs” that are not actually bugs but rather misunderstandings of phase ordering. When a configuration behaves unexpectedly, reading the file top to bottom is the wrong approach; the correct question is which phase each line belongs to.
The Eleven Phases
In order:
1. post-read
2. server-rewrite
3. find-config
4. rewrite
5. post-rewrite
6. preaccess
7. access
8. post-access
9. precontent
10. content
11. log
Key architectural notes:
– find-config (phase 3) is where NGINX selects the location block. Phases 1–2 run before any location exists.
– Precontent (phase 9) is the modern name for the old “try-files phase,” renamed in NGINX 1.13.4.
– The content phase (10) runs only a single content handler if the location has one—unlike other phases that run handler lists. Directives like echo, content_by_lua_block, and proxy_pass compete for that single slot; the last one NGINX parses wins, and the others are silently discarded with no error or warning. Static file serving (root, index, autoindex) runs only when no other content handler claims the slot.
– The log phase (11) runs for all requests, including rejections.
Phase Ordering Consequences
return beats deny
return runs in the rewrite phase (phase 4); deny runs in the access phase (phase 7). Since rewrite precedes access, return wins and the access check never executes. A location mixing return with access-control directives should be treated as a bug until proven otherwise.
Server-level rewrite changes location selection
Because find-config is phase 3, a server-level rewrite (phase 2) changes the URI before NGINX picks a location. The location handling the request matches the rewritten URI, not the original one. This is also why a server-level set is visible in every location.
Preaccess vs. access phases reject independently
A rate limiter (limit_req, preaccess phase 6) and an ACL (deny, access phase 7) produce different status codes depending on which rejects first. First request passing the limit gets 403 from access; subsequent over-limit requests get 503 from preaccess. The status code itself tells you which phase you are in.
Configuration constraint: limit_req_zone is valid only directly inside http { }; only limit_req may appear in server or location blocks.
try_files lives in precontent (phase 9)
Its non-final arguments are filesystem paths tested against root. Only a named location (@name) or a final =code acts as a fallback target—prefix locations like /fallback.txt do not work.
Third-Party Module Phase Registration
Third-party modules register into the same eleven phases:
rewrite_by_lua_blockalways runs after every standard rewrite-module directive in the same location, regardless of textual position—the Lua module deliberately places itself at the end of the rewrite phase.set-miscalso lands in the rewrite phase, making its output available to content-phase directives.headers-moresplits across the pipeline:more_set_input_headersregisters a rewrite-phase handler;more_set_headersis an output header filter running after the content phase produces a response.
Common Bug Patterns
returnorrewrite ... lastin a location with access control—rewrite precedes access, so access directives never run.- Treating
iflike a programming-language conditional—it is a rewrite-phase construct and cannot wrapdeny,limit_req, orproxy_pass. - Expecting
rewriteto pick the original location—it runs before find-config, so the rewritten URI selects the location. - Expecting a location-level
set(phase 4) to affect areal_ipdecision (phase 1). - Treating
try_filesarguments as locations—only@namedlocations work as fallbacks.
Installation for Examples (GetPageSpeed packages)
RHEL-based systems:
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-echo nginx-module-set-misc nginx-module-lua nginx-module-headers-more
Load modules near the top of nginx.conf—load ndk_http_module.so first (pulled in automatically as a dependency):
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_echo_module.so;
load_module modules/ngx_http_set_misc_module.so;
load_module modules/ngx_http_lua_module.so;
load_module modules/ngx_http_headers_more_filter_module.so;
Debian/Ubuntu:
sudo apt-get update
sudo apt-get install nginx-module-echo nginx-module-set-misc nginx-module-lua nginx-module-headers-more
Packages handle module loading automatically—no load_module directive needed.
Practical Payoff
Runtime tools like echo, set-misc, lua, and headers-more let you print the pipeline’s state from inside it, making phase order observable rather than theoretical. This transforms debugging from guesswork into inspection: instead of reading the configuration top to bottom, you can instrument each phase to see exactly what state exists when each directive runs.
Read the full article: NGINX Directive Execution Order: The 11 Request Phases
