Chuyển đến nội dung chính

Lesson 10: Rewrite and Redirects in NGINX

A lesson on Rewrite and Redirects in Nginx — rewrite rules with regex, return vs rewrite directive, location matching patterns (exact, prefix, regex), try_files directive, and conditional redirects. Guide to URL manipulation, SEO-friendly redirects, and best practices for production environments.

🔒 DevSecOps — Lesson 10 Lesson 10: Rewrite and Redirects in NGINX

Nginx from Basics to Advanced

Part 3: Security & Performance

xdev.asia

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 URI
  • last - Stop processing rewrite rules, re-search locations
  • redirect - Return 302 temporary redirect
  • permanent - 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):

  1. Exact match =
  2. Prefix match with ^
  3. Regular expression or * (first match wins)
  4. 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"

curl http://localhost/test123

→ "Prefix ^: /test*" (if ^ exists)

→ "Regex : /test" (if no ^)

curl http://localhost/other

→ "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-page

Follow 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/bash

echo "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

  1. Create redirect từ /old-about đến /about-us
  2. Implement HTTP to HTTPS redirect
  3. Redirect www to non-www
  4. Test với curl

Exercise 2: Regex Rewrites

  1. Rewrite /product/123 → /item/123
  2. Rewrite /blog/2024/12/post → /articles/2024-12-post
  3. Preserve query strings
  4. Test với curl

Exercise 3: Location Matching

  1. Setup exact match cho /
  2. Setup prefix match cho /api/
  3. Setup regex match cho image files
  4. Test priority order

Exercise 4: Try_files

  1. Setup SPA với try_files fallback
  2. Configure WordPress-style try_files
  3. Add custom 404 page
  4. Test với various URLs

Exercise 5: Bulk Redirects

  1. Create map với 10+ redirects
  2. Implement map-based redirect logic
  3. Test all redirects
  4. Measure performance vs individual rewrites

Exercise 6: Complex Scenario

  1. Migrate old site structure to new
  2. Implement mobile detection
  3. Add maintenance mode
  4. Setup proper error pages
  5. 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 return over rewrite for simple redirects
  • Use exact matches when possible
  • Avoid if directive when alternatives exist
  • Use map for 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.