Forum Moderators: phranque
I'm facing a problem with URL rewriting subdomain techniques.
My goal is to access two parts of an application (public and admin) located in two subdirectories (pages/public and pages/admin/) with www.domain.com heading to pages/public and webadmin.domain.com heading to pages/admin/.
My hosting service oriented me towards :
RewriteEngine On
RewriteCond %{REQUEST_URI} !/pages/public
RewriteRule (.*) /pages/public/$1 [QSA,L]
I tried to add rules for admin part but I could not find my way through. Last attempts left me with infinite looping or unreachable destination :
RewriteEngine On
RewriteCond %{REQUEST_URI} !/pages/public
RewriteCond %{REQUEST_URI} !/pages/admin
RewriteCond %{HTTP_HOST} !^webadmin\.domain\.com
RewriteCond $1 !^www/
RewriteRule (.*) /pages/public/$1 [QSA,L]
RewriteCond %{HTTP_HOST} ^webadmin\.domain\.com
RewriteCond $1 !^webadmin/
RewriteRule (.*) http://www.domain.com/pages/admin/$1 [QSA,S,L]
Thanks for any help.
[Rewrite requests for] www.example.com/ to /pages/public/ and [rewrite requests for] webadmin.example.com/ to /pages/admin/
RewriteEngine on
#
# Rewrite requests for www.example.com/xyz to /pages/public/xyz
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^(.*)$ /pages/public/$1 [L]
#
# Rewrite requests for webadmin.example.com/xyz to /pages/admin/xyz
RewriteCond %{HTTP_HOST} ^webadmin\.example\.com
RewriteCond $1 !^pages/admin/
RewriteRule ^(.*)$ /pages/admin/$1 [L]
Once you get this working, you should add rules so that direct access to /pages/admin and /pages/public is ot possible. These requests should be redirected back to the root of the correct subdomain. Don't worry about that until you get the two rules above working.
Jim
I had this working in the meantime :
RewriteCond %{HTTP_HOST} ^webadmin.example.com$
RewriteCond %{REQUEST_URI} !^/pages/admin
RewriteRule ^(.*)$ pages/admin/$1 [L]
RewriteCond %{HTTP_HOST} ^www.example.com$
RewriteCond %{REQUEST_URI} !^/pages/public
RewriteRule ^(.*)$ pages/public/$1 [L]
which seems to make the job the same.
Is it correct ?
I also could force www.example.com instead of example.com with :
RewriteCond %{HTTP_HOST} ^example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
Is this the correct approach ?
Is R=301 necessary/recommended ?
And what would be the solution to restrict access to public and admin subdirectories ?
thanks again.
2) Do not end-anchor your positive-match hostnames with "$", or your rules will be defeated by valid but "non-standard" requests like FQDN requests or requests with a port number appended, e.g. www.example.com./page or www.example.com:80/page or even www.example.com.:80/page.
3) For the same reason, I suggest improving your hostname canonicalization by using two rules:
# Canonicalize variant webadmin hostname requests (e.g. www.webadmin.www.example.com.:80)
RewriteCond %{HTTP_HOST} ^([^.]*\.)*webadmin\.([^.]*\.)*example\.com
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://webadmin.example.com/$1 [R=301,L]
#
# Canonicalize all other variant hostname requests (non-www or bogus subdomains)
RewriteCond %{HTTP_HOST} !^www\.example\.com$
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
4) Put the domain canonicalization redirect(s) before the internal rewrites, to avoid having an external redirect expose the internal filepath to the clients (browsers, robots, etc.)
A comment: In regular expressions and mod_rewrite, every single character matters. Anything less than absolutely-perfectly-correct can and will likely have unintended side-effects. The code is typically small -- but it's very powerful; One single typo can (if you are lucky) take down your server immediately. If you are unlucky, a little typo or omission can slowly erode your search engine rankings over time -- or open up your site to malicious exploitation. For this reason, every character I typed above was intentional and justified.
Jim
[edited by: jdMorgan at 5:21 pm (utc) on April 14, 2009]
# Canonicalize direct client requests for admin subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /admin(/[^\ ]*)?\ HTTP/
RewriteRule ^admin(/.*)?$ http://webadmin.example.com$1 [R=301,L]
#
# Canonicalize direct client requests for public subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public(/[^\ ]*)?\ HTTP/
RewriteRule ^public(/.*)?$ http://www.example.com$1 [R=301,L]
Checking THE_REQUEST (the HTTP request as received from the client) is mandatory in order to avoid interaction with your existing subdirectory rewrites. Without this check, the two sets of rules would interact, causing an 'infinite' rewrite/redirect loop.
Jim
[edited by: jdMorgan at 5:22 pm (utc) on April 14, 2009]
So my final set of rules look like this :
# Canonicalize direct client requests for admin subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /admin(/[^\ ]*)?\ HTTP/
RewriteRule ^admin(/.*)?$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize direct client requests for public subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public(/[^\ ]*)?\ HTTP/
RewriteRule ^public(/.*)?$ http://www.example.com/$1 [R=301,L]
# Force example.com to www.example.com
RewriteCond %{HTTP_HOST} ^example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
# Rewrite requests for webadmin.example.com/xyz to /pages/admin/xyz
RewriteCond %{HTTP_HOST} ^webadmin\.example\.com
RewriteCond $1 !^pages/admin/
RewriteRule ^(.*)$ /pages/admin/$1 [L]
# Canonicalize variant webadmin hostname requests (e.g. www.webadmin.www.example.com.:80)
RewriteCond %{HTTP_HOST} ^([^.]*\.)*webadmin\.([^.]*\.)*example\.com
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize all other variant hostname requests (non-www or bogus subdomains)
RewriteCond %{HTTP_HOST} !^www\.example\.com$
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
but I can still call www.example.com/pages/public/ and webadmin.example.com/pages/admin/ with no change...
And wrong.example.com isn't forced to www.example.com either (that's what rule nbr 6 is supposed to handle isn't it ?)
maybe I'm wrong with rule priorities...
Move the final two (external redirect) rules ahead of the /pages/admin (internal rewrite) rule.
What happened to the /pages/public internal rewrite rule? It's gone...
Remember, all external redirects (with a protocol and domain name in the RewriteRule substitution and/or a [R=301] flag on the rule) go first, followed by all internal rewrites. Within those two groups put your rules in order from most-specific conditions and regex patterns (fewest URL-paths affected) to least specific pattern (most URL-paths affected). This will prevent unexpected results and "stacked" or "chained" multiple redirects.
Jim
# Canonicalize variant webadmin hostname requests (e.g. www.webadmin.www.example.com.:80)
RewriteCond %{HTTP_HOST} ^([^.]*\.)*webadmin\.([^.]*\.)*example\.com
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize all other variant hostname requests (non-www or bogus subdomains)
RewriteCond %{HTTP_HOST} !^www\.example\.com$
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
# Canonicalize direct client requests for admin subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /admin(/[^\ ]*)?\ HTTP/
RewriteRule ^admin(/.*)?$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize direct client requests for public subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public(/[^\ ]*)?\ HTTP/
RewriteRule ^public(/.*)?$ http://www.example.com/$1 [R=301,L]
# Rewrite requests for webadmin.example.com/xyz to /pages/admin/xyz
RewriteCond %{HTTP_HOST} ^webadmin\.example\.com
RewriteCond $1 !^pages/admin/
RewriteRule ^(.*)$ /pages/admin/$1 [L]
# Rewrite requests for www.example.com/xyz to /pages/public/xyz
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^(.*)$ /pages/public/$1 [L]
but I can't get either wrong.example.com to be transformed into www.example.com nor pages/public and pages/admin/ to be restricted....
Anyway, my last question would be is it possible to have php page and GET variable relooking such as :
RewriteRule ^/projects/([0-9]+)/?$ index.php?v=projects&type=$1
RewriteRule ^/projects/?$ index.php?v=projects [L,QSA]
(type being a number)
These last rules look good to me but they won't work.
Would earlier rules prevent these from working good ?
According to your tips, I should put them at the bottom of all rules, is it correct ?
Thanks again Jim
> nor pages/public and pages/admin/ to be restricted....
Be sure to completely flush your browser cache before testing any changes to any server-side code.
Your new rules won't work for two reasons: First, URL-path patterns in RewriteRules in .htaccess files will never start with a slash. Second, the rules above your new rules will rewrite the URLs before your last two rules are processed. Your new rules will need to do everything that two the previous internal rewriterules do, plus the new URL-to-query-string rewrite. In other words rewrite projects/<numbers>/ directly to /pages/public/index.php?v=projects&type=<numbers> all at once. Then put these two new rules ahead of the preceding two internal rewrite rules.
Jim
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^projects/([0-9]+)/?([0-9]+)/?$ /pages/public/index.php?v=projects&type=$1&id=$2 [L,QSA]
RewriteRule ^projects/([0-9]+)/?$ /pages/public/index.php?v=projects&type=$1 [L,QSA]
RewriteRule ^projects/?$ /pages/index.php?v=projects [L,QSA]
RewriteRule ^(.*)$ /pages/public/$1 [L,QSA]
or do I have to repeat the rewriteCond statement before each rewriteRule ?
# Canonicalize variant webadmin hostname requests (e.g. www.webadmin.www.example.com.:80)
RewriteCond %{HTTP_HOST} ^([^.]*\.)*webadmin\.([^.]*\.)*example\.com
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize all other variant hostname requests (non-www or bogus subdomains)
RewriteCond %{HTTP_HOST} !^www\.example\.com$
RewriteCond %{HTTP_HOST} !^webadmin\.example\.com$
RewriteRule ^(.*)$ http://www.example.com/$1 [R=301,L]
# Canonicalize direct client requests for admin subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /admin(/[^\ ]*)?\ HTTP/
RewriteRule ^admin(/.*)?$ http://webadmin.example.com/$1 [R=301,L]
# Canonicalize direct client requests for public subdirectory
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public(/[^\ ]*)?\ HTTP/
RewriteRule ^public(/.*)?$ http://www.example.com/$1 [R=301,L]
# Rewrite requests for webadmin.example.com/xyz to /pages/admin/xyz
RewriteCond %{HTTP_HOST} ^webadmin\.example\.com
RewriteCond $1 !^pages/admin/
RewriteRule ^(.*)$ /pages/admin/$1 [L]
# Rewrite requests for www.example.com/xyz to /pages/public/xyz and transform query string to /a/b/c/
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^projects/([0-9]+)/?([0-9]+)/?$ /pages/public/index.php?v=projects&type=$1&id=$2 [L,QSA]
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^projects/([0-9]+)/?$ /pages/public/index.php?v=projects&type=$1 [L,QSA]
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^projects/?$ /pages/index.php?v=projects [L,QSA]
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^(.*)$ /pages/public/$1 [L,QSA]
I still can access pages/public/ and pages/admin/, even with cache disabled, but the main thing works good.
Thanks again a lot for your help Jim.
OK, but what is the purpose of rule nbr 2 then ? (non-www or bogus subdomains). Isn't it supposed to handle such a case anyway ?
Also, this comment is backward and inaccurate. While it may not seem important, it may confuse others who look at your code in the future:
# Rewrite requests for www.example.com/xyz to /pages/public/xyz and transform query string to /a/b/c/ This should read:
# Rewrite requests for www.example.com/projects/a/b/c/ to /pages/public/index.php?v=project&type=a&id=b/c,
# rewrite requests for www.example.com/projects/a/b/ to /pages/public/index.php?v=project&type=a&id=b, and
# rewrite requests for www.example.com/projects/[b]abc[/b]/ to /pages/public/index.php?v=project&type=[b]ab[/b]&id=[b]c[/b] Jim
# Rewrite requests for www.example.com/projects/a/b/c/ to /pages/public/index.php?v=project&type=a&id=b/c,
# and rewrite requests for www.example.com/projects/a/b/ to /pages/public/index.php?v=project&type=a&id=b
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteCond $1 !^pages/public/
RewriteRule ^projects/([0-9][b]+)/([[/b]0-9]+)/?$ /pages/public/index.php?v=projects&type=$1&id=$2 [L,QSA]
Jim