← Back to blog

Nginx Redirect Rules: 301s, HTTPS and www Examples

October 19, 2026
Nginx Redirect Rules: 301s, HTTPS and www Examples

The short version: for nearly every redirect in nginx, use return 301 inside a dedicated server or location block. Reach for rewrite only when the new URL has to be built from a pattern in the old one. This guide gives you tested-in-practice snippets for the cases people hit most: a single moved page, a whole moved path, HTTP to HTTPS, and www to non-www. It also covers the mistakes that cause redirect loops and rules that never fire.

Pingfloat
pingfloat.com
Check Your Site After Redirect Changes
Use Pingfloat’s uptime monitoring and SSL checks to review site availability and certificate status after updating Nginx redirects.
Explore Pingfloat tools

TL;DR

  • Use return 301 https://example.com$request_uri; for permanent redirects. It is simpler and cheaper than rewrite, and it is what the nginx engineers recommend for simple cases.
  • Put HTTP to HTTPS and www to non-www redirects in their own server blocks. Avoid if where a separate block will do.
  • rewrite only sends a redirect to the browser when you add the permanent or redirect flag, or when the target starts with http:// or https://.
  • Behind a CDN or load balancer, pass X-Forwarded-Proto correctly, or you will get an infinite redirect loop.
  • Always test after every change with nginx -t, then check the real response headers.

Table of Contents

return or rewrite: which one to use

nginx gives you two ways to redirect, both from the rewrite module. They look similar and behave differently.

return stops processing immediately and sends the status code and URL you give it. There is no regular expression to evaluate and nothing after it runs. Practitioners prefer it for simple redirects because it is more efficient and easier to reason about.

rewrite matches the request URI against a PCRE regular expression and substitutes a new one. Rewrite directives are processed in order, and flags control what happens next:

FlagWhat it does
lastStops processing the current rewrite directives and searches for a new location matching the changed URI
breakStops processing the current rewrite directives, without a new location search
redirectSends a temporary 302 redirect
permanentSends a permanent 301 redirect

The flag list comes straight from the nginx rewrite module documentation.

Here is the rule of thumb we use:

  • You know the exact old URL and the exact new URL: return 301.
  • The whole host or a whole path prefix moves and the rest of the URL stays the same: return 301 with $request_uri or a captured variable.
  • The new URL has to be computed from pieces of the old one (for example /blog/2019/05/post-name to /posts/post-name): rewrite ... permanent.

The rewrite surprise

A common trap: you write rewrite ^/old$ /new; and expect the browser to land on /new. It will not. Without a redirect flag or a full http:// or https:// URL as the replacement, rewrite only changes the URI internally, and the visitor's address bar stays on /old. If you want the client to see a redirect, say so:

rewrite ^/old$ /new permanent;

Redirect one URL to another

The most common request. Inside your HTTPS server block:

location = /old-page {
    return 301 /new-page;
}

The = makes it an exact match, so /old-page-2 and /old-page/extra are left alone. Without =, a plain prefix location /old-page would catch all of those too.

To send a page to a different domain, give a full URL:

location = /pricing {
    return 301 https://example.org/pricing;
}

Redirect a whole path, keeping the rest of the URL

When a directory moves, capture the remainder with a regular expression:

location ~ ^/docs/(.*)$ {
    return 301 /help/$1$is_args$args;
}

/docs/setup?lang=en becomes /help/setup?lang=en. The $is_args$args pair carries the query string along. Note that $1 already excludes the query string, because nginx matches locations against the path only.

If the URL needs real restructuring, use rewrite:

rewrite ^/blog/\d{4}/\d{2}/(.+)$ /posts/$1 permanent;

This drops the year and month segments from old dated blog URLs.

Redirect HTTP to HTTPS in nginx

The standard approach is a separate server block listening on port 80 that returns a 301 to the HTTPS version, as shown in this widely used tutorial:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

Your HTTPS block then handles only secure traffic:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;

    # site config
}

This keeps the rules readable. nginx's own documentation on how a request is processed explains that nginx first picks the server by the Host header, so separating servers by purpose means you always know which block a request lands in.

Sharing one redirect block across many domains

If you host several sites, hard-coding the host would send everyone to one domain. Use $host instead:

server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

The same tutorial notes that _ is just an invalid placeholder name and does not create a catch-all; the default_server parameter on listen is what makes the block the fallback.

301 or 308?

A 301 is fine for page loads, but some clients change a POST into a GET when following it. If you redirect form submissions or API calls, 308 Permanent Redirect keeps the method and body. The same tutorial lists return 308 https://example.com$request_uri; for exactly that case.

Redirect www to non-www (or the reverse)

Pick one canonical hostname and send the other to it. Search engines treat www.example.com and example.com as different addresses, so an unredirected duplicate splits your signals.

The cleanest version uses one block per hostname. For www to non-www over HTTPS:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    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;
}

You still need a certificate that covers the www name, because the browser completes the TLS handshake before it ever sees your redirect. If a visitor gets a certificate warning on the www address, the redirect never gets a chance to run. Our SSL Checker shows which hostnames a certificate covers and when it expires.

You will find tutorials that put both names in one block and use if ($host = www.example.com). It works, but the nginx team warns that if is hard to use correctly and can behave unexpectedly in some contexts. A second server block says the same thing with no surprises.

To go the other way (non-www to www), swap the names: the block with server_name example.com; returns a 301 to https://www.example.com$request_uri.

The complete picture: four blocks

A tidy production setup for one site, with HTTP and www all redirecting to one canonical URL, looks like this:

# 1. All plain HTTP goes to the canonical HTTPS URL
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# 2. HTTPS www goes to HTTPS non-www
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    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 real site
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    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;

    location = /old-page {
        return 301 /new-page;
    }
}

The first block sends http://www.example.com/x straight to https://example.com/x in a single hop. Chained redirects (HTTP to HTTPS, then www to non-www) work, but each extra hop adds latency and is one more place for something to go wrong.

If you would rather not hand-write these, our free Redirect Rule Generator produces nginx and Apache rules from the old and new URLs.

301 or 302: choosing the status code

Use 301 when the move is permanent. A 301 tells search engines the content has moved for good, and the Rackspace nginx guide notes that this is what passes link equity to the new location, while a temporary 302 does not. Use 302 (or 307) for short-lived moves such as maintenance pages or A/B tests, so the old URL stays the one that is indexed.

Permanent and temporary redirect differences

For how this plays out in search results, see our 301 vs 302 guide for developers and the reference pages for 301 Moved Permanently, 302 Found and 307 Temporary Redirect. If you are also planning your new URL structure, this guide to SEO-friendly URLs is a useful companion.

Why your redirect does not work

Most redirect bugs fall into three groups.

1. A different location wins

nginx matches literal prefix locations first, then evaluates regular expression locations in the order they appear in the file, as described in the request processing docs. If you expect a regex rule to take precedence and it is shadowed by another location, it will never run. Use exact matches (location = /path) for single pages, and read the order of your regex locations from top to bottom.

2. The redirect loop behind a proxy

If TLS ends at a CDN or load balancer, your backend sees plain HTTP. An application that forces HTTPS then redirects every request, forever. The usual cause is a missing X-Forwarded-Proto header, which comes up repeatedly on the nginx community forum in setups where nginx terminates SSL and proxies to a backend over HTTP. Pass the header and make sure the application is configured to trust it:

location / {
    proxy_pass http://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Also check that you are not redirecting HTTP to HTTPS in both nginx and the application, and that the CDN's "always use HTTPS" setting does not fight your origin rules.

3. A rewrite that never redirects

Covered above: no permanent flag and no full URL means an internal rewrite only.

Test every change

Validate the syntax, then reload:

sudo nginx -t && sudo systemctl reload nginx

Then look at what the server actually returns, without following the redirect:

curl -I http://example.com/old-page

You want a 301 status and a Location header with the exact target. Test the HTTP, HTTPS, www and non-www variants, and a URL with a query string. Our short guide, 3 quick checks to test redirects, walks through this routine. If a redirect target starts returning errors, the reference pages for 500 Internal Server Error and 503 Service Unavailable help you narrow down the cause.

Our perspective: treat redirects as infrastructure

We build monitoring and diagnostic tools for developers and IT teams, and redirects are a recurring theme in the problems people bring to those tools. The pattern is almost always the same: a redirect is written once, tested once in a browser, and never looked at again.

Our perspective: treat redirects as infrastructure — overview diagram

That is how a certificate expires on the www hostname nobody visits directly, or how a leftover rule from a migration quietly sends traffic through three hops. A redirect is part of your site's availability, not a footnote to it.

Our advice is practical. Keep redirects in version control with the rest of your nginx config. Prefer one server block per purpose. Re-check the canonical hostname and certificate coverage whenever you change DNS or hosting. And monitor the URL real visitors type, not just the final destination, so a broken rule shows up as an alert rather than a support ticket.

— Andreas

Keep your redirects honest with Pingfloat

Once your rules are live, you can check them without installing anything. Our free tools let you monitor uptime for the URL your visitors actually use, verify the certificate with the SSL Checker, and generate nginx or Apache rules with the Redirect Rule Generator. The rest of the suite, from traceroute to header inspection, is on the Pingfloat home page.

FAQ

What is the nginx 301 redirect syntax?

Inside a server or location block, write return 301 https://example.com$request_uri;. The status code comes first, then the target URL. $request_uri keeps the original path and query string, so deep links survive the move.

Should I use rewrite or return for redirects?

Use return for simple redirects. It stops processing at once and does not evaluate a regular expression. Use rewrite ... permanent only when the new URL must be built from parts of the old one.

Does a 301 redirect hurt SEO?

A correctly configured 301 is the normal way to move a page and passes link equity to the new URL. Trouble comes from redirect loops, long chains of hops, or using a temporary 302 for a permanent move. Redirect each old URL to its closest equivalent, not everything to the home page.

Why do I get too many redirects after adding an HTTPS rule?

This usually happens when a CDN or load balancer terminates TLS and your backend only sees HTTP. The backend, or nginx itself, then redirects to HTTPS again on every request. Pass X-Forwarded-Proto $scheme to the backend and configure the application to trust it.

Do I need nginx -t before reloading?

Yes. It checks syntax and catches typos before they take your site down. Run it, then reload, then confirm the result with curl -I.

Sources