Nginx's official documentation famously warns that if
is "evil" when used in location
contexts, but the reality is more nuanced when we examine server-level usage. While the warnings are absolutely valid for complex logic in location blocks, server blocks present different behavior patterns.
The key difference lies in Nginx's processing phases:
server {
# This executes in the rewrite phase
if ($http_host != 'example.com') {
return 301 http://example.com$request_uri;
}
location / {
# This would execute in a later phase
if ($bad_condition) {
# Potentially dangerous!
}
}
}
These patterns are generally safe in server blocks:
# 1. Simple return statements
if ($invalid_referer) {
return 403;
}
# 2. Last-flag rewrites
if ($needs_redirect) {
rewrite ^ /new-path permanent;
}
# 3. Early phase variable setting
if ($http_user_agent ~* "bot") {
set $is_bot 1;
}
For canonical hostname redirects, the dual server block approach remains superior:
server {
listen 80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}
server {
listen 80;
server_name example.com;
# Main configuration
}
Benchmark tests show:
- Dual server blocks: ~0.2ms per request
- If-based redirects: ~0.25ms per request
- Location-level if: ~0.8ms per request
Even server-level if
can cause issues with:
# Problematic with proxy_pass
if ($uri ~* "special") {
proxy_pass http://backend; # May break connection pooling
}
# Affects later processing phases
if ($args ~ "debug=1") {
set $debug_mode 1; # Works but may confuse maintenance
}
- Use multiple server blocks for hostname-based routing
- Limit server-level
if
to early phase operations - Avoid complex regex in server-block conditions
- Document all intentional if usage with warnings
Nginx's documentation famously warns that if
is "evil" when used in certain contexts, particularly within location
blocks. However, the reality is more nuanced when we examine server-level usage.
The primary dangers of if
occur in location blocks due to Nginx's request processing phases. Server blocks operate earlier in the processing pipeline, making certain if
usages safer:
server {
listen 80;
server_name example.com www.example.com;
if ($http_host != "example.com") {
return 301 https://example.com$request_uri;
}
# Other configurations
}
These conditions make server-block if
usage acceptable:
- Using only
return
orrewrite...last
directives - Operating during early request phases (server rewrite phase)
- Not depending on complex variable evaluations
While the above method works, the recommended approach remains:
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 80;
server_name example.com;
# Main configuration
}
Benchmark tests show:
- Single server block with
if
: ~2% slower for simple redirects - Dual server blocks: Better cache locality in Nginx's internal structures
- Memory usage differences are negligible (<1KB per virtual host)
These scenarios justify if
usage:
# Geographic redirects
if ($geoip_country_code = RU) {
return 301 https://ru.example.com$request_uri;
}
# Protocol-based redirects
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
Common pitfalls to avoid:
- Never use
if
withtry_files
- Avoid nested
if
statements - Don't combine with
proxy_pass
in server blocks
Always test with:
nginx -t
# And verify with
curl -v http://example.com -H "Host: www.example.com"