Consolidating www and HTTPS in a Single Hop

Every site starts life with four addresses: http://example.com, http://www.example.com, https://example.com, and https://www.example.com. Three of them should return a single 301 to the fourth. The number that matters is single — the common configuration produces two hops, because protocol normalisation and host normalisation are written as separate rules that fire in sequence.

Two hops is not a disaster. It is an avoidable round trip on every inbound link that arrives at the wrong variant, and inbound links arrive at the wrong variant constantly, because they were created before you picked.

Picking the canonical host

www or bare host: mechanically equivalent for indexing, and the choice is technical rather than editorial.

The argument for www: the apex domain cannot have a CNAME record under the original DNS specification, so pointing a bare domain at a CDN or platform requires either provider-specific flattening or an A record with an IP you do not control the lifecycle of. www is a subdomain and can CNAME freely. If you expect to move hosting, www is the more portable choice.

The argument for the bare host: it is shorter, it is what people type, and every serious DNS provider now offers apex flattening (ALIAS, ANAME, or CNAME flattening), which removes the original objection.

The overriding argument: whichever one you are already serving on your indexed URLs. Switching hosts means redirecting the entire site and is a migration, with the sequencing described in preserving links through a site migration. The status quo wins unless there is a hosting reason it cannot.

Cookies are a secondary consideration worth one sentence: a cookie set on the apex domain is sent to every subdomain, including asset hosts, which is a small performance cost and occasionally a security one. Serving from www lets you keep the apex cookie-free.

Why separate rules chain

The natural way to write this is two rules, each doing one thing:

# rule 1: force HTTPS
http://*  →  https://<same host><same path>

# rule 2: force www
https://example.com/*  →  https://www.example.com/<same path>

Both are correct. Together, a request for http://example.com/page/ produces:

http://example.com/page/       → 301 → https://example.com/page/
https://example.com/page/      → 301 → https://www.example.com/page/
https://www.example.com/page/  → 200

Two hops, because rule 1 fires first and hands off to a URL that rule 2 then rewrites. Add a trailing-slash rule and it is three; add case normalisation and it is four. That is the accretion mechanism described in redirect chains and how to flatten them, except here it happens on day one rather than over five years.

Combining them

The fix is one rule that evaluates every property and emits one redirect to the fully normalised URL.

nginx, with a dedicated server block for all non-canonical variants:

# nginx: everything that is not https://www.example.com gets one 301
server {
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;

    # TLS config for both names goes here

    if ($host != "www.example.com") {
        return 301 https://www.example.com$request_uri;
    }
    if ($scheme != "https") {
        return 301 https://www.example.com$request_uri;
    }

    # ... normal handling for the canonical variant
}

Both conditions redirect to the same fully-qualified target, so whichever one matches first produces the final URL directly. There is no handoff and therefore no chain. $request_uri carries the path and query string together, which is what preserves parameters.

The requirement this imposes: the certificate must cover both host names. A bare-domain request over HTTPS has to complete a TLS handshake before your redirect can be sent, so a certificate that only covers www means https://example.com fails at the transport layer with a certificate error, and no redirect is ever delivered. This is the single most common version of this misconfiguration, and it is invisible if you only ever test the canonical host.

Apache, same approach:

# Apache: one hop to https://www.example.com
RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

[OR] between the conditions means either triggers the rule, and %{REQUEST_URI} supplies path and query. The [NC] on the host comparison is appropriate because host names are case-insensitive by specification — unlike paths, which is the distinction in uppercase URLs and the duplicates they create.

If you are also normalising the trailing slash, fold that into the same rule rather than adding another — see trailing slashes are different URLs for the extension-safety condition it needs.

HSTS, and what it does not replace

Strict-Transport-Security tells a browser to use HTTPS for a host for a given duration, so subsequent http:// requests are upgraded by the browser without a round trip to your server.

Strict-Transport-Security: max-age=31536000; includeSubDomains

Two things to be clear about:

It does not replace the HTTP redirect. The header can only be delivered over HTTPS, and a browser that has never visited you has no HSTS entry. The very first http:// request must still be redirected by your server. HSTS eliminates the hop for returning visitors, not for first contact — and crawlers do not necessarily maintain HSTS state at all.

includeSubDomains is a commitment. Every subdomain must serve valid HTTPS from the moment the header is honoured, including internal ones you forgot about. A staging or legacy subdomain on plain HTTP becomes unreachable in browsers that have cached the policy, and the caching duration is whatever max-age you set. Start with a short max-age, confirm every subdomain, then lengthen it.

Do not add preload until the rest is stable. Preload list removal is slow.

Verifying all four

for u in http://example.com/page/ http://www.example.com/page/ \
         https://example.com/page/ https://www.example.com/page/; do
  printf '%-34s ' "$u"
  curl -sIL --max-redirs 5 "$u" 2>&1 \
    | grep -cE '^HTTP/' | tr -d '\n'
  echo " hop(s)"
done

Three of those should report 2 — one redirect plus the final 200 — and one should report 1. Any number above 2 is a chain worth flattening.

Then check what happens without following redirects, so you can read the Location values directly and confirm each one names the final canonical URL rather than an intermediate. And check https://example.com specifically for a certificate error rather than a redirect: curl will tell you, and it is the failure mode that never shows up in a redirect-hop count because the connection never got far enough to produce one.

The rest of the consolidation

Once the redirects are single-hop, the remaining work is making sure nothing you emit points at a non-canonical variant. Canonical tags, sitemap entries, hreflang annotations, internal links, and any absolute URLs hard-coded in templates or feeds should all use the canonical host and protocol. A sitemap listing http:// URLs is asking the crawler to fetch a redirect on every line, which defeats the point of the file.