.htaccess directives fail to redirect www to non-www URLs
I'm trying to redirect https://www.example.com
to https://example.com
.
I'm running Ubuntu server 2020 lts with Apache 2, PHP 7 and Let's Encrypt SSL.
My file structure:
logs/
private/
public/
└ index.php
.htaccess
index.php
My Apache2 config file (/etc/apache2/sites-available/example.com-le-ssl.conf
):
<IfModule mod_ssl.c>
<VirtualHost *:443>
<Directory /var/www/example.com/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
Options -Indexes
Options -Includes
Options -ExecCGI
</Directory>
ServerAdmin webmaster@localhost
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
ErrorLog $APACHE_LOG_DIR/error.log
CustomLog $APACHE_LOG_DIR/access.log combined
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/example.com-0001/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com-0001/privkey.pem
</VirtualHost>
</IfModule>
(/etc/apache2/sites-available/example.conf
):
<VirtualHost *:80>
<Directory /var/www/example.com/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
Options -Indexes
Options -Includes
Options -ExecCGI
</Directory>
ServerAdmin webmaster@localhost
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
ErrorLog $APACHE_LOG_DIR/error.log
CustomLog $APACHE_LOG_DIR/access.log combined
RewriteEngine on
RewriteCond %SERVER_NAME =www.example.com [OR]
RewriteCond %SERVER_NAME =example.com
RewriteRule ^ https://%SERVER_NAME%REQUEST_URI [END,NE,R=permanent]
</VirtualHost>
My .htaccess
file:
RewriteEngine On
# First rule
RewriteBase /
RewriteCond %HTTP_HOST ^www.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# Second rule
RewriteCond %THE_REQUEST ^GET /public/ [NC]
RewriteRule ^public/(.*)$ $1 [L,R=301,NE]
RewriteRule ^((?!public/).*)$ public/$1 [L,NC]
cURL output:
curl -I http://www.example.com
HTTP/1.1 301 Moved Permanently
Date: Thu, 01 Apr 2021 13:42:14 GMT
Server: Apache
Location: https://www.example.com/
Content-Type: text/html; charset=iso-8859-1
I don't understand why the first rule doesn't redirect as expected.
The second rule works, it hides public
. I'm in the public
directory but public
is invisible in https://example.com/
.
There's nothing actually "wrong" with the config/directives you've posted, but it seems you don't have a valid SSL cert that covers the www
subdomain so you can't redirect from https://www.example.com/
without the user accepting the invalid cert (this is the "error") - which they should never do.
When requesting HTTPS, the browser establishes a secure connection with the server before any server-side scripts/redirects are even processed. If the security cert is invalid (as in this case), the user sees a warning in the browser and are unable to continue (unless the user jumps through hoops and accepts the invalid cert - which they should never do, as already mentioned). If there is no SSL cert at all then the server simply fails to connect.
So, there's nothing actually "wrong" with the config you've posted. Providing you have a valid cert installed for the www subdomain then a request for http://www.example.com/
would be redirected to https://www.example.com/
by the mod_rewrite directive in the <VirtualHost *:80>
container and then your directives in .htaccess
would redirect from https://www.example.com/
to https://example.com/
(canonicalising the hostname). Yes, that's two redirects, but there's nothing wrong with two redirects here. And two redirects would actually be a requirement if implementing HSTS.
When testing multiple redirects (a redirect "chain") with CURL then you need to use the -L
flag, otherwise it does not follow the first redirect. For example:
curl -I -L http://www.example.com/
But in this case, without a valid cert installed for the www subdomain you get a warning back from CURL and further requests are stopped:
curl: (51) SSL: no alternative certificate subject name matches target host name 'www.example.com'
However, you could optimise this a bit (if HSTS is not a concern) and redirect directly to the canonical hostname (ie. example.com
) from the <VirtualHost *:80>
container. You can also do this with a simple mod_alias Redirect
. You don't need mod_rewrite for this.
For example, instead of the following in <VirtualHost *:80>
:
RewriteEngine on
RewriteCond %SERVER_NAME =www.example.com [OR]
RewriteCond %SERVER_NAME =example.com
RewriteRule ^ https://%SERVER_NAME%REQUEST_URI [END,NE,R=permanent]
you replace this with:
Redirect 301 / https://example.com/
There's no need to check the requested host (ie. SERVER_NAME
) anyway in the vHost container, since it can't be anything other than the two hostnames you've defined with the ServerName
and ServerAlias
directives. (Assuming you have a default vHost:80 defined earlier that catches all "invalid" requests? Either way, they will all be redirected to your canonical hostname anyway.)
With this in place, any request for HTTP (ie. http://example.com/
or http://www.example.com/
) will be redirected directly to https://example.com
in a single redirect (the redirect in .htaccess
is not required in this instance). This naturally circumvents the security cert issue for any request to HTTP only.
HOWEVER, any direct request the user makes to https://www.example.com/
will still be subject to the SSL cert warning until a valid cert is installed.
Aside: Due to rewriting all requests to the /public
subdirectory (which contains index.php
- the "front-controller" I assume), the additional /index.php
in the document root is entirely superfluous.
Comments
Post a Comment