← All articles
Technical SEO9 min read

Canonical Tags: Eight Mistakes That Quietly Cost You Rankings

Canonical tags fail silently. Eight ways they break in production, the exact HTML that causes each one, and the curl commands that expose them.


A broken canonical tag never throws an error. The page renders, the browser is happy, the CMS reports success, and somewhere in Google's indexing pipeline your carefully written product page gets folded into a URL you didn't choose. Because the failure is silent and the feedback loop is weeks long, canonical bugs survive redesigns, migrations and audits. Here are the eight that show up most often in real codebases, what each one actually does to your index coverage, and how to catch them with a terminal instead of a hunch.

First: rel=canonical is a hint, not a directive

This is the single most misunderstood thing about canonicals, and almost every mistake below is downstream of it.

When Google finds several URLs serving substantially the same content, it groups them into a cluster and picks one URL as the representative — the one that gets indexed and shown in results. Your <link rel="canonical"> is one input into that decision. Others include:

  • internal linking patterns (which version do you actually link to?)
  • which URL is in your XML sitemap
  • redirect targets
  • HTTPS preference over HTTP
  • URL cleanliness and length
  • hreflang cluster membership
  • external links pointing at each variant

If your canonical contradicts all of those signals, Google will frequently ignore it. Search Console's URL Inspection tool makes this explicit: it reports both User-declared canonical and Google-selected canonical, and when those differ you are looking at a hint that was overruled. Bing behaves similarly. Compare this to noindex, which is a directive — see robots.txt vs meta robots vs X-Robots-Tag for where the hard boundaries actually live.

The practical consequence: a canonical works when it agrees with the rest of your signals and merely resolves ambiguity. It does not work as a lever to force an outcome the rest of the site contradicts. Every mistake below is a form of contradiction.

1. Canonical pointing at a URL that redirects

Very common after a trailing-slash or HTTPS migration. The page canonicalises to a URL which then 301s somewhere else:

<!-- served at https://example.com/blog/canonical-guide/ -->
<link rel="canonical" href="http://example.com/blog/canonical-guide" />

That target 301s to https://example.com/blog/canonical-guide/ — the page you started on. You've built a one-hop loop. Google generally follows the redirect and lands back on the original, so the damage here is usually mild: wasted crawl, and a signal that your configuration is unreliable. But the same pattern with a chain — canonical → 301 → 301 → 404 — leaves the cluster with no valid representative and Google falls back to guessing. Redirect hops also dilute and delay signal consolidation; redirect chains covers the mechanics.

Rule: a canonical target must return 200 directly. No redirect, no exceptions.

2. Canonical pointing at a noindex page

<!-- /products/blue-widget?color=blue -->
<link rel="canonical" href="https://example.com/products/blue-widget" />
<!-- /products/blue-widget -->
<meta name="robots" content="noindex, follow" />

You have told Google two contradictory things: this cluster's representative is X, and X must not be indexed. Google's stated guidance is that noindex on a canonical target can cause the noindex to be applied to the whole cluster — you lose every URL in the group, not just the target. In practice the outcome is unstable and depends on which signal is discovered first, which is precisely why you should never ship this state.

It appears most often when a staging noindex is left on a template, or when an "out of stock" rule adds noindex to a product that other variants still canonicalise to. Audit for it by resolving every canonical target and checking its robots meta.

3. Relative canonicals that resolve to the wrong thing

Relative URLs in canonicals are legal. They are also a foot-gun:

<link rel="canonical" href="/blog/post" />

That resolves against the document's base URL — which is the current URL, or whatever a <base> element says, if one exists. On https://www.example.com/blog/post/ with a stray <base href="https://cdn.example.com/"> in the head, your canonical now points at a CDN host. Relative canonicals also silently do the wrong thing when the same HTML is served from a preview domain, a staging host, or a reverse-proxied vendor subdomain: each copy self-canonicalises to itself, and you've published a duplicate cluster with no consolidation.

<!-- correct: absolute, protocol included, host explicit -->
<link rel="canonical" href="https://www.example.com/blog/post/" />

Always absolute. Include the scheme. Include the host. Match the trailing slash to whatever your server actually serves at 200.

4. Conflicting canonicals: HTML head vs HTTP Link header

You can declare a canonical in two places, and both are valid:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: <https://example.com/report>; rel="canonical"
<link rel="canonical" href="https://example.com/report?v=2" />

When they disagree, behaviour is undefined in the sense that matters: Google treats multiple conflicting canonicals as a signal to ignore. Same for two <link rel=canonical> elements in one head. Same for a canonical injected by JavaScript that duplicates a different server-rendered one — the rendered DOM is what gets used, but if both survive rendering you have two.

The header form usually arrives from a CDN rule, a WordPress SEO plugin, or a framework middleware nobody remembers adding. It is invaluable for non-HTML resources (a PDF cannot have a <head>), but for HTML pages, pick exactly one mechanism and enforce it.

A related failure: canonicals placed outside <head>. Anything after the first element that implicitly closes the head — a stray <div>, an unclosed comment, a script that errors — puts your <link> in the body, where it is ignored. Framework-generated heads are usually safe here; hand-edited templates and tag managers are not. Next.js SEO problems walks through the App Router specifics.

5. Self-referencing canonicals: when they actually help

A self-referencing canonical is not required, and Google has said as much. It is still worth having, for one concrete reason: it defends against URL variants you don't control.

<!-- served at https://example.com/pricing -->
<link rel="canonical" href="https://example.com/pricing" />

When someone links to /pricing?utm_source=newsletter, or an affiliate appends ?ref=abc, or a marketplace adds ?fbclid=, your server serves the same page. Without a self-canonical, each parameterised URL is a candidate representative. With one, every variant declares the clean URL. Same for /pricing/index.html, uppercase paths on case-insensitive servers, and session IDs.

The cost is that a self-canonical generated from request.url is worse than useless — it will faithfully echo back the tracking parameter you were trying to strip. Generate canonicals from your routing layer, from the canonical path you know the resource has, never from the incoming request URL.

6. Protocol and host mismatch (http/https, www/non-www)

<!-- served at https://www.example.com/about -->
<link rel="canonical" href="http://example.com/about" />

Four hostname-ish variants of every page exist by default: http://example.com, http://www.example.com, https://example.com, https://www.example.com. If all four serve 200 and each self-canonicalises, you have four clusters instead of one, and links spread across them do not combine.

The fix is not primarily the canonical tag — it's redirects. Pick one host and one protocol, 301 the other three to it in a single hop, and make canonicals match the survivor exactly. The canonical then agrees with the redirects, the sitemap and the internal links, and consolidation is unambiguous. Getting this wrong tends to produce the classic http://example.comhttps://example.comhttps://www.example.com two-hop chain; collapse it.

7. Canonicalising page 2 of a paginated set to page 1

This one costs real content, and it is still recommended in a depressing number of old blog posts.

<!-- /articles?page=2 — WRONG -->
<link rel="canonical" href="https://example.com/articles" />

Page 2 is not a duplicate of page 1. It contains different items. Telling Google that page 2's representative is page 1 means everything linked only from page 2 onward may never be discovered or indexed, because you've declared those pages non-canonical and their outgoing links get devalued accordingly.

rel="next" / rel="prev" are no longer used by Google as indexing signals — that was confirmed in 2019. What remains is straightforward:

<!-- /articles?page=2 — correct -->
<link rel="canonical" href="https://example.com/articles?page=2" />

Self-canonicalise every page in the series, keep real crawlable <a href> links between pages (not a JS-only "load more"), and let each page stand on its own. If you also expose a "view all" page and it is genuinely usable, canonicalising the paginated pages to it is defensible — but only if that page really contains everything.

8. Cross-domain canonicals

Canonicals work across hostnames, which makes them the right tool for syndication:

<!-- on syndicationpartner.com/our-guest-post -->
<link rel="canonical" href="https://example.com/original-guest-post" />

That tells Google the original deserves the ranking. Two caveats. First, it is still a hint — a syndication partner with vastly more authority may keep the placement anyway. Second, the direction matters and is often reversed by accident: a white-label or multi-tenant setup where every tenant's page canonicalises to the vendor's master copy will deindex all the tenants. If you run a platform that generates customer sites, check what your template emits.

Cross-domain canonicals also break in the mundane way — the target domain expires, changes structure, or drops the page. A canonical to a 404 on someone else's domain is a cluster with no home.

Verifying with curl

Everything above is checkable from a terminal in under a minute.

Read the canonical from the HTML and the headers at once:

curl -sD - https://example.com/pricing -o /tmp/page.html \
  | grep -Ei '^(HTTP/|location:|link:)'

grep -Eio '<link[^>]+rel=["'\'']?canonical[^>]*>' /tmp/page.html

If the grep on the HTML returns more than one line, you have the multiple-canonical bug from mistake 4. If a Link: header appears and the HTML has one, compare them.

Check the canonical target resolves to 200 with no hops:

curl -so /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' \
  -L https://example.com/pricing

num_redirects must be 0 for a canonical target. Anything else is mistake 1.

Check the target isn't noindexed:

curl -sD /tmp/h https://example.com/pricing \
  | grep -Eio '<meta[^>]+name=["'\'']?robots[^>]*>'; grep -i 'x-robots-tag' /tmp/h

Check all four host/protocol variants:

for u in http://example.com/about http://www.example.com/about \
         https://example.com/about https://www.example.com/about; do
  printf '%s -> ' "$u"
  curl -so /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' -L "$u"
done

Exactly one should return 0 redirects. The other three should land on it.

One caveat on all of this: curl sees the server response, not the rendered DOM. If your canonical is injected client-side, curl will show nothing and you need Search Console's URL Inspection ("View crawled page" → rendered HTML) or a headless browser to confirm. Server-render your canonicals and this problem disappears.

Quick reference

Mistake Symptom in Search Console Fix
Canonical → redirect "Alternate page with proper canonical tag" on odd URLs; extra crawl Point at the final 200 URL
Canonical → noindex page Pages dropping out of the index in groups Remove noindex or repoint the canonical
Relative canonical Duplicate clusters on preview/CDN hosts Absolute URLs everywhere
Head vs header conflict "Duplicate, Google chose different canonical" One mechanism only
No self-canonical Parameterised URLs indexed Self-canonical from routing, not request.url
Host/protocol mismatch Split link equity, both variants indexed One-hop 301s + matching canonicals
Page 2 → page 1 Deep content never indexed Self-canonicalise each page
Wrong-direction cross-domain Whole tenant/partner site deindexed Reverse the direction; verify the target 200s

Where tooling fits

Most of these are mechanical checks: resolve the canonical, follow it, inspect the target's status and robots directives, compare host and protocol against the requested URL. SEO Fix Agent's Site Audit runs that class of check on the page you point it at — it fetches the page, reads the canonical from both the head and the HTTP headers, and flags mismatches against the requested URL as part of its technical category. It audits a single page per run rather than crawling a whole site, so the usual workflow is to check one representative URL per template. The AI Prompt Studio then compiles whatever it found into a severity-ordered prompt you can paste straight into Claude Code or Cursor, which matters here because canonical bugs are almost always template bugs — one fix in one layout file, applied everywhere.

The broader point stands regardless of tooling. Canonicals are a consolidation hint, and hints only work when they agree with your redirects, your sitemap, your internal links and your robots directives. Get those four saying the same thing and the canonical tag becomes almost boring. That is the goal.

#canonical#duplicate-content#technical-seo#html-head#crawling

Audit your page, then ship the fix

SEO Fix Agent runs 30+ technical, content and AI-search checks on a page, then compiles every finding into a severity-ordered prompt your coding agent can execute. 75 free credits, no card.

Start free →

Keep reading