introzen
introzen

Reputation: 109

.htaccess fails to rewrite when url contains HTTPS

I've been sitting with this a long time now and can't get my head around it.

Essentially, the functionality I'm looking for in the end would be similar to the following:

http://example.com redirects to https://example.com - currently working

and for

http://*.example.com and https://*.example.com to redirect to https://example.com/* - Not working. Currently, all subdomains get redirected to the main-domain, without their respective folder path. Also, when going to https://test.example.com, it doesn't redirect at all.

Here's the example.com.conf from sites-enabled:

<IfModule mod_ssl.c>

    <VirtualHost *:80>
        ServerName example.com
        ServerAlias www.example.com
#       Redirect permanent / https://example.com/

        <Directory /var/www/example.com/public_html>
            Options FollowSymLinks
            AllowOverride All
            Require all granted
        </Directory>
        
        <IfModule mod_rewrite.c>
            # Redirect all traffic to HTTPS
            RewriteCond %{HTTPS} !on
            RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [QSA,R=301,L]
        </IfModule>


    </VirtualHost>

    <VirtualHost _default_:443>
        ServerAdmin webmaster@localhost
        ServerName example.com
        ServerAlias www.example.com
        DocumentRoot /var/www/example.com/public_html

        <Directory /var/www/example.com/public_html>
            Options FollowSymLinks
            AllowOverride All
            Require all granted
        </Directory>

        <IfModule mod_rewrite.c>
            # Remove www
            RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
            RewriteRule ^(.*)$ http://%1%{REQUEST_URI} [QSA,R=301,L]

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

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        SSLEngine on
        SSLCertificateFile  /etc/ssl/civrpssl/example.com.crt
        SSLCertificateKeyFile /etc/ssl/civrpssl/example.com.key
        SSLCACertificateFile /etc/ssl/civrpssl/ca-example.com.crt

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
                SSLOptions +StdEnvVars
        </Directory>

    </VirtualHost>
</IfModule>

And finally, here's the .htaccess from example.com/public_html/:

removed, see edit #2.

I'm hoping it's a rather obvious issue that I've failed to see. Otherwise, please could someone enlighten me where I should start looking for the issue? I'm assuming that the HTTPS redirect interferes with the subdomain redirect in the .htaccess file but don't know how I could put it any differently.

EDIT #1: I just noticed that www.example.com throws error "URL not found" rather than re-directing to https://example.com. How comes it isn't caught by the first rewrite in .htaccess?

EDIT #2:

Upvotes: 2

Views: 927

Answers (1)

MrWhite
MrWhite

Reputation: 45829

ServerName example.com
ServerAlias www.example.com

Neither of the <VirtualHost> containers accept requests for any subdomains, except for www. The vHost may still catch additional subdomains (in fact, any hostname) if it is the first defined vHost in the server config. To catch "any" subdomain, you would need another "wildcard" ServerAlias of the form:

ServerAlias *.example.com

Remove the <IfModule mod_rewrite.c> wrappers - they are not required. They will only mask the error if mod_rewrite is not available. The <IfModule> wrapper should only be used if these directives are entirely optional and the config is to be ported to other systems where mod_rewrite is not available.

UPDATE: You were also missing the RewriteEngine On directive in the vHost. Unless the rewrite engine was already enabled elsewhere in the server config (unlikely) then these mod_rewrite directives would simply be skipped altogether.

In HTTP vHost (port 80)...

       # Redirect all traffic to HTTPS
       RewriteCond %{HTTPS} !on
       RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [QSA,R=301,L]

There's no point checking HTTPS in the vHost for port 80 as it will always be off. If you want to redirect directly from the subdomain to the subdirectory, you would need to change the above to read:

        # Redirect HTTP subdomains to HTTPS subdirectory (excluding www)
        RewriteCond %{HTTP_HOST} !^www\. [NC]
        RewriteCond %{HTTP_HOST} ^([^.]+)\.example\.com [NC]
        RewriteRule ^/(.*) https://example.com/%1/$1 [R=301,L]

        # Redirect HTTP to HTTPS
        RewriteRule ^/(.*) https://example.com/$1 [R=301,L]

However, if you have any intention to implement HSTS in the future then you will need to redirect to HTTPS on the same host first and issue a second redirect to the subdirectory in the vHost for port 443 only. (Redirect from HTTP to HTTPS using the Redirect directive - currently commented out - although this would require a separate (minimal) vHost for each subdomain, which may not be desirable.)

^/(.*) - Note the addition of a slash prefix outside of the capturing subpattern in the RewriteRule pattern. In a virtualhost context, the full URL-path is matched by the RewriteRule pattern. The trailing $ is not required since regex is greedy by default.

Note also the regex ([^.]+) (instead of (.*)) that specifically matches a subdomain only (no dots).

In your vHost for HTTPS (port 443) you have:

       # Remove www
       RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
       RewriteRule ^(.*)$ http://%1%{REQUEST_URI} [QSA,R=301,L]

You are redirecting back to HTTP (although this seems to be a bug you have introduced after your edit?).

No need to capture the RewriteRule pattern if you are using the REQUEST_URI variable.

Then the same edit applies to the subdomain to subdirectory redirect as mentioned above.

Summary

Bringing the above points together we have something like:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAlias *.example.com

    #Redirect permanent / https://example.com/

    RewriteEngine On
    
    # Redirect HTTP subdomains to HTTPS subdirectory (excluding www)
    RewriteCond %{HTTP_HOST} !^www\. [NC]
    RewriteCond %{HTTP_HOST} ^([^.]+)\.example\.com [NC]
    RewriteRule ^/(.*) https://example.com/%1/$1 [R=301,L]

    # Redirect HTTP to HTTPS (www and domain apex)
    RewriteRule ^/(.*) https://example.com/$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    ServerName example.com
    ServerAlias www.example.com
    ServerAlias *.example.com
    DocumentRoot /var/www/example.com/public_html

    <Directory /var/www/example.com/public_html>
        Options FollowSymLinks
        # Set "AllowOverride None" to disable .htaccess altogether
        AllowOverride All
        Require all granted
    </Directory>

    RewriteEngine On

    # Remove www
    RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
    RewriteRule ^ https://%1%{REQUEST_URI} [QSA,R=301,L]

    # Redirect other subdomains to subdirectory
    RewriteCond %{HTTP_HOST} ^([^.]+)\.example\.com [NC]
    RewriteRule ^/(.*) https://example.com/%1/$1 [R=301,L]

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    SSLEngine on
    SSLCertificateFile  /etc/ssl/civrpssl/example.com.crt
    SSLCertificateKeyFile /etc/ssl/civrpssl/example.com.key
    SSLCACertificateFile /etc/ssl/civrpssl/ca-example.com.crt

    <FilesMatch "\.(cgi|shtml|phtml|php)$">
            SSLOptions +StdEnvVars
    </FilesMatch>
    <Directory /usr/lib/cgi-bin>
            SSLOptions +StdEnvVars
    </Directory>

</VirtualHost>

You should first test with 302 (temporary) redirects to avoid potential caching issues.

And clear your browser cache before testing (any erroneous 301s will have been cached by the browser).

Upvotes: 1

Related Questions