Link rel=canonical header vs the HTML tag: the mismatch
A page declaring one canonical in an HTTP header and a different one in the markup. squirrel parses the header and compares.
What the canonical Link header is
A canonical can be declared in an HTTP header as well as in the markup: Link: <https://example.com/page>; rel="canonical". The header exists for responses that have no HTML to put a tag in, which is what makes it the right way to canonicalise a PDF or an image.
For an HTML page there is no reason to use both. When a page sends both and they disagree, a search engine has two contradictory declarations from the same page and has to pick, which is exactly the ambiguity the canonical was meant to remove.
What squirrel checks
The rule runs on every crawled page. It parses the Link response header, splitting on commas and matching entries of the form <url>; rel=canonical, with the rel value optionally quoted and matched case-insensitively. It emits one check named canonical-header:
- Skipped with the message
No Link header canonical foundand the reasonPage has no HTTP Link canonical header. This is the normal case. - At info status with the message
Canonical only in Link header (no HTML tag)when the header declares one and the markup does not, which is valid. - Passing with the message
HTML canonical matches Link headerwhen both are present and agree. - Failing with the message
Canonical mismatch between HTML and Link header, the valueHTML: <url>, Header: <url>and the expectationBoth should match.
The comparison normalises both URLs first: the fragment is dropped, a trailing slash is removed from the end of the whole serialised URL except on the site root, and the string is lowercased. Because the trim is on the serialised form rather than the path, https://example.com/page/?x=1 keeps its path slash. So https://example.com/Page/ in the header and https://example.com/page in the markup are treated as agreeing.
The comma split is a simplification of the Link header grammar. A URL containing a literal comma can be split across two entries, though a comma in a path is rare enough that it does not come up in practice.
How to fix it
Pick one. For an HTML page, that is the tag:
<link rel="canonical" href="https://example.com/guides/build-cache" />Then stop sending the header, which usually means removing a rule at the CDN or reverse proxy that was added for a different content type and applied too broadly.
The common false positive is a CDN adding the header automatically with the request URL, on a page whose markup canonicalises somewhere else deliberately. The mismatch is real and the fix is to stop the CDN adding it.
| Rule ID | core/canonical-header |
| Category | Core SEO |
| Scope | Per-page |
| Severity | warning |
| Weight | 7/10 |
Enable / disable
Disable this rule
[rules]
disable = ["core/canonical-header"]Disable all Core SEO rules
[rules]
disable = ["core/*"]Enable only this rule
[rules]
enable = ["core/canonical-header"]enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.
Related rules
- core/canonical: the markup declaration this rule compares against.
- core/canonical-form-drift: canonicals across the site that disagree in form.
- crawl/robots-meta-conflict: the same kind of contradiction in indexing directives.
- crawl/indexability-conflicts: robots.txt disagreeing with the meta tags and headers about indexing.
Core SEO findings ship in every audit next to the performance, security and agent experience rules. See Fix SEO issues with AI for how an agent works through a report.
References
Check your site
Run squirrel audit https://example.com and open the Core SEO section of the report. Each mismatching page shows both declared values. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.