URL Rewrites and Redirects
Introduction
Sometimes URLs change—marketing renames a path, you drop .html suffixes, or force www to non-www. Nginx handles this with return redirects and rewrite. This chapter shows when to use each, how to avoid redirect loops, and common patterns for canonical hostnames.
Prerequisites
return for Redirects (Preferred for Simple Cases)
Permanent (301)
location /old-page {
return 301 /new-page;
}Temporary (302)
location /maintenance {
return 302 /status.html;
}Full URL redirect
return 301 https://example.com$request_uri;Code explanation:
301— browsers and search engines cache the new URLreturnstops processing—fast and clear
HTTP → HTTPS (Again)
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}Pick one canonical host (example.com vs www.example.com).
www to non-www (or Reverse)
Certbot can issue cert covering both names with -d example.com -d www.example.com.
rewrite Directive
rewrite ^/blog/(.*)$ /news/$1 permanent;Flags:
| Flag | Effect |
|---|---|
permanent | 301 redirect |
redirect | 302 redirect |
last | Restart location search with new URI (internal) |
break | Stop rewrite processing in current location |
Internal rewrite (no browser redirect):
location / {
rewrite ^/user/(\d+)$ /profile?id=$1 last;
}Code explanation:
- Browser still shows
/user/123 - Nginx internally reprocesses as
/profile?id=123 - Backend must understand rewritten URI
Warning
Redirect Loops
rewrite or return pointing to a URL that triggers the same rule again causes too many redirects. Test with:
curl -I -L http://example.com/old-pagerewrite vs return
| Use | Tool |
|---|---|
| Simple redirect to new path | return 301 ... |
| Complex pattern capture | rewrite ... permanent |
| Internal path change only | rewrite ... last or break |
| HTTPS / canonical host | return 301 https://... |
Prefer return when a redirect is all you need—easier to read and slightly faster.
Trailing Slash Normalization
Add slash to directories:
rewrite ^([^.]*[^/])$ $1/ permanent;Or per-location:
location /docs {
return 301 /docs/;
}Trailing slash rules interact with proxy_pass—test API paths carefully.
Remove .html Extension (Legacy Sites)
location / {
try_files $uri $uri.html $uri/ =404;
}Or external redirect:
rewrite ^/(.*)\.html$ /$1 permanent;Map-Based Redirects (Many URLs)
map $request_uri $new_uri {
default "";
/old-a /new-a;
/old-b /new-b;
}
server {
if ($new_uri != "") {
return 301 $new_uri;
}
}Warning
if Is Evil in Nginx
Small if for redirects is common; complex logic in if breaks unexpectedly. Prefer map or separate location blocks when rules grow.
Example: Legacy Path Migration
FAQ
301 vs 302?
301 permanent (SEO passes link equity). 302 temporary (A/B tests, short moves).
Can rewrite proxy to backend?
Use rewrite ... break then proxy_pass in same block—advanced; easier to fix backend routes when possible.
Query string preserved?
return 301 /new$request_uri; includes path + query. rewrite with $1 may drop query unless you append $is_args$args.
How many redirects are OK?
One hop ideal; chain hurts performance and SEO—consolidate rules.
Certbot redirect vs my return?
Do not duplicate HTTP→HTTPS blocks—keep Certbot’s or yours, not both fighting.
Log redirects?
Access log shows status 301/302 and target—use for verifying migration traffic.