1. Rewrite Rules with Regex
Rewrite rules cho phép modify URLs trước khi xử lý requests.
1.1. Cú pháp Rewrite
rewrite regex replacement [flag];regex: Regular expression pattern to match
replacement: New URL
flag: Optional flag (break, last, redirect, permanent)
Flags:
break- Stop processing, use rewritten URIlast- Stop processing rewrite rules, re-search locationsredirect- Return 302 temporary redirectpermanent- Return 301 permanent redirect
1.2. Basic Rewrite Examples
server { listen 80; server_name example.com;# Simple rewrite rewrite ^/old-page$ /new-page permanent; # Rewrite with regex capture rewrite ^/user/(.*)$ /profile/$1 permanent; # Multiple captures rewrite ^/post/([0-9]+)/([a-z]+)$ /article/$2/$1 permanent; location / { root /var/www/html; }
}
Example với captures:
server { # /product/123 → /item.php?id=123 rewrite ^/product/([0-9]+)$ /item.php?id=$1 last;# /category/electronics/page/5 → /cat.php?name=electronics&page=5 rewrite ^/category/([^/]+)/page/([0-9]+)$ /cat.php?name=$1&page=$2 last; # /blog/2024/12/my-post → /blog.php?year=2024&month=12&slug=my-post rewrite ^/blog/([0-9]{4})/([0-9]{2})/(.+)$ /blog.php?year=$1&month=$2&slug=$3 last;
}
1.3. Rewrite Flags
break flag:
location /api/ { # Rewrite và dừng processing rewrite ^/api/v1/(.*)$ /v1/$1 break;# Continue với rewritten URI proxy_pass http://backend;}
Request: /api/v1/users
Rewritten: /v1/users
Proxied to: http://backend/v1/users
last flag:
server { # Rewrite và re-search locations rewrite ^/old/(.*)$ /new/$1 last;location /new/ { # This location will be matched after rewrite root /var/www/html; }}
Request: /old/page
Rewritten: /new/page
Re-search locations, matches /new/
redirect flag (302):
server { # Temporary redirect rewrite ^/temp/(.*)$ /permanent/$1 redirect; }Returns HTTP 302 Found
Browser redirects to new URL
permanent flag (301):
server { # Permanent redirect (SEO-friendly) rewrite ^/old-site/(.*)$ https://newsite.com/$1 permanent; }Returns HTTP 301 Moved Permanently
Search engines update their index
1.4. Regex Patterns
Common patterns:
server { # Match digits rewrite ^/product/([0-9]+)$ /item/$1 last;# Match letters rewrite ^/user/([a-z]+)$ /profile/$1 last; # Match alphanumeric rewrite ^/page/([a-zA-Z0-9]+)$ /content/$1 last; # Match anything except slash rewrite ^/category/([^/]+)$ /cat/$1 last; # Match with specific length rewrite ^/code/([0-9]{6})$ /verify/$1 last; # Optional parts rewrite ^/blog/([0-9]+)(/.*)?$ /post/$1$2 last; # Case-insensitive (use ~* in location) location ~* ^/PAGE/(.*)$ { rewrite ^ /page/$1 permanent; }
}
Special characters:
# Escape special chars: . * + ? [ ] ^ $ ( ) { } | \Match literal dot
rewrite ^/file.txt$ /document.txt last;
Match query string (use $args)
rewrite ^/search$ /search.php?$args last;
Preserve query string
rewrite ^/old$ /new permanent; # Automatically preserves ?param=value
1.5. Multiple Rewrites
server { listen 80; server_name example.com;# Sequential rewrites rewrite ^/products/(.*)$ /items/$1 last; # This won't execute because of 'last' flag above rewrite ^/items/(.*)$ /products/$1 last; location /items/ { root /var/www/html; }
}
Correct way for chained rewrites:
server { # First rewrite location /old/ { rewrite ^/old/(.*)$ /temp/$1 last; }# Second rewrite location /temp/ { rewrite ^/temp/(.*)$ /new/$1 last; } # Final location location /new/ { root /var/www/html; }
}
1.6. Conditional Rewrites
server { # Rewrite based on conditions# If not HTTPS, redirect if ($scheme != "https") { rewrite ^ https://$host$request_uri permanent; } # If mobile user agent if ($http_user_agent ~* (mobile|android|iphone)) { rewrite ^/$ /mobile/ last; } # If specific referer if ($http_referer ~* google.com) { rewrite ^/landing$ /landing-google last; } # Multiple conditions set $redirect 0; if ($scheme != "https") { set $redirect 1; } if ($host !~* ^www\.) { set $redirect "${redirect}1"; } if ($redirect = "11") { rewrite ^ https://www.$host$request_uri permanent; }
}
2. Return vs Rewrite Directive
2.1. Return Directive
Return is faster and more efficient than rewrite for simple redirects.
server { listen 80; server_name example.com;# Simple return location /old-page { return 301 /new-page; } # Return with variable location /redirect { return 301 https://example.com$request_uri; } # Return with custom response location /api/health { return 200 "OK\n"; } # Return JSON location /api/status { default_type application/json; return 200 '{"status":"ok","timestamp":"$time_iso8601"}'; }
}
2.2. Return Status Codes
server { # 301 - Permanent redirect (SEO) location /old { return 301 /new; }# 302 - Temporary redirect location /temp { return 302 /temporary-location; } # 307 - Temporary redirect (preserves method) location /preserve { return 307 /new-location; } # 308 - Permanent redirect (preserves method) location /permanent-preserve { return 308 /new-permanent; } # 200 - Success location /ok { return 200 "Success"; } # 204 - No Content location /nocontent { return 204; } # 400 - Bad Request location /bad { return 400 "Bad Request"; } # 403 - Forbidden location /forbidden { return 403 "Access Denied"; } # 404 - Not Found location /notfound { return 404 "Not Found"; } # 500 - Internal Server Error location /error { return 500 "Internal Server Error"; } # 503 - Service Unavailable location /maintenance { return 503 "Under Maintenance"; }
}
2.3. Return vs Rewrite Performance
# GOOD - Fast, efficient location /old-page { return 301 /new-page; }SLOWER - Regex processing overhead
location /old-page { rewrite ^/old-page$ /new-page permanent; }
GOOD - Simple return for redirects
if ($host != "www.example.com") { return 301 https://www.example.com$request_uri; }
SLOWER - Regex rewrite
if ($host != "www.example.com") { rewrite ^ https://www.example.com$request_uri permanent; }
2.4. When to Use Each
Use return when:
- Simple redirects
- No regex needed
- Better performance required
- Returning custom content
Use rewrite when:
- Need regex capture groups
- Complex URL transformations
- Need to preserve parts of URL
- Using flags (break, last)
Examples:
server { # Use return - simple redirect location = /about { return 301 /about-us; }# Use rewrite - need regex captures location /product/ { rewrite ^/product/([0-9]+)$ /item.php?id=$1 last; } # Use return - redirect to external URL location /blog { return 301 https://blog.example.com; } # Use rewrite - transform URL structure location /old-api/ { rewrite ^/old-api/v1/(.*)$ /api/v2/$1 break; proxy_pass http://backend; }
}
3. Location Matching
Location matching xác định cách Nginx xử lý different URL patterns.
3.1. Location Modifiers
# No modifier - Prefix match location /images/ { # Matches: /images/*, /images/photo.jpg }= - Exact match (highest priority)
location = /about { # Matches: /about only # NOT: /about/, /about?page=1 }
^~ - Prefix match, stop regex checking
location ^~ /static/ { # Matches: /static/* # Stops checking regex locations }
~ - Case-sensitive regex
location ~ .php$ { # Matches: .php files (case-sensitive) # Matches: file.php, script.PHP (no) }
~* - Case-insensitive regex
location* .(jpg|png|gif)$ { # Matches: .jpg, .JPG, .Jpg (all cases) }
3.2. Location Priority Order
Priority (highest to lowest):
- Exact match
= - Prefix match with
^ - Regular expression
or*(first match wins) - Prefix match (longest match wins)
Example:
server { listen 80;# Priority 1 - Exact match location = /test { return 200 "Exact match: /test\n"; } # Priority 2 - Prefix (stop regex) location ^~ /test { return 200 "Prefix ^~: /test*\n"; } # Priority 3 - Regex location ~* ^/test { return 200 "Regex ~*: /test*\n"; } # Priority 4 - Prefix location /test { return 200 "Prefix: /test*\n"; } # Default location / { return 200 "Default: /\n"; }
}
Test results:
curl http://localhost/test→ "Exact match: /test"
→ "Prefix ^
: /test*" (if ^exists)→ "Regex
: /test" (if no ^)→ "Default: /"
3.3. Prefix Matching
server { # Longest prefix wins location /api/ { return 200 "API root\n"; }location /api/v1/ { return 200 "API v1\n"; } location /api/v2/ { return 200 "API v2\n"; }}
/api/test → "API root"
/api/v1/users → "API v1"
/api/v2/products → "API v2"
3.4. Regex Matching
server { # Case-sensitive regex location ~ ^/API/ { return 200 "Uppercase API\n"; }# Case-insensitive regex location ~* ^/api/ { return 200 "Any case API\n"; } # File extension regex location ~ \.(jpg|png|gif)$ { return 200 "Image file\n"; } # Complex regex location ~ ^/blog/([0-9]{4})/([0-9]{2})/(.+)$ { return 200 "Blog post: $1-$2-$3\n"; }
}
3.5. Nested Locations
server { location /api/ { # Outer location# Nested location location ~ \.json$ { # Matches: /api/*.json return 200 "JSON API\n"; } location ~ \.xml$ { # Matches: /api/*.xml return 200 "XML API\n"; } # Default for /api/ return 200 "API default\n"; }
}
3.6. Named Locations
server { location / { try_files $uri $uri/ @backend; }# Named location (cannot be accessed directly) location @backend { proxy_pass http://backend_server; } # Another example error_page 404 = @notfound; location @notfound { return 404 "Custom 404 page\n"; }
}
3.7. Complete Location Example
server { listen 80; server_name example.com; root /var/www/html;# Exact match - homepage location = / { index index.html; } # Exact match - specific file location = /favicon.ico { log_not_found off; access_log off; } # Prefix match with stop regex location ^~ /static/ { expires 1y; add_header Cache-Control "public, immutable"; } # Regex - PHP files location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # Regex - Image files location ~* \.(jpg|jpeg|png|gif|ico|svg)$ { expires 30d; add_header Cache-Control "public"; } # Prefix match - API location /api/ { proxy_pass http://api_backend/; } # Default location location / { try_files $uri $uri/ =404; } # Deny hidden files location ~ /\. { deny all; }
}
4. Try_files Directive
Try_files checks for files in order and uses the first one found.
4.1. Basic Try_files
location / { # Try file, then directory, then 404 try_files $uri $uri/ =404; }Request: /page
Tries:
1. /page (file)
2. /page/ (directory with index)
3. Returns 404
4.2. Try_files với Fallback
# Single Page Application (SPA) location / { try_files $uri $uri/ /index.html; }Request: /about
Tries:
1. /about (file)
2. /about/ (directory)
3. /index.html (fallback)
WordPress/PHP applications
location / { try_files $uri $uri/ /index.php?$args; }
4.3. Try_files với Named Location
location / { try_files $uri $uri/ @backend; }location @backend { proxy_pass http://backend_server; }
Request: /api/users
Tries:
1. /api/users (file)
2. /api/users/ (directory)
3. @backend (proxy to backend)
4.4. Multiple Fallbacks
location / { # Try multiple files try_files $uri $uri/index.html $uri.html @backend; }location @backend { proxy_pass http://backend; }
Request: /page
Tries:
1. /page
2. /page/index.html
3. /page.html
4. @backend
4.5. Try_files with Custom Response
location / { try_files $uri $uri/ /404.html =404; }Or return custom content
location / { try_files $uri $uri/ =404;
error_page 404 = @notfound;}
location @notfound { return 404 "File not found\n"; }
4.6. Try_files Patterns
Static site:
location / {
root /var/www/html;
try_files $uri $uri/ =404;
}
React/Vue/Angular SPA:
location / {
root /var/www/app;
try_files $uri $uri/ /index.html;
}
WordPress:
location / { try_files $uri $uri/ /index.php?$args; }
location ~ .php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
API with fallback:
location /api/ { # Try cached response, then proxy try_files /cache$uri @api; }
location @api { proxy_pass http://api_backend; }
Multiple static directories:
location /assets/ {
# Try multiple roots
try_files /public$uri /static$uri =404;
}
5. Conditional Redirects
5.1. If Directive
# IMPORTANT: If is evil in Nginx (use sparingly)Prefer map, return, or rewrite when possible
server { # Redirect non-www to www if ($host !~* ^www.) { return 301 https://www.$host$request_uri; }
# Redirect HTTP to HTTPS if ($scheme = http) { return 301 https://$host$request_uri; } # Redirect based on user agent if ($http_user_agent ~* (mobile|android|iphone)) { return 302 /mobile; } # Redirect old domain if ($host = old.example.com) { return 301 https://new.example.com$request_uri; }
}
5.2. Map-based Redirects
http { # Map for redirects (better than if) map $request_uri $redirect_uri { /old-page /new-page; /old-about /about-us; /old-contact /contact; default ""; }server { if ($redirect_uri != "") { return 301 $redirect_uri; } }
}
5.3. Geo-based Redirects
http { geo $country { default US; 192.0.2.0/24 UK; 198.51.100.0/24 JP; }map $country $redirect_domain { US example.com; UK uk.example.com; JP jp.example.com; } server { listen 80; if ($host != $redirect_domain) { return 302 https://$redirect_domain$request_uri; } }
}
5.4. Time-based Redirects
http { map $time_iso8601 $maintenance { default 0; # Maintenance window: 2024-12-03 02:00 to 04:00 ~^2024-12-03T0[2-3] 1; }server { if ($maintenance) { return 503; } error_page 503 /maintenance.html; location = /maintenance.html { root /var/www/errors; } }
}
5.5. Argument-based Redirects
server { # Redirect based on query parameter if ($arg_lang = "fr") { return 302 /fr$request_uri; }if ($arg_lang = "es") { return 302 /es$request_uri; } # Redirect if parameter missing if ($arg_id = "") { return 400 "ID parameter required"; }
}
5.6. Complex Conditions
server { # Multiple conditions (AND logic) set $redirect 0;if ($scheme = http) { set $redirect "${redirect}1"; } if ($host !~* ^www\.) { set $redirect "${redirect}1"; } if ($redirect = "11") { return 301 https://www.$host$request_uri; }
}
6. Real-world Examples
6.1. SEO-friendly Redirects
server { listen 80; listen [::]:80; server_name example.com www.example.com;# HTTP to HTTPS (301 permanent) return 301 https://example.com$request_uri;}
server { listen 443 ssl http2; server_name www.example.com;
# www to non-www (301 permanent) return 301 https://example.com$request_uri;}
server { listen 443 ssl http2; server_name example.com;
# Old URLs to new structure rewrite ^/blog/([0-9]+)/([0-9]+)/(.+)$ /articles/$1-$2-$3 permanent; # Product URLs rewrite ^/product-([0-9]+)\.html$ /products/$1 permanent; # Category URLs rewrite ^/cat-([a-z-]+)$ /category/$1 permanent; location / { root /var/www/html; try_files $uri $uri/ =404; }
}
6.2. Migration from Old Site
http { # Bulk redirects using map map $request_uri $new_uri { /old-about.html /about; /old-contact.html /contact; /products.html /shop; /blog/post1.html /blog/post-1; /blog/post2.html /blog/post-2; # ... hundreds more default ""; }server { listen 443 ssl http2; server_name example.com; # Apply bulk redirects if ($new_uri != "") { return 301 $new_uri; } # Pattern-based redirects rewrite ^/news/([0-9]+)$ /articles/$1 permanent; rewrite ^/downloads/(.+)\.zip$ /files/$1 permanent; location / { root /var/www/html; try_files $uri $uri/ =404; } }
}
6.3. API Versioning
server { listen 80; server_name api.example.com;# Redirect v1 to v2 location /v1/ { rewrite ^/v1/(.*)$ /v2/$1 permanent; } # Current API version location /v2/ { proxy_pass http://api_backend/; } # Default to latest version location / { rewrite ^/(.*)$ /v2/$1 last; }
}
6.4. Multi-language Sites
http { # Detect language from Accept-Language map $http_accept_language $lang { default en; ~^fr fr; ~^es es; ~*^de de; }server { listen 80; server_name example.com; # Redirect to language-specific subdomain location = / { return 302 https://$lang.example.com; } # Or redirect to language path # location = / { # return 302 /$lang/; # } } server { listen 80; server_name en.example.com; root /var/www/en; } server { listen 80; server_name fr.example.com; root /var/www/fr; }
}
6.5. Mobile Redirect
http { # Detect mobile devices map $http_user_agent $is_mobile { default 0; ~mobile 1; ~android 1; ~iphone 1; ~ipad 1; }server { listen 80; server_name example.com; # Redirect mobile users if ($is_mobile) { return 302 https://m.example.com$request_uri; } location / { root /var/www/html; } } server { listen 80; server_name m.example.com; root /var/www/mobile; }
}
6.6. Maintenance Mode
server { listen 80; server_name example.com; root /var/www/html;# Enable maintenance mode set $maintenance off; # Whitelist admin IPs if ($remote_addr ~ "^(192\.168\.1\.|10\.0\.0\.)") { set $maintenance off; } # Check if maintenance file exists if (-f /var/www/maintenance.flag) { set $maintenance on; } if ($maintenance = on) { return 503; } error_page 503 @maintenance; location @maintenance { rewrite ^(.*)$ /maintenance.html break; } location / { try_files $uri $uri/ =404; }
}
6.7. A/B Testing
http { # Split traffic 50/50 split_clients "${remote_addr}${http_user_agent}" $variant { 50% "a"; * "b"; }server { listen 80; server_name example.com; location / { if ($variant = "a") { rewrite ^ /variant-a last; } if ($variant = "b") { rewrite ^ /variant-b last; } } location /variant-a { root /var/www/test-a; } location /variant-b { root /var/www/test-b; } }
}
7. Troubleshooting
7.1. Rewrite Loop Detection
# BAD - Creates infinite loop location / { rewrite ^(.*)$ /index.php last; }location /index.php { rewrite ^(.*)$ / last; }
Request: /page
Rewrite: /index.php
Rewrite: /
Rewrite: /index.php
... infinite loop (Nginx stops after 10 cycles)
Fix:
# GOOD - Use break or proper conditions location / { try_files $uri $uri/ /index.php?$args; }
location ~ .php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; }
7.2. Debug Rewrites
# Enable rewrite log error_log /var/log/nginx/error.log notice; rewrite_log on;server { location / { rewrite ^/test/(.*)$ /result/$1 last; return 200 "Original URI: $uri\n"; } }
Check logs:
tail -f /var/log/nginx/error.log
7.3. Test Redirects
# Test with curl curl -I http://example.com/old-pageFollow redirects
curl -L http://example.com/old-page
Verbose output
curl -v http://example.com/old-page
Check redirect chain
curl -IL http://example.com/old-page
7.4. Common Issues
Issue 1: Query string not preserved
# BAD - Loses query string rewrite ^/old$ /new;GOOD - Preserves query string automatically
rewrite ^/old$ /new permanent;
Or explicitly:
rewrite ^/old$ /new?$args permanent;
Issue 2: Wrong flag causes issues
# BAD - Uses 'last' in if if ($host = old.com) { rewrite ^ https://new.com$request_uri last; }GOOD - Use return or permanent
if ($host = old.com) { return 301 https://new.com$request_uri; }
Issue 3: Regex not matching
# BAD - Missing anchors rewrite /old /new permanent;Matches: /old, /cold, /folder/old
GOOD - Use anchors
rewrite ^/old$ /new permanent;
Matches: /old only
Or use exact location
location = /old { return 301 /new; }
8. Best Practices
8.1. Prefer return over rewrite
# GOOD - Fast and clear location /old { return 301 /new; }AVOID - Slower, less clear
location /old { rewrite ^/old$ /new permanent; }
8.2. Use exact matches when possible
# GOOD - Fastest matching location = /about { return 301 /about-us; }SLOWER - Regex overhead
location ~ ^/about$ { return 301 /about-us; }
8.3. Avoid if when possible
# BAD - Using if if ($request_uri ~ ^/old) { rewrite ^ /new permanent; }GOOD - Use location
location ^~ /old { rewrite ^/old(.*)$ /new$1 permanent; }
BETTER - Use return
location /old { return 301 /new; }
8.4. Use map for bulk redirects
# GOOD - Efficient for many redirects http { map $request_uri $redirect_uri { /page1 /new1; /page2 /new2; /page3 /new3; # ... hundreds more }server { if ($redirect_uri != "") { return 301 $redirect_uri; } }}
BAD - Many individual rewrites
server { rewrite ^/page1$ /new1 permanent; rewrite ^/page2$ /new2 permanent; rewrite ^/page3$ /new3 permanent; # ... hundreds more }
8.5. Document complex rewrites
server { # Redirect old blog structure to new # Old: /blog/2024/12/post-title # New: /articles/2024-12-post-title rewrite ^/blog/([0-9]{4})/([0-9]{2})/(.+)$ /articles/$1-$2-$3 permanent;# Redirect product IDs # Old: /product-123.html # New: /shop/products/123 rewrite ^/product-([0-9]+)\.html$ /shop/products/$1 permanent;
}
8.6. Test thoroughly
# Create test script #!/bin/bashecho "Testing redirects..."
Test 1: Old to new
curl -IL http://example.com/old-page | grep "301|Location"
Test 2: HTTP to HTTPS
curl -IL http://example.com | grep "301|Location"
Test 3: www to non-www
curl -IL https://www.example.com | grep "301|Location"
echo "Tests complete!"
9. Practice Exercises
Exercise 1: Basic Redirects
- Create redirect từ /old-about đến /about-us
- Implement HTTP to HTTPS redirect
- Redirect www to non-www
- Test với curl
Exercise 2: Regex Rewrites
- Rewrite /product/123 → /item/123
- Rewrite /blog/2024/12/post → /articles/2024-12-post
- Preserve query strings
- Test với curl
Exercise 3: Location Matching
- Setup exact match cho /
- Setup prefix match cho /api/
- Setup regex match cho image files
- Test priority order
Exercise 4: Try_files
- Setup SPA với try_files fallback
- Configure WordPress-style try_files
- Add custom 404 page
- Test với various URLs
Exercise 5: Bulk Redirects
- Create map với 10+ redirects
- Implement map-based redirect logic
- Test all redirects
- Measure performance vs individual rewrites
Exercise 6: Complex Scenario
- Migrate old site structure to new
- Implement mobile detection
- Add maintenance mode
- Setup proper error pages
- Test thoroughly
Summary
In this lesson, you learned:
- ✅ Rewrite rules với regex và flags
- ✅ Return vs rewrite performance
- ✅ Location matching patterns và priority
- ✅ Try_files directive và use cases
- ✅ Conditional redirects
- ✅ Real-world redirect scenarios
- ✅ Troubleshooting và best practices
Key takeaways:
- Prefer
returnoverrewritefor simple redirects - Use exact matches when possible
- Avoid
ifdirective when alternatives exist - Use
mapfor bulk redirects - Test redirects thoroughly
- Document complex rewrite rules
Next lesson: Nginx với Application Stack - PHP-FPM configuration, Nginx + Node.js, Python (uWSGI/Gunicorn), Docker containers và WebSocket proxying để integrate Nginx với various backend technologies.