1

I host a MediaWiki website on a shared server with Apache, MySQL and PHP.

Up until today I used a CDN to deliver that website's content --- which required redirecting each webpage to a www. including version.

Today I have reached the conclusion that a CDN is redundant for that specific website and deleted the CDN account associated with it.

This is my current global rewrite rules for HTTPS websites WITH www. in my main .htaccess fiile:

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\.seo-wiki\.org$ [NC]
RewriteRule (.*) https://www.seo-wiki.org/$1 [L,R=301]

What would be a modern global rewrite command for HTTPS websites without cname (www.) at all (without a CDN) which may or may not include my websites domain?

1
  • 2
    Mod rewrite is very stable and hasn't changed drastically in 15 years. Rules for this from years ago are still best practice today. May 21, 2021 at 23:34

1 Answer 1

2

To clarify what @StephenOstermiller mentioned in comments, nothing has changed in this regard for quite literally decades. Any question you find on the Stack Exchange network, regarding canonical redirects on Apache, will be just as relevant now it was "back in the day".

There is also no single "correct" solution. Due to the variation of servers/configs, different solutions may be required. And due to the powerful/flexible nature of the directives there can be several ways of doing (exactly) the same thing.

So, if you are using a .htaccess file in the document root directory and you want to redirect from HTTP to HTTPS and from www to non-www and you are not using a CDN (that often acts as a reverse proxy) then you could do this like the following using mod_rewrite (very similar to the non-www to www redirect you posted):

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule (.*) https://example.com/$1 [R=301,L]

The above 301 redirects all requests where HTTPS is off or www.example.com is requested to https://example.com/, preserving the URL-path and query string.

This needs to go near the top of the .htaccess file, before any existing rewrite directives. Order matters.

Note that the $ at the end of the 2nd RewriteCond pattern (CondPattern) is removed to catch fully-qualified-domain-names (FQDNs) that end in a dot. eg. a request of the form http://www.example.com./foo (note the dot at end of the hostname).

The RewriteRule pattern (.*) captures the requested URL-path, which is referred to using the $1 backreference in the RewriteRule substitution string.

This will not redirect any other subdomains you might have.

(You need RewriteEngine On once at the top of the file.)

Modify for any directory and the server config

Due to the way the URL-path is captured in the backreference, the above only works if the .htaccess file is in the document root directory. It also only works in .htaccess files (or a directory context), as opposed to in a server or virtualhost context (in the main server config). It will result in a double-slash at the start of the URL-path if used in the main server config.

This is because in .htaccess, the URL-path that is matched by the RewriteRule pattern excludes the directory-prefix (which always ends with a slash), so the URL-path that is matched never starts with a slash. For example, given a request for example.com/foo/bar, the URL-path that is matched in the root .htaccess file is foo/bar (no slash prefix). Whereas when used in the main server config, it always matches against the full URL-path, ie. /foo/bar (slash prefix).

Since we are expecting the $1 backreference to not start with a slash, we can resolve this by making the slash prefix optional (as @StephenOstermiller suggests in comments). For example:

:
RewriteRule ^/?(.*) https://example.com/$1 [R=301,L]

So, this now works in both the server config and a .htaccess file in the document root.

However, this won't work if the .htaccess file is located in a subdirectory (as opposed to the document root), since the subdirectory will get removed in the redirect (it's not captured by the RewriteRule pattern). To resolve this you would need to use the REQUEST_URI server variable instead - which contains the full URL-path, including the slash prefix. For example:

:
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

This now works everywhere (.htaccess in any directory and the server config) and avoids the capturing backreference, so is arguably the "better" solution. However, it really makes no difference on most configs.

Note that the slash is removed at the start of the URL-path in the substitution string, since the REQUEST_URI server variable itself already contains the slash prefix.

In summary, the complete rule then looks like:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

However, if you are implementing this in the server config then you probably wouldn't do it this way to begin with. It would be more efficient to use a mod_alias Redirect directive instead (in the appropriate vHost container), which is not possible in .htaccess.

Avoid repetition of the domain name

You could save repetition of the domain name in the substitution string by capturing the required domain name from the preceding CondPattern. And write it like this instead:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.(example\.com) [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

The %1 backreference contains example.com captured in the preceding CondPattern. Otherwise, this is very similar to the above rule, except that it will not canonicalise a mixed case Host header. If www.ExAmPlE.com is requested then the request will be redirected to ExAmPlE.com. Although this is rare and will potentially only affect bot traffic (browsers lowercase the hostname before making the request).

Generic "no domain" solution

To make this entirely generic, without explicitly stating the domain then you could instead write the rule like this:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+?)\.?$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

However, this doesn't only redirect www.example.com to example.com. It will also redirect from any www subdomain. For example, www.foo.example.com will be redirected foo.example.com and www.another.example will be redirected to another.example. The www subdomain is simply removed in all cases. This may or may not be desirable for your config. It's a potential caveat of having an entirely "generic" solution, although can be adjusted for particular cases (although may be less "generic" as a result).

This "generic" version is similar to the previous. The 3rd condition (RewriteCond directive) is simply used to capture the domain name, less the optional www. prefix. The 3rd condition always matches the request, regardless of whether HTTPS is off or the requested hostname starts www..


ASIDE:

A "modern" website...

What would be a modern global rewrite command for HTTPS websites without cname (www.)

Now, you mention "modern". On a modern website you might be considering to implement HTTP Strict Transport Security (HSTS). In which case you need to change the above... so instead of a single redirect to the canonical protocol and hostname you redirect from HTTP to HTTPS on the same hostname first, before canonicalising the hostname. This (unfortunately) potentially results in two redirects, not one (but only affects first time visitors to the non-canonical protocol and hostname, so the impact is minimal). This is a requirement of HSTS.

So, you would do something like the following instead:

# 1. Redirect HTTP to HTTPS (same host)
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# 2. Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.(example\.com) [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

And to make the second rule "generic" (ie. without explicitly stating the domain) then...

# 2. Redirect www to non-www (generic version)
RewriteCond %{HTTP_HOST} ^www\.(.+?)\.?$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

(This isn't a complete HSTS solution, for that see the following related question:
HSTS implementation when using www.TLD)

Side note about the rewrite rule presented in your question

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule (.*) https://www.example.com/$1 [L,R=301]

This redirects everything that is not www.example.com, which naturally includes any subdomains and any other domains that might resolve to your account. Depending on your site this might be "too much" or it might be perfectly OK. However, to redirect only example.com to www.example.com, ie. only non-www to www then you would need to modify the 2nd condition to be a positive match like this:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^example\.com [NC]
RewriteRule (.*) https://www.example.com/$1 [L,R=301]
10
  • @RAMOS I've updated my answer. (Made some minor changes throughout and added another couple of sections - addressing the "modern" aspect.) So, it does successfully redirect from www to non-www? Since you previously had the 301 (permanent) redirect in the other direction, you will need to ensure all (browser) caches are cleared. MediaWiki itself may need to also be configured for the new hostname (example.com vs www.example.com). You might also have an issue with cookies that were previously set on the www subdomain but may not exist on the domain apex (ie. example.com)?
    – MrWhite
    May 23, 2021 at 13:04
  • Sorry, the problem about generally not being able to edit existing webpages but to be able create existing ones, didn't have anything to do with your code example; reather it had to do with some APCu configuration issue which I fixed already.
    – RAMOS
    May 24, 2021 at 13:51
  • About your code example: I have already flushed DNS cache via CMD and fully flushed browser's cache but if I go to my website by just example.com and then respectively change the URL to www.example.com than any webpage besides the webpage in which I have added www. that I will access would be loaded with www. --- this situation will continue until I load the webpage in which I have added www.. That's super strange ! Perhaps it's a bug in Microsoft Edge.
    – RAMOS
    May 24, 2021 at 13:53
  • Yep, this problem happens in Chrome as well and most likely don't have anything to do with cache ; perhaps browser developers assume that "one time www. in a session AND in a given website` == "always www. in a session AND in a given website** ; just strange, I do believe it's a browser development standard issue and isn't associated with .htaccess (maybe it is associated with my hoster Namecheap but also unlikely) because there is nothing in public_html/.htaccess to initiate such behavior.
    – RAMOS
    May 24, 2021 at 13:54
  • Yep, this seems like a bug in both Edge and Chrome ;;; the developers of the browsers were allegedly thinking that if a user wants it withwww. than thus it would be in the same session and for the same website --------- I have just confirmed this by changing your first example to another and got the same behavior in Edge:
    – RAMOS
    May 24, 2021 at 19:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.