A redirect comes after the first plain request has crossed the network. HSTS (Strict-Transport-Security) protects later visits: for max-age seconds the browser upgrades every http:// URL for the host itself and allows no click-through on certificate errors. Browsers ignore it over HTTP, so send it on every HTTPS response.
# /var/www/example/.htaccess: HTTPS only, and tell browsers to remember it
# Needs: mod_rewrite, mod_headers; AllowOverride FileInfo; Options FollowSymLinks
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
# Stage 1 of the ramp-up: 5 minutes. Raise it only after each stage has run clean
Header always set Strict-Transport-Security "max-age=300; includeSubDomains" \
"expr=%{HTTPS} == 'on'"http://example.com/?q=1 301 Location: https://example.com/?q=1 https://example.com/ 200 Strict-Transport-Security: max-age=300; includeSubDomains https://example.com/missing 404 Strict-Transport-Security: max-age=300; includeSubDomains
Lines 3 to 5 are rule 2 of Canonical Host and HTTPS (behind a TLS proxy, test X-Forwarded-Proto as it does), with a fixed host rather than the client's %{HTTP_HOST}. always puts the header on errors and HTTPS redirects; the expr keeps it off the plain 301. A browser cannot be told to forget HSTS early, and includeSubDomains binds intranet hosts still on HTTP, so ramp up: 300 seconds, then 604800 (a week), then 2592000 (a month). Preloading, which protects even the first visit, needs max-age=31536000; includeSubDomains; preload and a submission at hstspreload.org (https://hstspreload.org/ 26,500 ); removal takes months. Canonical HTTPS Block adds the canonical host.