Sooner or later every site changes a URL: a cleaner slug, a move to HTTPS, a new domain or subdomain. Each time, the same question comes up: should the redirect be a 301 or a 302?
I covered HTTP status codes in general in “HTTP Status Code Basics.” This article narrows in on 301 and 302: how search engines interpret each one, and which redirects WordPress already sends on its own. The examples come from running curl against this blog.
A redirect is “don’t look here, look there”
A redirect response has two parts: a status code that describes the nature of the move, and a Location header that says where to go.
HTTP/2 301
location: https://wpmm.jp/blog/
For a visitor, a 301 and a 302 look identical: the browser simply follows Location. The difference is whether the move is meant to be permanent or temporary.
| Code | Name | Nature | Method on re-request |
|---|---|---|---|
| 301 | Moved Permanently | Permanent | POST may become GET |
| 302 | Found | Temporary | POST may become GET |
| 307 | Temporary Redirect | Temporary | Method preserved |
| 308 | Permanent Redirect | Permanent | Method preserved |
For ordinary page URLs (visited with GET), “permanent → 301, temporary → 302” is all you need. 307 and 308 matter when you move form endpoints or APIs.
How search engines read 301 and 302
You will often read that “a 301 passes ranking value and a 302 does not.” Google’s own explanation is more precise. Redirects are treated as signals for choosing the canonical URL:
- 301 / 308 (permanent): a strong signal that the target should be canonical. Search results gradually show the new URL.
- 302 / 307 (temporary): a weak signal. The original URL tends to stay in search results for a while.
Google also states that neither type of redirect, by itself, causes ranking value to be lost. So the real difference is not “value kept vs. value lost” but how clearly you tell the search engine which URL to show. A long-lived 302 may eventually be treated as permanent, but that leaves the decision to guesswork. If you know the move is permanent, say so with a 301.
What this blog actually returns
I sent a variety of URL forms to this blog and checked the status, Location, and x-redirect-by headers:
curl -s -o /dev/null -D - https://www.wpmm.jp/blog/ | grep -i "^HTTP\|^location\|^x-redirect-by"
| Requested URL | Code | Target | Sent by |
|---|---|---|---|
http://wpmm.jp/blog/ |
301 | https://wpmm.jp/blog/ |
Web server (HTTPS) |
https://www.wpmm.jp/blog/ |
301 | https://wpmm.jp/blog/ |
WordPress |
https://wpmm.jp/blog (no trailing slash) |
301 | https://wpmm.jp/blog/ |
Web server |
| Post URL without trailing slash | 301 | Same URL with / |
WordPress |
https://wpmm.jp/blog/?p=168 |
301 | The post’s permalink | WordPress |
https://wpmm.jp/blog/http-status (partial slug) |
301 | The matching post | WordPress |
https://wpmm.jp/en/blog/ (old EN URL) |
301 | https://en.wpmm.jp/blog/ |
.htaccess rule |
https://wpmm.jp/blog/wp-admin/ (logged out) |
302 | Login page | WordPress |
Three takeaways:
- Every “same content, different spelling” consolidation is a 301:
httpvs.https,wwwvs. none, trailing slash,?p=IDvs. permalink. These are permanent canonical decisions. - The only 302 is the admin-to-login hop. After logging in you return to the admin screen, so the move is genuinely temporary.
x-redirect-by: WordPresstells you where a redirect came from. If the header is missing, the redirect happened earlier, in the web server or.htaccess(see “Reading .htaccess mod_rewrite Rules“). This is the first thing to check when you are hunting an unexpected redirect.
Redirects WordPress sends on its own
Checking the WordPress 7.1.2 source on this server, the WordPress-generated 301s come from three core functions:
redirect_canonical()(hooked totemplate_redirect): if the requested URL differs from what WordPress considers canonical, it sends a 301. That covers?p=ID, trailing slashes, andwwwnormalization.wp_old_slug_redirect(): when you change the slug of a published post, WordPress stores the old slug in_wp_old_slugand later 301s the old URL to the new one. Hierarchical types such as pages are not covered. This blog forbids changing slugs after publishing, and a read-only database check showed zero_wp_old_slugentries.redirect_guess_404_permalink(): when a URL would 404, WordPress looks for a post whose slug starts with the requested one (post_name LIKE 'http-status%') and 301s there. That is thehttp-statusrow above. It rescues typos and truncated links, but a deleted post’s URL can end up redirecting to an unrelated post with a similar slug. Thestrict_redirect_guess_404_permalinkanddo_redirect_guess_404_permalinkfilters let you tighten or disable it.
The default-302 trap in your own code
// wp-includes/pluggable.php (WordPress 7.1.2)
function wp_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) {
Both wp_redirect() and wp_safe_redirect() default to 302. If you mean a permanent move, pass 301 explicitly, and always call exit afterwards:
wp_safe_redirect( home_url( '/new-page/' ), 301 );
exit;
wp_safe_redirect() restricts the destination to your own site (plus allowed hosts), which is the safer choice whenever any part of the target comes from the request.
When 301 is “too strong”
Browsers may cache a 301 even without caching headers. If you set the wrong target, fixing it on the server does not help visitors whose browsers already remember the old hop (see “Cache-Control vs. ETag“). A practical approach:
- Test with a 302, then switch to 301 once the target is confirmed.
- Verify with
curl, not a browser. - Conversely, do not leave a permanent move (HTTPS, a domain change) on 302 indefinitely.
Redirect chains
On this blog, http://wpmm.jp/en/blog/ takes two hops:
curl -sL -o /dev/null -w "%{num_redirects} redirects -> %{url_effective} %{http_code}\n" \
http://wpmm.jp/en/blog/
# 2 redirects -> https://en.wpmm.jp/blog/ 200
Two hops are harmless, but chains tend to grow with each migration. Keep them short:
- When adding a new redirect, point old redirects straight at the final URL (A→C and B→C, not A→B→C).
- Use final URLs in internal links,
canonical, hreflang, and sitemaps (see “robots.txt and sitemap.xml Basics“). - Watch for loops (A→B→A), often caused by an HTTPS rule that disagrees with the WordPress site address.
A checklist for changing URLs
- Ask whether the URL really needs to change. Not changing it is the safest option.
- Build a one-to-one map from old to new URLs. Do not send everything to the homepage; search engines may treat that as a soft 404.
- Return 404 or 410 for content that no longer exists.
- Use 301 (or 308) for permanent moves, after testing with 302.
- Check each URL with
curl: status,Location, number of hops, final 200. - Update internal links, canonicals, and sitemaps to the new URLs.
- Keep redirects in place for a long time. Google recommends at least a year for site moves, and external links can last far longer.
Summary
A 301 says “moved permanently” and a 302 says “moved for now.” For search engines, the difference is the strength of the canonical signal, not whether ranking value survives. On this blog, every URL-spelling consolidation is a 301, and the only 302 is the temporary admin-to-login hop. WordPress sends many 301s by itself through redirect_canonical(), wp_old_slug_redirect(), and redirect_guess_404_permalink(), and the x-redirect-by header shows when WordPress is the source. Watch out for the 302 default in wp_redirect() and for browsers caching 301s, and URL changes become a routine task rather than a risky one.