301 Redirect Not Working: How to Find and Fix the Cause
You add a 301 redirect, and the old page still loads. Or you change one, and visitors keep landing on the old destination. A permanent redirect that does nothing is nearly always one of three things: a cache answering instead of your server, another rule catching the request first, or a rule written in the wrong place or format for the platform. Guessing wastes hours, because the browser you test with remembers old redirects. This guide shows you how to test a redirect properly from the command line, find which layer is answering, and fix the rule on Apache, nginx, Plesk, Cloudflare, cPanel, WordPress, Wix, Squarespace and Webflow.
Why a 301 redirect seems not to work
A 301 redirect tells browsers and search engines that a page has moved permanently to a new address. When you add one and the old URL still loads, or you remove one and it keeps redirecting, the cause is nearly always one of three things:
- A cache is answering instead of your server. Browsers store 301 responses and reuse them without asking again. Page caches and CDNs can also hold the old response.
- Another rule handles the request first. A CMS, plugin, hosting panel or CDN rule catches the URL before your redirect gets the chance, or sends it somewhere else afterwards.
- The rule is in the wrong place or the wrong format. An .htaccess rule on an nginx server, a path entered with the domain on a site builder, or a pattern that does not quite match the URL being requested.
The first job is to stop guessing which. The browser is the worst tool for this, because it shows what it remembers. A command-line request shows what the server actually sends now.
How to test a redirect properly
Request the old URL with curl, which never caches redirects:
curl -sI https://www.example.com/old-page
A working redirect returns something like:
HTTP/2 301
location: https://www.example.com/new-page/
server: nginx
To follow the whole chain and see each step, add -L and filter the output:
curl -sIL https://www.example.com/old-page | grep -iE '^(HTTP/|location:|server:|x-redirect-by:|cf-)'
Read the result against this table:
| What curl shows | What it means |
|---|---|
| 200 on the old URL | No redirect is reaching the request; the rule is not being read or not matching |
| 301 to the wrong address | Another rule runs first, or the target is written wrongly |
| 302 or 307 instead of 301 | The rule or tool defaults to temporary; set the type explicitly |
| 301 in curl, but the browser behaves differently | The browser is using a cached response; clear it or use a private window |
| Several 301s in a row | Rules are stacked; see redirect chains |
| Redirects back to itself, or curl stops at 30 redirects | A loop; see redirect loops |
The headers often tell you which layer answered. cf-ray means Cloudflare, x-redirect-by: WordPress or a plugin name means the CMS, and the server header shows whether Apache, nginx or LiteSpeed sent it. Test every variant too: http and https, with and without www, and with and without a trailing slash, because a rule often matches only one of them.
Apache, .htaccess, nginx and Plesk
Apache and .htaccess
For .htaccess rules to work at all, three things must be true: the server runs Apache or LiteSpeed, the virtual host allows overrides with AllowOverride All (or at least FileInfo), and for RewriteRule the rewrite module is enabled, with RewriteEngine On in the file. A quick test is to put a deliberate typo at the top of .htaccess: if the site does not return a 500 error, the file is not being read.
Common mistakes in the rule itself:
- A leading slash in the pattern. In .htaccess,
RewriteRulepatterns are matched without the leading slash, so^/old-page$never matches. Use^old-page/?$. - Rules below the WordPress block. WordPress sends every request that is not a real file to
index.php. Put redirects above# BEGIN WordPress. - Query strings.
RewriteRuledoes not see anything after the?. Match it with aRewriteCond %{QUERY_STRING}line. - Mixing modules.
Redirect 301lines andRewriteRulelines are handled by different Apache modules. Mixing them for the same URLs gives results that are hard to predict; pick one.
nginx
nginx does not read .htaccess files at all. If the server header says nginx and Apache is not running behind it, every rule in .htaccess is ignored. Redirects go in the server block, and nginx must be reloaded after each change:
sudo nginx -t && sudo systemctl reload nginx
Plesk
On Plesk with nginx in front of Apache, .htaccess works for most pages, because nginx passes them to Apache. But if Serve static files directly by nginx is switched on under Apache and nginx Settings, requests for files such as .pdf or .jpg never reach Apache, so .htaccess redirects for them do nothing. Put those redirects in the Additional nginx directives box instead. On a Plesk site running nginx only, all redirects must go there. See redirects on Plesk and redirects on Apache.
Cloudflare, cPanel, WordPress and site builders
Cloudflare
Cloudflare Redirect Rules only act on traffic that passes through Cloudflare, so the DNS record for the hostname must be proxied, shown by the orange cloud. A grey-cloud record goes straight to your server and ignores every Cloudflare rule. Rules also run in order, so an earlier rule can catch the request first. Check Single Redirects, Bulk Redirects and any old Page Rules for overlapping matches. Purge the cache for the URL after changing anything. See redirects on Cloudflare.
cPanel
The Redirects tool under Domains writes lines into the .htaccess file of the domain's document root. The usual reasons it fails are choosing the wrong domain from the list, the www option not matching how visitors arrive, and the wildcard box being ticked or left unticked by mistake. Open .htaccess in File Manager afterwards and check the generated RewriteCond and RewriteRule lines sit above the WordPress block.
WordPress
Redirect plugins only run when WordPress loads, so a page cache plugin or server cache can serve the old page without WordPress ever seeing the request. Clear the page cache after adding a redirect. WordPress also redirects old post slugs on its own, which can override or confuse a plugin rule. See redirects in WordPress.
Wix, Squarespace and Webflow
| Platform | Where redirects live | Common reason it fails |
|---|---|---|
| Wix | The URL Redirect Manager in the SEO settings | Full domain typed in the old URL box; the site is not on a connected custom domain |
| Squarespace | URL Mappings in the developer tools settings, written as /old-page -> /new-page 301 | A live page still uses the old path; missing spaces around the arrow |
| Webflow | The 301 redirects section in Site settings | Site not published after saving; trailing slash not matching the old URL |
On all three, enter the old path without the domain, match the trailing slash exactly as the old URL had it, and check that no live page still sits at the old path.
Working redirect rules
One page on Apache
RewriteEngine On
RewriteRule ^old-page/?$ /new-page/ [R=301,L]
# BEGIN WordPress
...
A whole domain on Apache
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old-example\.com$ [NC]
RewriteRule ^(.*)$ https://new-example.com/$1 [R=301,L]
One page on nginx
location = /old-page {
return 301 /new-page/;
}
A whole domain on nginx
server {
listen 443 ssl;
server_name old-example.com www.old-example.com;
# ssl_certificate lines for the old domain go here
return 301 https://new-example.com$request_uri;
}
$request_uri keeps the path and query string, so every old page goes to its matching new page. The old domain still needs a valid certificate, or browsers stop with a security warning before they ever see the redirect.
A worked example
A business renames its services page from /services-old/ to /services/ and adds this to .htaccess:
# BEGIN WordPress
...
# END WordPress
RewriteRule ^/services-old/$ /services/ [R=301,L]
The old URL still loads. curl returns 200. There are two faults. The rule sits below the WordPress block, so WordPress has already taken the request, and the pattern starts with a slash, so it would not match in .htaccess anyway. Moving the rule above # BEGIN WordPress and changing it to ^services-old/?$ gives a 301 in curl straight away.
The owner's browser, however, now loops between the two pages. Earlier that week he had tested a redirect from /services/ to /services-old/, and the browser cached it. A private window shows the redirect working correctly, and clearing the browser cache fixes his own view.
The fix, step by step
Step 1
Test the old URL with curl
Run curl -sI on the old URL and read the status line and Location header. Then run it again with -L to see every hop. Do this for http and https, with and without www, and with and without a trailing slash. Write down what each variant returns. You now know whether the redirect is missing, pointing somewhere wrong, temporary instead of permanent, or working perfectly and hidden by your browser cache.
Step 2
Rule out the browser cache
If curl shows the correct 301 but your browser does not, the browser is replaying a stored response. Open a private window, or in Chrome DevTools tick Disable cache on the Network panel and reload. Clear the browser cache if your normal window still misbehaves. Confirm by loading the old URL in the private window and seeing it arrive at the new address in a single step.
Step 3
Identify which layer answers the request
Look at the response headers from curl. A cf-ray header means Cloudflare responded, x-redirect-by names WordPress or a plugin, and the server header shows Apache, nginx or LiteSpeed. If the redirect you wrote lives in a different layer from the one answering, that layer is catching the request first. Confirm by checking whether the answering layer has its own rule or cached copy for the URL.
Step 4
Put the rule where the server reads it
If the server header says nginx with no Apache behind it, move the rule from .htaccess into the nginx server block, or the Additional nginx directives box on Plesk. On Apache, check that AllowOverride permits .htaccess and that the rewrite module is enabled. For Cloudflare, make sure the DNS record is proxied. Confirm with a curl request that the layer you chose now returns the 301.
Step 5
Correct the rule's syntax and order
On Apache, place the rule above # BEGIN WordPress, remove any leading slash from the RewriteRule pattern and add /? to match with or without a trailing slash. On nginx, use location = /old-page with return 301. On site builders, enter the path only, without the domain. Set the type to 301 explicitly. Reload the server after editing, then test again with curl to see the new response.
Step 6
Remove competing rules
Search every layer for other rules that match the same URL: redirect plugins, the CMS's own slug redirects, the cPanel Redirects tool, Cloudflare Single and Bulk Redirects and any old Page Rules. Delete duplicates so each redirect exists in one place only, and make sure it points straight at the final address. Retest the full chain with curl -sIL and confirm there is exactly one 301 followed by a 200.
Step 7
Clear server and CDN caches
Purge the page cache in your CMS or caching plugin, any server-level cache such as LiteSpeed or Varnish, and the CDN cache for both the old and new URLs. Cached responses can keep serving the old page or an old redirect for hours after the rule changes. Run curl once more after purging and check that the response headers show a fresh response rather than a cache hit.
Step 8
Publish and retest every variant
On Wix, Squarespace or Webflow, publish the site if the platform needs it before redirects go live. Then run your curl loop over every variant of the old URL and confirm each returns a single 301 to the final page. Update internal links, menus and the sitemap to use the new address. Finally, inspect the old URL in Search Console so Google can see the redirect sooner.
Edge cases worth knowing
- The old URL is a real file or folder. If a file or directory still exists at the old path, some setups serve it before the rewrite runs. Remove or rename it.
- Redirects that only work on http. A rule placed in the port 80 server block, or a CDN rule limited to http, does nothing for https visitors. Check both.
- Case differences. URLs are case-sensitive on most servers. Add
[NC]on Apache, or a case-insensitive match on nginx, if old links use mixed case. - Encoded characters. Paths with spaces or accented letters arrive encoded, for example
%20. Match the form the server actually receives. - Google still shows the old URL. That is not a broken redirect. Search engines take days or weeks to recrawl and update results.
Keeping redirects reliable
- Keep each redirect in one place only: the server, the CMS or the CDN. Write down which layer you use so the next person does not add a competing rule elsewhere.
- Point every redirect at the final URL, so a later change does not create a chain.
- Keep a simple list of old and new URLs, and test it with a curl loop after every hosting move, plugin change or redesign.
- Update internal links to the new URL rather than relying on the redirect for your own menus and content.
If you later need to undo a permanent redirect, the guide to removing a 301 redirect covers the caches involved. For larger moves, see migration redirects.
1. Why is my 301 redirect not working?
A 301 that seems not to work is nearly always cached, overruled by another rule, or written in the wrong place or format. Browsers store 301s and replay them, so test with curl or a private window. Then check which layer answers the request, since a plugin, CDN or hosting panel can catch the URL first. Finally, check the rule itself: an .htaccess rule does nothing on nginx, and a pattern with a leading slash never matches in .htaccess.
2. Why does my .htaccess redirect not work?
The most common reasons are that the server runs nginx and never reads .htaccess, that the virtual host has AllowOverride set to None, or that the rule sits below the WordPress block, which takes every request first. In .htaccess, RewriteRule patterns also have no leading slash, so a pattern beginning with a slash never matches. Put a deliberate typo at the top of the file: if the site does not return a 500 error, the file is not being read at all.
3. Why is my Cloudflare redirect rule not working?
Cloudflare redirect rules only run on traffic that passes through Cloudflare, so the DNS record for that hostname must be proxied, shown by the orange cloud. A grey-cloud record goes straight to your server and skips every rule. Also check the rule order, because an earlier rule or an old Page Rule can match the request first, and make sure the hostname and path in the rule match exactly. Purge the cache and test with curl afterwards.
4. How do I clear a cached 301 redirect in my browser?
Clear the browser's cached images and files, or test in a private window, which starts with an empty cache. In Chrome you can also open DevTools, tick Disable cache on the Network panel and reload the page while DevTools stays open. Browsers keep 301 responses because they are meant to be permanent, so a redirect you removed can still seem to work, and a new one can seem missing, until the stored copy is gone.
5. How long does a 301 redirect take to work?
A 301 redirect works as soon as the server sends it, which is immediately after you save the rule and reload the server or publish the site. If it seems slow, a cache is in the way: a page cache, a CDN or your browser is still serving the old response. Search engines are different, because they need to recrawl the old URL before updating results, which can take days or weeks for less visited pages.
6. Why does my redirect work on http but not on https?
The rule is probably only in the configuration that handles http on port 80, or the CDN rule is limited to the http scheme. On nginx, http and https are usually separate server blocks, so a rule in one does not apply to the other. On Apache, an https virtual host may point to a different folder or not allow .htaccess. Add the rule where https requests are handled, then test both variants with curl.
7. Should I use .htaccess, a plugin or Cloudflare for redirects?
Use whichever single layer you can manage reliably, and keep all redirects in that one place. Server rules in .htaccess or nginx are fast and do not depend on the CMS. A WordPress plugin is easier to edit but only runs when WordPress loads. Cloudflare rules run before your server and suit domain-wide moves. Problems start when the same URL is redirected in two or three layers, so choose one and remove copies from the others.
The redirect checker requests each URL fresh, with no browser cache, and shows every hop with its status code and Location header, so you can see straight away whether your 301 is live, wrong or missing.
Open Redirect Fixes โ