Do you want to come to Andalu-SEO?
Andalu-SEO

URL Uppercase and Lowercase

Managing Uppercase in a URL

URL Uppercase and Lowercase
Author:
Carlos Sánchez
Topics:
Crawling
,
servers
Publication Date:
2026-01-07

Last Review:
2026-01-07

A URL can be distinguished from another simply by uppercase letters. This is known as Case Sensitive. By default, even if they are different URLs, certain technologies, such as the WordPress CMS, generate the same content.

This creates a page duplication as if it were a different version, and sometimes it has a self-canonical and a 200 response code. That is, it can lead to cannibalization and even be an entry point for negative SEO attacks.

That is, if you have the URL:

https://example.com/content/

It is a different URL from:

https://example.com/Content/

Depending on the CMS or framework, both may provide exactly the same content.

Hopefully, the canonical may point to the lowercase version. But depending on what is used for the canonical, it is not always guaranteed.

To avoid these issues, one can resort to the canonical, and many SEO extensions/plugins do this.

The 3 most accepted options for this practice are:

Redirect from Uppercase to Lowercase

This change must be made on the server. Since there are programming languages that allow greater flexibility, I do a combination between the server and the language being used. In this case, I combine Apache with PHP.

.htaccess

<IfModule mod_rewrite.c>
#Redirect from uppercase to lowercase
RewriteEngine On
RewriteBase /
# Look for any uppercase letters in the URL
RewriteCond %{REQUEST_URI} [A-Z]
# Avoid redirecting media content that could cause redirects to image files
RewriteCond %{REQUEST_URI} !\.(css|js|woff|woff2|svg|jpg|jpeg|webp|avif|mp4|pdf|png|gif|bmp)$ [NC]
# Call a .php file placed in the root or the same folder as .htaccess where we will put the code below

RewriteRule ^(.*)$ lowercase.php?q=$1 [L,QSA]
</IfModule>

nginx

location / {
if ($request_uri ~* "[A-Z]") {
rewrite ^(.*)$ /lowercase.php?q=$1 last;
}
if ($request_uri ~* "\.(css|js|woff|woff2|svg|jpg|jpeg|webp|avif|mp4|pdf|png|gif|bmp)$") {
break;
}
}

PHP File

It must have the same name as the file being called, as follows.

<?php
// Redirect the uppercase version to the lowercase version. Called from .htaccess
if(isset($_GET['q'])) {
$url = strtolower($_GET['q']);
$request_uri = $_SERVER['REQUEST_URI'];
// Here we make a conditional so that it always has a trailing slash, avoiding duplication. Otherwise, it should be reversed: remove the slash if it exists
if($url !== $_GET['q'] || substr($request_uri, -1) !== '/') {
if(substr($request_uri, -1) === '/') {
$new_url = str_replace($_GET['q'], $url, $request_uri);
} else {
$new_url = str_replace($_GET['q'], $url, $request_uri) . '/';
}
header("HTTP/1.1 301 Moved Permanently");
header("Location: ".$new_url);
exit();
}
}
?>

Extra: Hashbangs

URL fragments cannot be redirected from the server since it is an interaction between the browser and the user. The only way to redirect these URLs is through JavaScript, although this is usually not a problem since, with few exceptions, Google does not crawl these parameters. Therefore, this implementation would simply be to improve user experience.

But it can be done using JavaScript. Here is an example of how to redirect all fragments to their lowercase version, replacing "_" with "-". This code also ensures that the same scrolling behavior is maintained as if the redirect were applied correctly.

document.addEventListener('DOMContentLoaded', function() {
var fragment = decodeURI(window.location.hash.substr(1));
if (fragment.indexOf('') !== -1) {
var newFragment = fragment.replaceAll('', '-').toLowerCase();
history.replaceState(null, '', window.location.href.replace(fragment, newFragment));
} else if (fragment.match(/[A-Z]/)) {
var newFragment = fragment.toLowerCase();
history.replaceState(null, '', window.location.href.replace(fragment, newFragment));
}
// If there is a URL fragment, find the corresponding element and scroll to it
if (fragment) {
var targetElement = document.getElementById(fragment);
if (targetElement) {
setTimeout(function() {
targetElement.scrollIntoView();
}, 100); // Wait 100ms before scrolling
}
}
});

Code explanation.

For this code to work correctly, it should perform the scrolling effect to the corresponding ID. Normally, the browser does this automatically. But if we implement this type of redirection, we need to provide a little help.

To make the browser scroll to the corresponding section, you must use the scrollIntoView() property on the element that has the ID corresponding to the URL fragment.

In this code, after changing underscores and uppercase letters, it checks if there is a URL fragment. If so, it searches for the corresponding element in the DOM using document.getElementById(), and if found, it scrolls to it using scrollIntoView(). This ensures the page scrolls to the correct section when loaded with a URL fragment in the address bar.

In this case, to force the scroll, it is useful to apply a brief delay before calling scrollIntoView() to allow the browser to finish rendering the page.

Apache code explanation
Nginx code explanation

Note that excessive use of if statements in Nginx is not recommended due to its impact on performance.

References

If you like this article, you would help me a lot by sharing my content:
Interested in Advanced SEO Training?

I currently offer advanced SEO training in Spanish. Would you like me to create an English version? Let me know!

Tell me you're interested
You might be interested in other articles:
SEO Articles

If you liked this post, you can always show your appreciation by liking this LinkedIn post about this same article.

Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Aquí tienes nuestra política de Cookies.