nginx redirects
Copy-ready nginx configuration for 301 redirects: single pages, patterns, whole domains, HTTPS and www in a single hop, and hundreds of URLs with map.
return or rewrite?
nginx has no .htaccess. All redirects live in the server configuration, usually in /etc/nginx/nginx.conf or a file under /etc/nginx/conf.d/ or sites-available/. There are two directives that send a redirect:
| Directive | Status codes | When to use |
|---|---|---|
return 301 URL; | Any: 301, 302, 303, 307, 308 | The default choice. Fast, explicit, stops processing immediately. |
rewrite regex replacement permanent; | 301 (permanent) or 302 (redirect) | When you need to transform the path with a regular expression inside a broader location. |
The nginx documentation itself recommends return wherever possible. It doesn't have to evaluate a regex, and it can send 307 and 308, which rewrite can't. Use rewrite only when you really need to build the new path from parts of the old one.
Redirect a single page
An exact-match location is the cleanest way:
location = /old-page {
return 301 https://example.com/new-page/;
}
The = means the location only matches exactly /old-page, not /old-page/ or /old-pages. You can also write a relative target like return 301 /new-page/;. nginx turns it into an absolute URL using the requested host, because absolute_redirect is on by default.
Note that return with a fixed URL drops the query string. To keep it, append $is_args$args:
location = /old-page {
return 301 https://example.com/new-page/$is_args$args;
}
Redirect a folder or pattern
With a regex location and captures:
location ~ ^/blog/\d{4}/(.+)$ {
return 301 https://example.com/articles/$1$is_args$args;
}
The same with rewrite, which you can place directly in the server block:
rewrite ^/blog/\d{4}/(.+)$ https://example.com/articles/$1 permanent;
rewrite appends the original query string automatically. If you want to drop it, end the replacement with a question mark: ... /articles/$1? permanent;.
Keep in mind how nginx picks a location: exact matches (=) first, then the longest prefix; if that prefix isn't marked with ^~, regex locations are checked in the order they appear, and the first matching regex wins. A redirect in a regex location can therefore be shadowed by an earlier regex such as a generic location ~ \.php$.
HTTP to HTTPS and www in a single hop
The cleanest setup uses separate server blocks and no if. Every non-canonical variant goes straight to https://example.com with one redirect:
# 1. All HTTP requests, with and without www
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# 2. HTTPS on www
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
# 3. The canonical host
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
root /var/www/example.com;
}
Two details matter here. The certificate must cover www.example.com too, otherwise browsers show a certificate error before the redirect can happen. And the http2 on; directive exists since nginx 1.25.1; on older versions write listen 443 ssl http2; instead. For the SEO side, see HTTP to HTTPS and www vs. non-www.
$request_uri vs. $uri
Both variables look similar but behave differently in redirects:
$request_uriis the original request line: path plus query string, exactly as the client sent it, still URL-encoded. Perfect for "same path on another host" redirects, because nothing gets lost or changed.$uriis the normalized, decoded path without query string, and it can change during processing (for example after an internal rewrite orindex). Use it for matching, not as a redirect target.
Don't write return 301 https://example.com$uri$is_args$args; when $request_uri does the job. Decoded characters in $uri can produce broken or even unsafe Location headers.
Redirect a whole domain
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name old-domain.com www.old-domain.com;
ssl_certificate /etc/ssl/old-domain.com/fullchain.pem;
ssl_certificate_key /etc/ssl/old-domain.com/privkey.pem;
return 301 https://example.com$request_uri;
}
Keep the certificate for the old domain valid as long as the redirect runs. Links and bookmarks with https://old-domain.com will otherwise fail before they ever reach the redirect. The domain migration checklist covers the rest.
Many redirects with map
Hundreds of location blocks are hard to maintain and slow to evaluate. A map is a hash lookup and scales well. It goes into the http context, outside of any server block:
map $uri $redirect_target {
default "";
/old-page /new-page/;
/pricing.html /pricing/;
/team/jane-doe /about/;
~^/shop/(?<slug>.+)$ /store/$slug;
}
Then use it in your server block:
server {
# ...
if ($redirect_target) {
return 301 $redirect_target$is_args$args;
}
}
Using return inside if is one of the few uses of if that is always safe. Exact entries are looked up first, regex entries (starting with ~) are checked afterwards in order. For long lists, move the entries into a separate file and write include /etc/nginx/redirects.map; inside the map block. If nginx complains that it could not build the map hash, raise map_hash_max_size or map_hash_bucket_size in the http block.
You can generate these entries (and the other snippets on this page) from a list of URLs with the nginx redirect generator.
Trailing slash
Remove the trailing slash from everything except the home page:
rewrite ^/(.+)/$ /$1 permanent;
Only do this if your application actually serves the URLs without slash. nginx itself adds a slash with a 301 when a request hits a directory, and removing it again would loop.
Test and reload safely
Always check the syntax before reloading. A broken configuration will prevent nginx from starting:
sudo nginx -t
sudo systemctl reload nginx
nginx -t parses all included files and reports the file and line of any error. reload (or nginx -s reload) applies the new configuration without dropping open connections, unlike a restart.
Then test the result. Browsers cache 301s, so a browser test after a change is often misleading. Use curl -I http://www.example.com/old-page or enter the URL in the redirect checker, which shows every hop with status code and target. Check each variant (HTTP, www, with query string) and make sure you get exactly one hop. If you see two or more, read redirect chains for how to collapse them.
Frequently asked questions
- What is the difference between return 301 and rewrite permanent?
Both send a 301.
returnsends the response immediately with a URL you define, supports any status code and needs no regex.rewrite ... permanentbuilds the target from a regex match and automatically keeps the query string. Preferreturnunless you need to transform the path.- How do I send a 308 redirect with nginx?
Use
return 308 https://example.com/new/;.rewriteonly knows 301 and 302. A 308 keeps the request method and body, which matters for APIs and forms, see 307 vs. 308.- Do I need to restart nginx after changing redirects?
No, a reload is enough:
sudo nginx -tto check the syntax, thensudo systemctl reload nginx. The reload keeps running connections alive.- Can nginx read my .htaccess redirects?
No. nginx ignores
.htaccessfiles completely. Each rule has to be rewritten as nginx configuration. The .htaccess guide helps you understand what the old rules do, and the nginx generator produces the new ones.
Related guides
-
.htaccess redirects on Apache
Everything you need to redirect pages and whole domains with .htaccess on Apache: mod_alias vs. mod_rewrite, copy-ready rules for the common cases, and how to avoid loops and chains.
-
Redirects on Cloudflare
Cloudflare can answer redirects at the edge before a request ever reaches your server. Here is how Single Redirects, Bulk Redirects and Always Use HTTPS work, and how to keep them from fighting with your origin.
-
Redirects in WordPress
WordPress gives you several ways to redirect a URL: a plugin, a rule in .htaccess or a few lines of PHP. This guide shows when to use which, and how to avoid the typical pitfalls with caching and HTTPS.
-
Redirects in Next.js
Next.js has four places where you can redirect: the config file, middleware, server code in the App Router and the trailingSlash option. Each one sends different status codes by default, so it pays to know which is which.
-
Redirects on Vercel and Netlify
On Vercel and Netlify you don't touch a web server config. Redirects live in a file in your repository or in the dashboard, and the platform's edge network sends them. Here is how both work and where they differ.
-
Redirects on Microsoft IIS
IIS gives you two ways to redirect: the built-in HTTP Redirect feature and the URL Rewrite module. Here is when to use which, with web.config examples you can copy.