Do’s and don’ts of redirects with .htaccess
20/12/2023 in #http #apache #redirectI have spent many hours configuring thousands of redirects when migrating websites, following a redesign or a change of stack. In this article, I am sharing some of the do's and don'ts learned along the way and that I would recommend for anyone working on this kind of tasks to follow.
Table of content
Basics of URL structure
To understand how to set up URL redirects, it's best to understand the structure of a URL and the terms used to describe each part. Here's a quick diagram that sums it up:
https://www.fqdn.com:443/blogs/?sort=latests#fragment
└┬──┘ └─┬─────────┘└┬┘└─┬───┘└─┬─────────┘└───┬───┘
└ scheme │ port ┘ │ │ └ fragment
│ │ └ query string
└ domain └ path
For more details on URL structure, make sure to check MDN's documentation and for front-end devs used to work with the window.location
object, the following diagram can be used to compare with the previous one:
┌ protocol ┌ port
┌─┴──┐ ┌┴┐
https://www.fqdn.com:443/blogs/?sort=latests#fragment
│ └──┬───────┘ │└─┬──┘ └─┬─────────┘└─┬─────┘
│ │ └ hostname │ │ └ search └ hash
│ └──┬───────────┘ └ pathname
│ └ host │
└──────────┬───────────┘
└ origin
Basics of redirects with Apache
Apache has 2 main directives which can be used to configure redirects:
Redirect
RedirectMatch
They can be used to define redirects from an old path to a new URL simply:
Redirect /old-path https://fqdn.com/new-path
RedirectMatch /old-path https://fqdn.com/new-path
Both directives will default to a 302
temporary redirect, if you need a permanent one, make sure to specify the 301
status:
Redirect 301 /old-path https://fqdn.com/new-path
RedirectMatch 301 /old-path https://fqdn.com/new-path
The RedirectPermanent
directive can also be used instead of Redirect 301
:
RedirectPermanent /old-path https://fqdn.com/new-path
Note that the new URL can have a different domain than the original one, i.e. we can redirect from domain-a.com
to domain-b.com
.
Do’s and don’ts
Prefer specific redirects to overly generic rules
It's easier to specifically test redirected URLs when they're complete, rather than defining a matching pattern. You'll also have better control over the impact of your redirect rules on your site, and avoid false positives.
# Do
RedirectPermanent /blog/page/1 https://fqdn.com/
RedirectPermanent /blog/page/2 https://fqdn.com/
RedirectPermanent /blog/page/3 https://fqdn.com/
RedirectPermanent /blog/page/4 https://fqdn.com/
# Don't
RedirectMatch /blog/page/* https://fqdn.com/
Redirect
directives may retain parts of the old path
When a URL matches a Redirect
directive (Redirect
, RedirectMatch
, RedirectPermanent
), the part of the URL that doesn't match will be added to the redirect URL.
Thus, with the following configurations:
RedirectPermanent /foo https://fqdn.com/redirected
# or with RedirectMatch
RedirectMatch 301 ^/foo https://fqdn.com/redirected
The following redirects will happen:
https://fqdn.com/foo → https://fqdn.com/redirected
https://fqdn.com/foo/ → https://fqdn.com/redirected/
https://fqdn.com/foo/bar → https://fqdn.com/redirected/bar
https://fqdn.com/foo/bar/ → https://fqdn.com/redirected/bar/
To ensure that an exact URL redirects to another exact URL without keeping parts of the original URL, use the RedirectMatch
directive with a .*
pattern:
RedirectMatch 301 ^/foo.* https://fqdn.com/redirected
The following redirects will happen:
https://fqdn.com/fo → https://fqdn.com/fo
https://fqdn.com/foo → https://fqdn.com/redirected
https://fqdn.com/foo/ → https://fqdn.com/redirected
https://fqdn.com/foo/bar → https://fqdn.com/redirected
https://fqdn.com/foo/bar/ → https://fqdn.com/redirected
The order of redirect rules is important
Redirect rules in the .htaccess
file must be added from the most specific one to the most generic one. The first rule matching the request URL is followed and other rules are ignored.
So, with the following configuration:
RedirectPermanent /foo https://fqdn.com/bar
RedirectPermanent /foo/bar https://fqdn.com/foo/baz
The URL https://fqdn.com/foo/bar
will be redirected by the first redirect rule, while the second rule will never be evaluated.
To fix this problem, a simple trick is to sort your redirect rules in reverse alphabetical order:
RedirectPermanent /foo/bar https://fqdn.com/foo/baz
RedirectPermanent /foo https://fqdn.com/bar
Redirect
directives ignore URL parameters
If the URL that needs to be redirected contains URL parameters (e.g. ?search=keyword
), Redirect
directives will have no effect on the request.
For example, with the following configuration:
RedirectPermanent /foo/?search=news https://fqdn.com/fr/
A request with the https://fqdn.com/foo?search=news
URL will not be handled.
Redirects including URL parameters must be set up with RewriteCond
and RewriteRule
:
RewriteCond %{QUERY_STRING} ^search=news$
RewriteRule ^foo$ https://doo.com/? [L,R=301]
Make sure to enable the ´RewriteEngine´ mode before using ´RewriteCond´ directives:
´´´apacheconf RewriteEngine On RewriteBase / ´´´
Validating your .htaccess
file
Before deploying your redirect rules in production, it is important to test and validate them. First because any syntax error in a ´.htaccess´ file will result in an error 500 when accessing the website. Second, to ensure that each rule send your users to the correct location.
online htaccess tester
For simple rules, you can use the very good and useful htaccess tester by madewithlove.
studiometa/test-redirection
If you have hundreds or more rules, I would recommend to try our own tool based on Docker, which can test redirects defined in a CSV file, with host mocking:
# Create your .htaccess file with redirections to test
vim .htaccess
# Create a CSV fiels containing 2 columns: from,to
vim redirects.csv
# Run the Docker image by linking the current directy to /app
docker run -it --rm -v $PWD:/app -e "DOMAINS=fqdn.com www.fqdn.com" studiometa/test-redirection redirects.csv
Make sure to check out the GitHub repository to learn more!