A permanent redirect is marked temporary
Updated
6% of the 771 domains we could check get a warning for this. Sample: the first 1,000 domains by rank in the Majestic Million, checked on 2026-10-12. How we measured
What this check looks for
The redirect between your www and non-www addresses says 'this is temporary'. Browsers and search engines therefore keep asking for the old address every time.
Why it matters
A 302, 303 or 307 is not cached unless the response explicitly allows it, so every visit pays the extra round trip, and search engines keep both addresses in the index instead of consolidating them onto one.
When the check passes, your report says: “The canonical redirect is marked permanent, as it should be”.
What it costs your score
When this check fails it removes 5 points from your HTTP score, before the status, confidence and repeat multipliers are applied. HTTP carries a weight of 7 in the overall score.
It shares the http.redirects family ceiling of 45 points: however many findings that family produces, together they cannot remove more than that from HTTP. One underlying problem showing up in several places is still one problem.
- Severity
- low
- Default confidence
- confirmed
- Status when triggered
- warn
- Deduction
- 5 points
- Family cap
- http.redirects · 45
- Category
- HTTP
- Module
- Http redirects
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Change the www/non-www redirect from its temporary status (302, 303 or 307) to 301, or to 308 where the request method must be preserved.
A 302, 303 or 307 is not cached by default and does not consolidate the two hostnames in search indexes; a 301 or 308 does both.
Change the status code on the
www/non-wwwredirect: a 302 or 303 becomes 301, and a 307 becomes 308, its method-preserving permanent counterpart.Roll it out at a short cache lifetime first: a 301 or 308 is cached aggressively and is awkward to take back.
How to confirm it worked
curl -sSIL http://‹host›/ | grep -iE '^(HTTP/|location:)' — the hop whose Location adds or removes
wwwshould answer 301 or 308, not 302, 303 or 307
A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.
Remediation by platform
# Two hops at most: HTTPS on the same host first, then the canonical host.
# Each name is written out rather than taken from $host, so no request is redirected
# to a Host value the client made up.
server {
listen 80;
server_name ‹host›;
return 301 https://‹host›$request_uri;
}
server {
listen 80;
server_name www.‹host›;
return 301 https://www.‹host›$request_uri;
}
server {
listen 443 ssl;
server_name ‹host›;
# The site's ssl_certificate lines belong here too.
add_header Strict-Transport-Security "max-age=31536000" always;
return 301 https://www.‹host›$request_uri;
}# Two hops at most: HTTPS on the same host first, then the canonical host.
<VirtualHost *:80>
ServerName ‹host›
RedirectMatch 301 ^/(.*)$ https://‹host›/$1
</VirtualHost>
<VirtualHost *:80>
ServerName www.‹host›
RedirectMatch 301 ^/(.*)$ https://www.‹host›/$1
</VirtualHost>
<VirtualHost *:443>
ServerName ‹host›
# The site's SSLEngine and certificate lines belong here too.
Header always set Strict-Transport-Security "max-age=31536000"
RedirectMatch 301 ^/(.*)$ https://www.‹host›/$1
</VirtualHost>‹host› {
header Strict-Transport-Security "max-age=31536000"
redir https://www.‹host›{uri} permanent
}SSL/TLS → Edge Certificates → Always Use HTTPS: on (hop one, the upgrade on the same name).
SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS): enable, Max Age 12 months; turn on includeSubDomains only once every subdomain serves HTTPS. Hop one exists so that ‹host› sends this header; without it the extra hop buys nothing.
Rules → Overview → Create rule → Redirect Rule: when Hostname equals ‹host› and SSL/HTTPS is on, Dynamic redirect to concat("https://www.‹host›", http.request.uri.path), status 301, preserve query string (hop two).
Then confirm with curl -sSI https://‹host›/ that the 301 carries a Strict-Transport-Security header.
(Dashboard paths and option names as at 2026-10, a dated snapshot; Cloudflare reorganises these pages and may again.)Each starting URL reaches the canonical one in at most two hops, and the bare name sends its own HSTS header on the way, which is the shape the HSTS preload list requires. Add includeSubDomains once every subdomain serves HTTPS. If HSTS preload is not a goal, sending plain HTTP for ‹host› straight to https://www.‹host› is also fine and saves a round trip; the bare name then never sends an HSTS header a browser accepts.
Shown because the response identified Apache. Prefer the virtual host over .htaccess: .htaccess is re-read on every request and its rules run after the virtual host's, which is how these chains get long in the first place. If HSTS preload is not a goal, sending plain HTTP for ‹host› straight to https://www.‹host› is also fine and saves a round trip; the bare name then never sends an HSTS header a browser accepts.
Caddy's automatic HTTPS already answers plain HTTP for ‹host› with a redirect to HTTPS on the same name, so this block is the second hop of the two the preload list expects.
Cloudflare Redirect Rules run at the edge and apply only to requests that arrive through it. An origin reachable directly — by IP, or through a DNS record that is not proxied — still produces the chain measured here. Fix the origin's own configuration as well.
Technical detail
‹detail›
Three redirect statuses stop short of declaring a permanent move. 302 Found and 307 Temporary Redirect say the resource is elsewhere for now and the original URL should keep being requested; 303 See Other points this one request at a different resource. None of the three is cached without explicit freshness information, and none tells a search engine to consolidate the two URLs. Choosing between the canonical and non-canonical form of your own hostname is not temporary, so the redirect should use one of the two permanent statuses. Use 301, or 308 if the request method must be preserved — 301 permits a client to rewrite POST to GET and 308 does not. 307 is the method-preserving form of 302 in the same way that 308 is of 301, so a 307 here becomes a 308.
Standards and references
Test this on your domain
Run the check that produces this finding, on its own, against any domain.