Bài 10: Rewrite và Redirects trong NGINX

Bài học về Rewrite và Redirects trong Nginx - rewrite rules với regex, return vs rewrite directive, location matching patterns (exact, prefix, regex), try_files directive và conditional redirects. Hướng dẫn URL manipulation, SEO-friendly redirects và best practices cho production environments.

12 min read
Bài 10: Rewrite và Redirects trong NGINX

1. Rewrite Rules với 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. Bài tập Thực hành

Bài tập 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

Bài tập 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

Bài tập 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

Bài tập 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

Bài tập 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

Bài tập 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

Tổng kết

Trong bài này, bạn đã học:

  • ✅ 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

Bài tiếp theo: 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.

Nginx Rewrite Redirect Regex URLRewriting LocationMatching TryFiles URLManipulation SEO 301Redirect

Đánh dấu hoàn thành (Bài 10: Rewrite và Redirects trong NGINX)