Technical SEO

Canonical Tags and Duplicate URLs: www, Slashes, Parameters

Four ways one page gets several addresses, the fix for each in nginx, Apache and HTML, and the canonical mistakes that quietly undo your work.

A round convex mirror reflecting a building facade
Photo: Gera Cejas on Pexels

Publish one page and your server may answer for 8 addresses. Take example.com/guide and combine http or https, with or without www, with or without a trailing slash: 2 x 2 x 2 is 8 URLs that can return the same content, before a single tracking parameter is added. Google treats such copies as duplicates and picks one to show, and the one it picks is not always the one you would have chosen.

Four sources of duplicates, and the right tool for each

Google’s documentation lists four causes of duplicate URLs. The fix depends on whether a visitor ever needs the duplicate.

SourceExampleBest fixWhy
Protocolhttp://example.com/guide and https://example.com/guide301 redirect to httpsNobody needs the insecure copy
Hostwww.example.com and example.com301 redirect from one to the otherNobody needs both
Trailing slash/guide and /guide/301 redirect to the form you use everywhereSame content, two addresses
Parameters/shoes/?sort=price, ?utm_source=news, session IDsrel="canonical" on the variant, pointing at the clean URLThe variant must stay reachable, so it cannot be redirected

Google ranks the ways of stating a preference. Redirects are a strong signal, rel="canonical" is a strong signal as well and can be sent as an HTTP header for files that have no HTML head, and listing a URL in a sitemap is a weak one. Use the strongest tool that fits: redirect when a visitor never needs the duplicate, canonical when they do, and the sitemap only to confirm what the other 2 say. Google also advises against using robots.txt to canonicalize. A page it cannot crawl is a page whose canonical tag it never reads.

What makes any of this work is agreement. The redirect target, the canonical tag, the sitemap entry and your internal links should all use the same absolute address, with the same protocol, host and slash style. Every disagreement is a signal pointing the other way.

Redirect what visitors never need

Hosts, protocol and slashes are all best handled by redirects, and the point is to get from any variant to the final address in one hop. This is the nginx version, with 2 small server blocks in front of your real one.

# Port 80: any host, one hop to the final HTTPS address
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# Port 443, www: one hop to the bare domain
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;                        # nginx 1.25.1 or later
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://example.com$request_uri;
}

$request_uri keeps the path and the query string, so deep links survive. The www block still needs a certificate that covers www.example.com, or visitors meet a browser warning before the redirect happens. Inside your main server block, a missing trailing slash can be added with one line, which also keeps the query string:

# Extensionless paths only: /guide becomes /guide/
rewrite ^([^.]*[^/])$ $1/ permanent;

Keep it away from API paths and test it before you rely on it. You may not need it: on a static site served with try_files $uri $uri/ =404;, our nginx answers /seo-checker with a 301 to /seo-checker/ by itself. If you prefer addresses without the slash, the mirror rule is rewrite ^/(.+)/$ /$1 permanent;. Either choice works, as long as it is the only one.

On Apache, mod_dir already adds the slash to real directories, and the host and protocol rule fits in .htaccess or the virtual host:

RewriteEngine On

# http or www -> https://example.com in one hop
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

If a proxy or a CDN terminates HTTPS in front of Apache, %{HTTPS} can read off on every request and send you into a loop. Test on the X-Forwarded-Proto header instead.

Write the canonical tag correctly

Where a redirect is not possible, say which address is the original with one tag in the head of the page, written as an absolute URL.

<head>
  <link rel="canonical" href="https://example.com/shoes/">
</head>

On https://example.com/shoes/ that tag points at itself, which is the recommended pattern: a self-referencing canonical on the clean page, and the same tag on every variant such as /shoes/?sort=price. For a file with no HTML head, such as a PDF copy of a page, the same statement travels as a response header: Link: <https://example.com/shoes/>; rel="canonical". The same markup goes wrong in 4 common ways.

<!-- 1. Relative URL -->
<link rel="canonical" href="/shoes/">

<!-- 2. Wrong host, copied from staging or from a template -->
<link rel="canonical" href="https://staging.example.com/shoes/">

<!-- 3. Canonical combined with noindex -->
<meta name="robots" content="noindex">
<link rel="canonical" href="https://example.com/shoes/">

<!-- 4. Two canonicals that disagree -->
<link rel="canonical" href="https://example.com/shoes/">
<link rel="canonical" href="https://www.example.com/shoes/">

A relative URL resolves against whichever host served the page, so on the www copy it points at the www copy: the duplicate vouches for itself, and Google asks for absolute URLs. A wrong host tells Google your live page is a copy of someone else’s, and our AdSense checker fails a homepage canonical on another host for that reason. Canonical plus noindex sends 2 opposite messages, one asking Google to drop the page and the other naming it as preferred, so pick one: noindex alone for a page that must stay out of results, a redirect or a canonical for a true duplicate. Duplicate canonicals usually appear when a theme and an SEO plugin each print one and disagree about www or the slash; view the source and search for canonical to count them.

A canonical tag is not a substitute for a status code either. A page you deleted should answer 404 or 410. Pointing it at the homepage hides the problem instead of fixing it, and error pages that answer 200 are the soft 404s covered in Soft 404s and HTTP status codes.

Translations are not duplicates, and some duplicates hide well

A French page is not a copy of the English one, and canonicalizing one language to the other tells Google to index only that one. Each version should name itself as canonical and list every version, itself included, as an alternate. This is the markup our own French guide page carries:

<link rel="canonical" href="https://greenlightmysite.com/fr/guide/">
<link rel="alternate" hreflang="en" href="https://greenlightmysite.com/guide/">
<link rel="alternate" hreflang="fr" href="https://greenlightmysite.com/fr/guide/">
<link rel="alternate" hreflang="x-default" href="https://greenlightmysite.com/guide/">

The English page carries the same 3 alternates with its own address as canonical. Alternates that are not reciprocal may be ignored, so check both directions after you add a language.

Some duplicates do not show up in the 4-way count. Paths are case-sensitive in URLs, so /Guide and /guide are 2 addresses whenever your server answers both, as some content management systems and application routers do. /index.html can answer next to /, and a doubled slash in //guide can slip through a careless rewrite. Redirect each to the form you use. Tracking parameters deserve a rule of their own: never link internally to a URL with utm_ parameters, because every such link hands Google a duplicate to sort out. To find duplicates you already have, run the sitemap script from our guide to finding low value content. Its duplicate-title and canonical-elsewhere flags point at the pages worth a closer look.

Check what your server and Google really do

Do not trust the configuration, trust the requests. This loop follows every variant of one existing page and prints how many redirects each needed and where it ended. Set host to your own domain and path to a page that exists, without the trailing slash.

host=greenlightmysite.com
path=/seo-checker
for u in http://$host$path http://www.$host$path https://www.$host$path https://$host$path https://$host$path/; do
  curl -sIL -o /dev/null -w "%{num_redirects} redirect(s) -> %{url_effective}\n" "$u"
done

On 7 October 2026 this printed the following for our own site.

2 redirect(s) -> https://greenlightmysite.com/seo-checker/
3 redirect(s) -> https://greenlightmysite.com/seo-checker/
2 redirect(s) -> https://greenlightmysite.com/seo-checker/
1 redirect(s) -> https://greenlightmysite.com/seo-checker/
0 redirect(s) -> https://greenlightmysite.com/seo-checker/

All 5 variants end on one address, which is what matters, and every hop is a 301. The weak spot is the second line: our server first upgrades http://www to https://www, then drops the www, then adds the slash. Google follows up to 10 redirect hops, so nothing breaks, but it is 3 redirects where 2 would do, and the port 80 block above removes one of them. Our AdSense checker passes a homepage chain of 0 or 1 redirects, warns at 2 and fails at 3 or more.

Use 2 more views to complete the picture. Run the SEO checker on a page: it reports whether the canonical exists and whether it points at the page itself. The AdSense checker adds the homepage canonical host, whether www and the bare domain agree, and the length of the redirect chain. Then open URL Inspection in Search Console, which shows the canonical you declared next to the one Google selected. When they differ, one of your signals contradicts another, so check first that your sitemap and internal links use the variant you declared. The certificate and HSTS side of the same chain is covered in HTTPS done right.

Sources

Who stands behind this

GreenlightMySite is run by Stéphane Cardon, a network and cybersecurity engineer. We write from running real sites through real reviews, and every technical claim here is either linked to a primary source or measured with our own tools.

About · Editorial policy · LinkedIn