Redirects look simple until they are not. I have seen one misplaced 301 break a migration plan for days because browsers cached it so aggressively that testers kept insisting the “old bug” was still there after the config had already changed. I have also seen 302 used for a permanent site move, which delayed search engines from consolidating signals and kept internal links messy far longer than necessary.
So when someone asks “301 vs 302 redirect nginx apache,” I do not think in abstract SEO terms first. I think in operational terms: what exactly is changing, is the redirect temporary or permanent, do I need the request method preserved, and how do I verify I am not creating a chain or loop?
If you use redirects mostly for HTTPS enforcement or canonical host cleanup, pair this guide with setting up SSL in Nginx and Apache mod_rewrite redirects and HTTPS enforcement. Redirects are simple to write but easy to get subtly wrong.
The semantic difference between 301 and 302
At a high level:
- 301 Moved Permanently means the resource has a new permanent location.
- 302 Found means the redirect is temporary.
That seems obvious, but the consequences are real.
When I use 301
I use 301 when I know the old URL should no longer be used and I want clients, crawlers, and caches to update their behavior over time.
Typical cases:
- HTTP to HTTPS enforcement
- old domain to new domain migration
wwwto non-wwwcanonicalization, or the reverse- old article slug permanently replaced
- trailing slash normalization where the canonical form is fixed
When I use 302
I use 302 when the change is temporary or conditional.
Typical cases:
- short-term maintenance reroute
- A/B testing or campaign routing
- temporary language or region flow
- incomplete migration where the old URL may return later
- application logic sending users to a temporary holding page
The key question I ask is simple: if a client remembers this forever, would that be correct? If yes, 301 is probably right. If not, use a temporary redirect.
SEO implications of choosing the wrong redirect type
People often overcomplicate this part. The operational summary is enough.
- A 301 strongly signals permanent replacement and usually passes ranking signals more directly over time.
- A 302 says “do not assume this is permanent.”
If you use 302 for a permanent move, search engines may keep treating the old URL as canonical longer than you want.
If you use 301 for a temporary reroute, browsers and caches may hang onto it, making rollback frustrating.
That is why I do not casually switch 302 to 301 during active migrations until I am sure the target is stable.
307 and 308: the method-preserving versions
This matters more than many guides admit.
Historically, some clients changed POST requests into GET after 301 or 302 redirects. Modern clients behave better, but the HTTP specs introduced 307 Temporary Redirect and 308 Permanent Redirect to preserve the original request method explicitly.
I use them when method preservation matters.
- 307: temporary and method-preserving
- 308: permanent and method-preserving
Examples where I consider 307/308:
- APIs receiving POST or PUT requests
- login or form submission flows
- webhook endpoints being moved
For ordinary browser navigation from page to page, 301 and 302 are usually still fine. For APIs, I think harder before using them.
Nginx: return vs rewrite
In Nginx, I prefer return for straightforward redirects. It is simpler, faster to read, and harder to misuse.
Simple 301 with return
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
This is my default for HTTP to HTTPS plus canonical host cleanup if the pattern is simple.
Simple 302 with return
location /promo {
return 302 /seasonal-offer;
}
When I use rewrite
I use rewrite when I need pattern matching and path transformation.
rewrite ^/old-section/(.*)$ /new-section/$1 permanent;
Or temporary:
rewrite ^/beta/(.*)$ /preview/$1 redirect;
In Nginx, permanent maps to 301 and redirect maps to 302.
For simple one-to-one changes, return is usually the cleaner option. A deprecated habit is writing every redirect as a rewrite rule when a one-line return would be clearer.
Apache: Redirect vs RewriteRule
Apache gives you similar choices.
Simple redirects with Redirect
If I do not need regex matching, I prefer Redirect from mod_alias because it is easier to read.
Permanent example:
Redirect 301 /old-page /new-page
Temporary example:
Redirect 302 /campaign /campaign-2026
Flexible rules with mod_rewrite
Use RewriteRule when you need patterns, conditions, or host-based logic.
Example HTTP to HTTPS:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
Example old path to new path:
RewriteEngine On
RewriteRule ^old-section/(.*)$ /new-section/$1 [R=301,L]
Validate config safely:
sudo apachectl configtest
For a deeper Apache rewrite workflow, Apache mod_rewrite redirects and HTTPS enforcement is still the detailed guide I point people to.
Common redirect patterns I configure
HTTP to HTTPS
This is the classic case and a good use of 301 once TLS is in place.
Nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Apache
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
If you are still building the TLS side, Let’s Encrypt with Certbot and Apache SSL configuration with modern settings are the references I usually combine with redirect work.
www to non-www
Nginx
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
Apache
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
Pick one canonical host and stick with it consistently.
Trailing slash normalization
This is a place where I try to be conservative, because apps and frameworks have their own routing opinions.
Nginx example: remove trailing slash except root
location / {
rewrite ^(.+)/$ $1 permanent;
}
I test rules like that very carefully because it can break paths where the trailing slash is meaningful.
Apache example
RewriteEngine On
RewriteCond %{REQUEST_URI} .+/$
RewriteRule ^(.+)/$ /$1 [R=301,L]
Only use this if your application routing actually expects it.
Redirect chains and why I work to avoid them
A redirect chain is when one redirect leads to another and maybe another after that. Example:
http://www.example.com/page- redirects to
https://www.example.com/page - redirects to
https://example.com/page - redirects to
https://example.com/new-page
That adds latency, complicates caching, and makes debugging harder.
I prefer collapsing that into one redirect whenever possible.
How I detect chains
Use curl:
curl -I -L -o /dev/null -sS -w 'redirects=%{num_redirects} final=%{url_effective}\n' http://www.example.com/page
Or inspect each hop manually:
curl -I http://www.example.com/page
curl -I https://www.example.com/page
curl -I https://example.com/page
If you are cleaning up related server behavior, Nginx reverse proxy with SSL termination often matters because upstream or app-level redirects may be mixing with edge redirects.
How browsers cache 301 redirects
This is one of the more annoying real-world behaviors. Browsers can cache 301s aggressively, which means after you change the server config, your own browser may keep following the old redirect path.
That is why I test with curl -I and sometimes a fresh browser profile when validating redirect changes.
Useful commands:
curl -I https://example.com/old-page
curl -I -L https://example.com/old-page
If I need to test like a new client, curl is often more trustworthy than my browser history.
This is also why I sometimes deploy a 302 first during a migration rehearsal, validate all target behavior, and only switch to 301 once I am sure I will not need quick rollback.
Testing redirects properly
I validate syntax first, behavior second.
Nginx validation
sudo nginx -t
sudo systemctl reload nginx
Apache validation
sudo apachectl configtest
sudo systemctl reload apache2
Curl checks
Check headers only:
curl -I http://example.com/
Follow redirects and show final destination:
curl -I -L http://example.com/
Count hops and total time:
curl -o /dev/null -sS -w 'code=%{http_code} redirects=%{num_redirects} total=%{time_total}\n' -L http://example.com/
For method-sensitive testing, I verify with POST when relevant:
curl -X POST -I http://api.example.com/endpoint
If method preservation matters, 307 or 308 may be the right answer.
Avoiding loops when both the app and the web server want to redirect
One of the more frustrating redirect bugs happens when the application and the web server both try to enforce canonical URLs. I have seen this with frameworks that force HTTPS based on proxy headers, while Nginx also redirects HTTP to HTTPS, and a load balancer sits in front terminating TLS. If the proxy headers are incomplete, the app may think the request is still plain HTTP and keep redirecting forever.
That is why I validate forwarded headers whenever redirects do not make sense:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Then I test both the edge URL and the direct upstream path if possible. If the edge gives multiple hops but the direct app URL does not, the redirect logic is split across layers in a bad way. My preference is to keep canonical redirect behavior at the edge when possible and let the application merely respect the forwarded scheme and host.
Redirect design for migrations and staged rollouts
I rarely switch a large site migration straight to blanket 301 rules without rehearsal. A staged rollout is safer.
A pattern I use looks like this:
- map old URLs to new destinations
- test with 302 first on a controlled subset if rollback risk is high
- validate analytics, cache behavior, and final targets
- switch to 301 or 308 when the mapping is confirmed
For content-heavy sites, I also review whether redirect rules preserve the path and query string correctly. A homepage redirect may look fine while deeper article URLs silently break. This is where automated curl checks help a lot:
while read -r url; do
curl -o /dev/null -sS -w '%{http_code} %{url_effective}\n' -L "$url"
done < urls-to-test.txt
That command is simple and valid on any normal shell, and it scales better than clicking through a browser when you have a batch of legacy URLs to verify.
Permanent redirects and cache rollback planning
Because 301 responses can stick in browsers and intermediate caches, I think about rollback before I call something permanent. If a high-risk migration is happening, I prefer to confirm the destination content, cookies, login flow, and canonical host behavior under 302 first unless the move is already well proven.
This is less about being cautious for its own sake and more about saving time during incidents. If users cache the wrong 301 and your support team tests only in browsers, you can end up chasing a bug that no longer exists on the server. Having a rollback and validation plan for redirects is one of those boring habits that pays off immediately when something changes under load.
Common mistakes I see with redirects
Using 302 for permanent canonicalization
It works, but it sends the wrong signal and can delay cleanup.
Using 301 during temporary maintenance
Then clients keep following it after the maintenance window ends.
Stacking redirects across app and web server layers
This creates chains and sometimes loops.
Using regex-heavy rewrites when return or Redirect would do
Simple rules are easier to audit.
Forgetting API semantics
A moved browser page and a moved POST endpoint are not the same problem.
Security and operational considerations
Redirects are not just SEO tools. They shape trust boundaries and canonical entry points.
- redirect HTTP to HTTPS once certificates are valid
- keep host canonicalization consistent to avoid session and cookie confusion
- avoid open redirects that use untrusted query parameters or user-supplied target URLs
- validate changes with
curl, not only a browser cache - if you redirect admin or login paths, make sure the target preserves expected headers and scheme awareness
For the security layer around those moves, HTTP security headers in Nginx is worth reviewing after redirect work.
My redirect checklist
1. Decide whether the move is permanent or temporary
If rollback should be easy, start with 302 or 307.
2. Decide whether method preservation matters
For APIs and form submissions, consider 307 or 308.
3. Implement the simplest rule possible
Prefer return in Nginx and Redirect in Apache when pattern matching is not needed.
4. Validate syntax
sudo nginx -t
sudo apachectl configtest
5. Test every expected entry point
curl -I http://example.com/
curl -I http://www.example.com/
curl -I https://www.example.com/
6. Test chains and final targets
curl -I -L -o /dev/null -sS -w 'redirects=%{num_redirects} final=%{url_effective}\n' http://www.example.com/
Query strings, path preservation, and small details that break big migrations
When I test redirects, I do not stop at the bare path. I also check whether query strings are preserved when they should be. Campaign parameters, filter states, and tracking links are easy to drop accidentally.
A quick check:
curl -I 'http://example.com/old-page?utm_source=test&ref=abc'
If the redirect target strips information the application still needs, users can land on the right page but lose context. I also verify path preservation on nested URLs because a rule that works on / can still mis-handle /products/item-1 or /blog/old-slug. Those edge cases are where redirect migrations usually become messy.
I also test redirects from a fresh client perspective whenever possible, because cached behavior can hide server-side improvements and send troubleshooting in the wrong direction.
That extra validation step is boring, but it catches a lot of redirect mistakes before search engines and users cache them aggressively.
Conclusion
The right redirect type depends on more than habit. A 301 is for genuine permanent moves, a 302 is for temporary ones, and 307 or 308 are better when the request method must be preserved. In Nginx, I prefer return for simple redirects and reserve rewrite for transformations. In Apache, Redirect is the clean option for straightforward cases while RewriteRule handles the complex ones. The operational discipline is just as important as the syntax: avoid chains, watch browser caching behavior, validate with curl, and do not mark something permanent until you are sure it really is.