Home > Software design >  Dynamically pointing multiple domains to a single domain's subfolder with .htaccess
Dynamically pointing multiple domains to a single domain's subfolder with .htaccess

Time:10-30

In this example, I manage domain.com. Inside it, I have a store template in php that loads the selected store dinamically:

  • domain.com/store1
  • domain.com/store2
  • domain.com/store3

The nice urls are being generated by the .htaccess below. What's really being loaded in the back, is this:

  • domain.com/store/index.php?store=store1
  • domain.com/store/index.php?store=store2
  • domain.com/store/index.php?store=store3

Each store has internal links, such as these:

  • domain.com/store1/catalogue
  • domain.com/store1/catalogue/instruments
  • domain.com/store1/catalogue/instruments/guitars
  • domain.com/store1/electric-guitar/1337

It works exactly as expected. These are the .htaccess rules to make that work in domain.com. The store query string (store1, store2, store3) in each RewriteRule determines which store is being loaded:

# permalinks
RewriteEngine on

# errors
ErrorDocument 404 /error.php?error=404

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]

#################################################

# store
RewriteRule ^([0-9a-zA-Z-]{2,50})/?$ store/index.php?store=$1 [QSA,L]

# store: catalogue
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/?$ store/catalogue.php?store=$1 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2&subcat=$3 [QSA,L]

# store: products
RewriteRule ^([0-9a-zA-Z-]{2,50})/([0-9a-zA-Z-]{1,150})/([0-9] )$ store/product.php?store=$1&slug=$2&id_product=$3 [QSA,L]

Now, here comes the tricky part. Some of the clients, would like to use their own domains, so that store1.com/* loads the same content of domain.com/store1/* (without using a redirect).

Example:

  • store1.com => domain.com/store1/
  • store1.com/catalogue => domain.com/store1/catalogue
  • store1.com/catalogue/instruments => domain.com/store1/catalogue/instruments
  • store1.com/catalogue/instruments/guitars => domain.com/store1/catalogue/instruments/guitars
  • store1.com/electric-guitar/1337 => domain.com/store1/electric-guitar/1337

You get the idea.

All the domains (domain.com, store1.com, store2.com, store3.com) are configured in the same Apache Web Server environment (using virtual hosts).

The question is: Can this be implemented in a .htaccess file in each one of the store's domain root path dynamically or inside the virtualhost .conf file? If so, how?

CodePudding user response:

Make sure all your custom "store" domains (eg. store1.com, store2.com, etc.) are defined as ServerAlias in the domain.com vHost and so resolve to the same place as domain.com.

You can then rewrite requests for the custom store domain to prefix the URL-path with the store-id (the domain name) and then use your existing rules unaltered.

For example, a request for store1.com/catalogue/instruments is internally rewritten to /store1/catalogue/instruments and then processed by the existing rules as usual.

The following directives should go before the existing # store rules:

# Internally rewrite the request when a custom domain is used
#  - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.] )
RewriteCond %{REQUEST_URI}@%1 !^/([^/] )/.*@\1
RewriteRule (.*) %1/$1

And that's basically it, although you may also decide to implement a canonical redirect - see below.

Explanation of the above directives:

  • The first condition simply excludes requests for the main domain.com (which should already have the relevant store-id prefixed to the URL-path).

  • %{REQUEST_URI} !\.\w{2,4}$ - The second condition avoids rewriting requests for static resources (images, JS, CSS, etc.). Specifically, it excludes any request that ends in - what looks like - a file extension.

  • %{HTTP_HOST} ^([^.] ) - The third condition captures the requested domain name before the TLD which is then accessible using the %1 backreference later and used to prefix the URL-path. This assumes there is no www subdomain (as stated in comments). eg. Given a request for store1.com or store2.co.uk, store1 or store2 respectively are captured.

  • %{REQUEST_URI}@%1 !^/([^/] )/.*@\1 - The fourth condition checks that the URL-path is not already prefixed with the domain name (captured above). This is primarily to ensure that rewritten requests are not rewritten again, causing a rewrite loop. This is achieved using an internal backreference (\1) that compares the first path-segment in the URL-path against the previously captured domain name (%1).

  • (.*) %1/$1 - Finally, the request is internally rewritten, prefixing the domain name to the requested URL-path.


Ignore existing files (may not be required)

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]

You've not stated how you are referencing your static resources (images, JS, CSS, etc.), but you may need to modify this rule to also match requests for existing files. (Although the condition I added to the rule above may already be sufficient to exclude these requests.) For example:

# ignore existing directories or files
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]

Canonical redirect (optional)

You should also consider redirecting any direct requests of the form store1.com/store1/catalogue/instruments back to the canonical URL store1.com/catalogue/instruments - should these URLs ever be exposed/discovered. A request of the form store1.com/store2/catalogue/instruments will naturally result in a 404, so is not an issue.

For example, the following would go immediately after the ErrorDocument directive in your existing rules:

# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.] )
RewriteCond %{REQUEST_URI}@%1 ^/([^/] )/.*@\1
RewriteRule ^(?:[^/] )(/.*) $1 [R=301,L]

Test first with a 302 (temporary) redirect to avoid potential caching issues.

This is basically the reverse of the above rewrite, but applies to direct requests only. The check against the REDIRECT_STATUS env var ensures that only direct requests from the client and not rewritten requests by the later rewrite are processed.


Summary

With the two rule blocks in place...

# permalinks
RewriteEngine on

# errors
ErrorDocument 404 /error.php?error=404

# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.] )
RewriteCond %{REQUEST_URI}@%1 ^/([^/] )/.*@\1
RewriteRule ^(?:[^/] )(/.*) $1 [R=301,L]

# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Internally rewrite the request when a custom domain is used
#  - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.] )
RewriteCond %{REQUEST_URI}@%1 !^/([^/] )/.*@\1
RewriteRule (.*) %1/$1

#################################################

# store

:
: existing directives follow
:
  • Related