Nginx Redirect Configuration Guide
Complete guide to Nginx redirects: return vs rewrite, server and location block redirects, regex patterns, map directive for bulk redirects, HTTPS enforcement, and debugging tips.
Nginx handles redirects differently from Apache. There is no .htaccess file. All redirect configuration goes into Nginx config files, and changes require a reload. The tradeoff is better performance and clearer syntax. For the full overview of all redirect types, see our HTTP Redirect Guide.
This guide covers the two redirect mechanisms in Nginx, when to use each, and the patterns you will need for common redirect scenarios.
return vs rewrite
Nginx has two directives for redirects. They serve different purposes.
return
return sends a response immediately with a status code and optional URL. It is the fastest option because Nginx does not need to evaluate a regex.
# 301 redirect
return 301 https://example.com/new-page;
# 302 redirect
return 302 https://example.com/temporary-page;
Use return when:
- You are redirecting an entire server block (all traffic)
- The target URL is static or uses only built-in variables
- You want the best possible performance
rewrite
rewrite matches the request URI against a regex and replaces it. It is more flexible but slower because of the regex evaluation.
# Redirect with regex capture
rewrite ^/old-blog/(.*)$ https://example.com/blog/$1 permanent;
# permanent = 301, redirect = 302
rewrite ^/temp/(.*)$ https://example.com/current/$1 redirect;
Use rewrite when:
- You need regex pattern matching
- You need to capture parts of the URL and use them in the target
- The redirect logic depends on the URL structure
Bottom line: Use return when you can, rewrite when you must.
Server Block Redirects
A server block redirect handles all requests for a particular domain or port. This is the standard approach for domain-level redirects like HTTPS enforcement or www canonicalization.
Redirect all HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
$host preserves the original hostname. $request_uri preserves the full path and query string. This is the correct way to enforce HTTPS in Nginx. For a deeper look at HTTPS migration, see HTTP to HTTPS Redirect Guide.
Redirect www to non-www
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
return 301 https://example.com$request_uri;
}
Redirect non-www to www
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
return 301 https://www.example.com$request_uri;
}
Combine HTTPS + www in one hop
Do not stack a port 80 redirect with a separate www redirect. That creates a redirect chain. Handle both in one server block:
# Catch all non-canonical variations
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
return 301 https://www.example.com$request_uri;
}
# The canonical server block
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# ... your site configuration
}
Every non-canonical request reaches the canonical URL in exactly one redirect.
Location Block Redirects
Location blocks handle redirects for specific URL paths within a server block.
Exact match redirect
location = /old-page {
return 301 https://example.com/new-page;
}
The = prefix means exact match only. /old-page matches, /old-page/sub does not.
Prefix match redirect
location /old-section/ {
return 301 https://example.com/new-section/;
}
Without =, this matches any URL starting with /old-section/. But there is a problem: the path after /old-section/ is lost. For preserving the subpath, use rewrite:
location /old-section/ {
rewrite ^/old-section/(.*)$ https://example.com/new-section/$1 permanent;
}
Regex location redirect
# Match URLs ending in .html and strip the extension
location ~ \.html$ {
rewrite ^(.+)\.html$ $1 permanent;
}
The ~ prefix enables case-sensitive regex matching. Use ~* for case-insensitive.
Regex Patterns in Nginx
Nginx uses PCRE (Perl Compatible Regular Expressions). Here are patterns you will use often:
# Capture everything after a path prefix
rewrite ^/blog/(.*)$ /articles/$1 permanent;
# Capture a slug between slashes
rewrite ^/category/([^/]+)/([^/]+)$ /shop/$1/$2 permanent;
# Match a numeric ID
rewrite ^/post/([0-9]+)$ /articles/$1 permanent;
# Strip trailing slash
rewrite ^/(.*)/$ /$1 permanent;
# Add trailing slash
rewrite ^(.*[^/])$ $1/ permanent;
Capture groups use $1, $2, etc. in the replacement string.
The map Directive for Bulk Redirects
When you have dozens or hundreds of one-to-one URL redirects, listing individual location or rewrite blocks gets messy. The map directive creates a lookup table that scales cleanly.
# Define the map in the http block (outside server blocks)
map $request_uri $redirect_target {
default "";
/old-page-1 /new-page-1;
/old-page-2 /new-page-2;
/old-page-3 /new-page-3;
/legacy/about /about-us;
/legacy/team /our-team;
}
server {
listen 443 ssl;
server_name example.com;
# Apply the redirects
if ($redirect_target != "") {
return 301 $redirect_target;
}
# ... rest of your config
}
The map is evaluated lazily (only when the variable is used) and is implemented as a hash table, so it performs well even with thousands of entries.
Map with regex
map $request_uri $redirect_target {
default "";
~^/old-blog/(?<post>.+)$ /blog/$post;
~^/docs/v1/(?<path>.+)$ /docs/v2/$path;
}
The ~ prefix in a map value enables regex. Named captures (?<post>) make the pattern more readable than $1.
Query String Redirects
Nginx's $request_uri includes the query string. But rewrite only matches against $uri (no query string). To redirect based on query parameters, use if with $arg_ variables or $args:
# Redirect /search?q=term to /find?query=term
if ($arg_q) {
return 301 /find?query=$arg_q;
}
# Strip all query parameters during redirect
location = /old-page {
return 301 https://example.com/new-page;
}
# Note: return does not append query strings by default
# rewrite does append them unless you add ? at the end:
rewrite ^/old-page$ /new-page? permanent;
The trailing ? in a rewrite target discards the original query string. Without it, Nginx appends the original query string to the target.
Common Nginx Redirect Mistakes
Using if incorrectly
The if directive in Nginx is not like if in a programming language. Inside a location block, if creates an implicit nested location, which can cause unexpected behavior. The Nginx community calls this "if is evil."
# PROBLEMATIC: if inside location
location / {
if ($host = "old.example.com") {
return 301 https://new.example.com$request_uri;
}
# Other directives here may not work as expected
}
# BETTER: Separate server block
server {
listen 443 ssl;
server_name old.example.com;
return 301 https://new.example.com$request_uri;
}
Use if sparingly. Prefer separate server blocks for domain-level redirects and map for conditional logic.
Forgetting to handle both HTTP and HTTPS
If you set up a redirect in your HTTPS server block but forget the HTTP server block, users who visit http://example.com/old-page will not be redirected.
# This only handles HTTPS requests to old-page
server {
listen 443 ssl;
server_name example.com;
location = /old-page {
return 301 /new-page;
}
}
# You also need to handle HTTP
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
The HTTP server block sends everything to HTTPS, where the page-level redirect handles the rest.
Redirect loops
# LOOP: /page redirects to /page/
# Then /page/ redirects to /page (if you also strip trailing slashes)
rewrite ^(.+)/$ $1 permanent; # strip trailing slash
rewrite ^(.*[^/])$ $1/ permanent; # add trailing slash
Pick one convention. Do not try to both add and strip trailing slashes.
Testing Nginx Redirects
Test config syntax before reloading
nginx -t
Always run this before reloading. A syntax error in your config will prevent Nginx from restarting, which means downtime.
Test with curl
# Check redirect status and location
curl -I https://example.com/old-page
# Follow the full chain
curl -ILs https://example.com/old-page | grep -E "HTTP/|Location:"
Check redirect chains
A single redirect should reach the final destination. If you see multiple hops, you have a redirect chain that needs consolidation.
reload vs restart
After changing Nginx config, use nginx -s reload, not systemctl restart nginx. A reload gracefully applies the new config without dropping active connections. A restart kills all connections and starts fresh. In production, always reload.
Reload vs Restart
# Preferred: graceful reload (no downtime)
nginx -s reload
# or
systemctl reload nginx
# Avoid in production: full restart (drops connections)
systemctl restart nginx
reload re-reads the config and applies it to new connections while existing connections finish with the old config. restart stops Nginx entirely and starts it again. There is no reason to restart for a config change unless the binary itself was updated.
Performance Notes
Nginx redirects are fast. A return directive adds negligible overhead. Even regex-based rewrite rules are fast in practice because Nginx compiles the regex once at config load, not per request.
That said:
- Prefer
returnoverrewritewhen possible - Use exact match (
location =) over regex when the URL is known - Use
mapfor large redirect lists rather than long chains ofifblocks - Avoid nested
ifdirectives
For most sites, redirect performance is not the bottleneck. Correctness matters more than speed here.
Trace your redirect chains
Paste any URL and see every hop, status code, and header instantly.
References
- Nginx Documentation, "Module ngx_http_rewrite_module," https://nginx.org/en/docs/http/ngx_http_rewrite_module.html
- Nginx Documentation, "map directive," https://nginx.org/en/docs/http/ngx_http_map_module.html
- Nginx Wiki, "If Is Evil," https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
Related Articles
Never miss a broken redirect
Trace redirect chains and detect issues before they affect your users and SEO. Free instant tracing.
Try Redirect Tracer