Nginx and Apache Redirects for SEO: Correct Rules Without Chains

Crawvix Editorial Team · Published October 29, 2026 · Last updated October 29, 2026 · 8 min read

Most redirect problems on nginx and Apache come from the same few habits. People reach for a powerful rewrite rule when a one-line redirect would do. They add an HTTPS rule and a www rule separately, so visitors bounce through two hops. They forget that the default status in several directives is a temporary 302. The result works in a browser and still sends search engines a muddled message.

This guide is for site owners and developers who manage their own nginx or Apache configuration. You will write the four redirects almost every site needs: HTTP to HTTPS, one canonical host, moved URLs and removed pages. You will pick the simplest directive for each, set the status code on purpose, and test the result with curl.

Redirects do not rank a page. They tell browsers and crawlers where a URL now lives. Done well, they keep old links useful and avoid duplicate versions of your site.

Before you begin

  • Access: you need to edit the server configuration (nginx server blocks, Apache virtual hosts) and reload the service. On shared hosting you may only have .htaccess; the Apache sections say what changes.
  • Decide the target: write down the one canonical address, such as https://www.example.com. Every other variant must lead there. If you are unsure which host to choose, read www vs non-www.
  • Know your layers: if a CDN, load balancer or application also redirects, one of them is enough. Two layers doing the same job create chains.
  • Risk: moderate. A typo can loop the site. Test the configuration syntax (nginx -t or apachectl configtest) before every reload.
  • Back up: copy the current configuration files and keep a list of the URLs you plan to test.

How redirect status codes work

A redirect is a response with a 3xx status and a Location header. Google groups them by intent. 301 and 308 are permanent, and Google uses them as a signal that the target should be the canonical URL. 302, 303 and 307 are temporary, and Google keeps showing the source page in results because the move is not expected to last. Google also states that server-side redirects are the method it is most likely to interpret correctly, ahead of meta refresh and JavaScript.

Use a permanent status when you are sure the move will not be reversed. Use a temporary one for short outages, tests and campaigns. For the full comparison, see 301 vs 302 redirects.

Step-by-step instructions

Step 1: List the URL variants and decide the one-hop target

Why: a visitor can arrive on four versions of your home page: http://example.com, http://www.example.com, https://example.com and https://www.example.com. Three of them should redirect, and each should reach the canonical page in a single hop.

Action: write the four variants and the final URL. Then plan one redirect per variant, each pointing straight at https://www.example.com/..., not at the next variant.

Confirm: your plan has no variant pointing at another variant.

Step 2: Redirect HTTP and the non-canonical host in nginx

Why: nginx's own guidance for host redirects is a separate server block that returns a 301, not an if test inside the main block. The documentation calls the if plus rewrite pattern a "wrong, cumbersome, and ineffective way".

Action: use return 301 with $request_uri, which carries the path and query string.

# All HTTP traffic, either host
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://www.example.com$request_uri;
}

# HTTPS on the non-canonical host
server {
    listen 443 ssl;
    server_name example.com;
    # ssl_certificate and ssl_certificate_key go here
    return 301 https://www.example.com$request_uri;
}

# The real site
server {
    listen 443 ssl;
    server_name www.example.com;
    # ssl settings and site config
}

Keep two details in mind. return with a code of 301, 302, 303, 307 or 308 treats its second argument as a redirect URL. A URL given as the only argument, with no code, creates a temporary 302, so always write the code. The second HTTPS block needs a valid certificate for the non-canonical name, or visitors see a certificate error before the redirect can happen.

Confirm: nginx -t passes, and after reload each of the four variants returns 301 with a Location that is already the final URL.

Step 3: Redirect HTTP and the non-canonical host in Apache

Why: the Apache documentation says mod_rewrite should be a last resort and prefers Redirect from mod_alias in a dedicated virtual host, which it calls the cleanest approach for forcing HTTPS.

Action: one virtual host per job.

# HTTP, both hosts
<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

# HTTPS on the non-canonical host
<VirtualHost *:443>
    ServerName example.com
    # SSL directives go here
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

# The real site
<VirtualHost *:443>
    ServerName www.example.com
    # SSL directives and site config
</VirtualHost>

Redirect appends the rest of the path to the target and keeps GET query strings. The default status is 302, so you must write permanent (301) or a numeric code. POST requests are discarded when redirected.

If you only have .htaccess: use mod_rewrite, with the explicit status and the L flag.

RewriteEngine On
RewriteCond "%{HTTPS}" !=on
RewriteRule "^(.*)" "https://www.example.com/$1" [R=301,L]

The R flag alone issues a 302. Use R=301, and add L so no later rule runs on the half-rewritten URL. Behind a load balancer or SSL terminator, %{HTTPS} always reads off. Test X-Forwarded-Proto instead, and only if your proxy overwrites that header on every request. Otherwise you can create a loop.

Confirm: apachectl configtest reports a valid syntax, and the four variants each return 301 once.

Step 4: Redirect moved URLs

Why: when a page moves, the old address should lead to the new one so links and bookmarks keep working.

Action in nginx: use rewrite ... permanent for patterns. The permanent flag returns a 301 and the redirect flag returns a 302. A replacement that starts with http://, https:// or $scheme also ends processing with a redirect. With no flag, nginx rewrites internally and the visitor sees no redirect.

server {
    # inside the www.example.com HTTPS block
    rewrite ^/old-blog/(.*)$ /blog/$1 permanent;
    rewrite ^/contact-us$ /contact permanent;
}

Action in Apache: use Redirect for fixed paths and RedirectMatch for patterns. RedirectMatch does not append the remaining path on its own, so capture it.

Redirect permanent "/contact-us" "https://www.example.com/contact"
RedirectMatch permanent "^/old-blog/(.*)$" "https://www.example.com/blog/$1"

Point each old URL at its closest replacement, not at the home page. If you are moving a whole site, follow the website migration checklist as well.

Confirm: each old URL returns 301 once and the destination returns 200.

Step 5: Handle removed pages honestly

Why: a page with no replacement should say so. Redirecting everything to the home page hides the problem.

Action: if a page is gone for good, return an error status rather than a redirect. Apache supports Redirect gone "/old-promo", which returns 410. In nginx, return 410; inside a matching location does the same. For the difference between 404 and 410, see 410 vs 404 for removed pages.

Confirm: curl -sI on the removed URL returns the status you chose.

Step 6: Watch the query string and trailing characters

Why: query strings carry tracking and filter values you may want to keep or drop.

Action: in nginx, a rewrite that adds new arguments appends the original arguments after them; end the replacement with ? to drop the original query. return 301 ...$request_uri keeps everything. In Apache, Redirect keeps GET query strings. In mod_rewrite, a query string in the replacement replaces the original unless you add QSA, and QSD discards it. Decide per rule instead of accepting defaults.

Confirm: request a URL with ?a=1 and check the Location header.

Step 7: Test every hop

Action: run one curl per variant and per moved URL.

curl -sI http://example.com/page
curl -sI http://www.example.com/page
curl -sI https://example.com/page
curl -sIL https://www.example.com/old-blog/post-1
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://example.com/

-L follows redirects and shows each response, so you can count the hops. Anything beyond one redirect before the final 200 is worth fixing. See redirect chains and loops.

Confirm: every variant reaches the canonical 200 page with at most one redirect.

Final verification checklist

  • All four HTTP/HTTPS and www/non-www variants end at one canonical URL.
  • Each starting variant needs one redirect, not two.
  • Every redirect that is meant to last returns 301 (or 308).
  • Moved URLs go to their closest equivalent page, and that page returns 200.
  • Removed pages return 404 or 410, not a redirect to the home page.
  • Query strings are kept or dropped on purpose.
  • No loop appears with curl -L.
  • The server configuration passes its syntax test and redirects live in one layer only.

Common mistakes to avoid

  • Relying on defaults. Apache Redirect, the mod_rewrite R flag and a bare nginx return URL all give a 302. Write the status.
  • Separate HTTPS and www rules. They cause two hops. Redirect straight to the final address.
  • Using if for host checks in nginx. Use separate server blocks.
  • Forgetting L in Apache rewrite rules. Later rules can modify the redirect target.
  • Trusting %{HTTPS} behind a proxy. It reads off there. Test the forwarded protocol header you control.
  • Redirecting every dead URL to the home page. Use the closest page or a real 404/410.
  • Redirecting from two places. A CDN rule plus a server rule is how loops start.
  • Skipping a certificate for the redirecting host. The redirect never arrives if the TLS handshake fails.

Frequently asked questions

Which is faster, nginx return or rewrite?

This guide does not cite a benchmark, so treat speed claims with care. return is simpler to read and test, and it is what nginx's own host-redirect example uses. Choose it when you do not need pattern matching.

Should I use 301 or 308?

Google treats both as permanent. 301 is the long-standing choice and every server directive here supports it. 308 also forbids changing the request method, which matters for non-GET requests.

Can I keep redirects in .htaccess?

Yes, and sometimes you must. Apache's documentation prefers Redirect in virtual-host configuration for clarity, and treats mod_rewrite as the fallback when you only have .htaccess.

How long should I keep redirects?

Google's redirect documentation does not give a duration. Keep them for as long as the old URLs receive requests or links, and recheck before you remove any.

Official references


Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 9 Platforms guides