dotvitals

Loading the list of pages…

↑↓ to moveEnter to openEsc to close

Plain HTTP is sent to HTTPS with a temporary redirect

Updated

LowConfirmedQuick winhttp.reachability.temporary-https-redirect

What this check looks for

Visitors who arrive over plain HTTP are moved to the secure address, but the redirect says the move is temporary. A browser therefore does not remember it, and every visit that starts from an http:// address goes through the insecure one again.

Why it matters

A 301 or 308 may be cached, so a browser that has followed it once can go straight to HTTPS next time. A 302, 303 or 307 is not, so every typed address, bookmark and http:// link sends its first request in the clear again. HSTS or the preload list stops a browser that has them from making that request at all, but a first visit without either still goes over HTTP, through this redirect.

When the check passes, your report says: “Plain HTTP is sent to HTTPS with a permanent redirect”.

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.reachability family ceiling of 60 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.reachability · 60
Category
HTTP
Module
Http reachability
Fix owned by
user
In the ruleset since
2026.10

How the whole score is calculated

How to fix it

Make the redirect from plain HTTP to HTTPS permanent: a 302 or 303 becomes 301, and a 307 becomes 308.

This scan saw HTTP ‹status›, so the change here is ‹status› → ‹fix status›.

A temporary redirect is not remembered, so the plain-HTTP request it exists to end is repeated on every visit from a browser that has not seen your HSTS header.

  1. Change the status code on the port-80 redirect and nothing else: same host, same path, same query string. A 302 or 303 becomes 301; a 307 becomes 308.

    This redirect also changes the path, to ‹target path›. If that part is deliberately temporary — a language or sign-in redirect — split it in two: a permanent redirect to the same path on HTTPS first, then the temporary one over HTTPS, where it no longer travels in the clear.

  2. If the redirect comes from a CDN or hosting setting rather than your own server, change it there. Cloudflare's Always Use HTTPS answers with a 301; a Redirect Rule or Page Rule doing the upgrade has its own status code field.

  3. Keep /.well-known/acme-challenge/ served over plain HTTP if certificates are renewed with the HTTP-01 challenge.

  4. Then send HSTS from the HTTPS site, so a returning browser stops making the plain request at all.

How to confirm it worked

  • curl -sSIL http://‹host›/ | grep -iE '^(HTTP/|location:)' — lists every hop; the first status line is the answer to http://‹host›/ and should read 301 or 308, with a Location beginning https://‹host›/

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

nginx
server {
	listen 80;
	listen [::]:80;
	server_name ‹host›;

	# Keep the ACME challenge on plain HTTP, or renewal breaks.
	location ^~ /.well-known/acme-challenge/ {
		root /var/www/html;
	}

	location / {
		return 301 https://$host$request_uri;
	}
}
Apache
<VirtualHost *:80>
	ServerName ‹host›
	RedirectMatch 301 ^/(?!\.well-known/acme-challenge/)(.*)$ https://‹host›/$1
</VirtualHost>
Caddy
# Caddy's own HTTP-to-HTTPS redirect is permanent. A temporary status usually comes from
# an explicit block like this one with `temporary`, or with no status at all (302):
http://‹host› {
	redir https://‹host›{uri} permanent
}
Cloudflare
SSL/TLS → Edge Certificates → Always Use HTTPS → On.
If a Redirect Rule does the upgrade instead: Rules → Redirect Rules → that rule → Status code → 301 (or 308).
  • return 302 and return 307 are the temporary forms; rewrite … redirect is a 302 too, and rewrite … permanent a 301. Use return 308 where the redirect is a 307 today and the request method must be kept.

  • Redirect and RedirectMatch without a status, and RewriteRule … [R] without =301, all answer 302. Use 308 in place of 301 where the redirect is a 307 today and the request method must be kept.

  • This runs at Cloudflare's edge and applies only to traffic that reaches visitors through it. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still answers on port 80 exactly as before. Configure the redirect on the origin as well.

Technical detail

http://‹host›/ answered HTTP ‹status› with a Location of ‹location›. The request is upgraded, but ‹status› is a temporary status, and the change here is ‹status› → ‹fix status›.

Two statuses declare a permanent move: 301 Moved Permanently and 308 Permanent Redirect, and both may be cached without explicit freshness information. 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 unless the response says so. Moving a site from http to https is not temporary, so a 302 or 303 becomes 301, and a 307 becomes 308, the permanent status that, like 307, does not let a client change the request method.

HSTS changes how often this redirect is used, not whether it is right. A browser that has seen the Strict-Transport-Security header, or that has the name on its HSTS preload list, rewrites http:// to https:// before sending anything; the first visit from any other browser is still a plain-HTTP request, and this redirect answers it.

Standards and references

Test this on your domain

Run the check that produces this finding, on its own, against any domain.

Open the http reachability checkerBuild the fix