Search code examples
apache.htaccesshttpshttp-status-code-308

Apache 308 redirects change the protocal from https to http


So I recently updated my .htaccess to replicate the directory slash directive with a 308 instead of the 301 Apache normally uses, so that the browser repeats the same request instead of changing it to a GET request and droping the data from other request methods.

# Disable 
DirectorySlash Off

# Recreate the DirectorySlash directive with 308
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ /$1/ [R=308,L]

This works great in my local tests but when I use my testing server that uses HTTPS it suddenly breaks. In Chrome devtools I can see its being directed to a non secure url or some reason.

Error:

Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://...'. This request has been blocked; the content must be served over HTTPS.

I've also tried adding the request scheme like so and its had no effect.

RewriteRule ^(.+[^/])$ %{REQUEST_SCHEME}://%{HTTP_HOST}/$1/ [R=308,L]

Does anyone know why Apache changes the protocol to an insecure one?

UPDATE: I tried checking for the protocol and storing it in a variable because REQUEST_SCHEME is not always defined and cause it to default back to http again.

RewriteCond %{HTTPS} on
RewriteRule .* - [E=PROTO:https]
RewriteCond %{HTTPS} !on
RewriteRule .* - [E=PROTO:http]


RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ %{ENV:PROTO}://%{HTTP_HOST}/$1/ [R=308,L]

This appeared to work initially, but it still redirects HTTPS to HTTP when the request is a POST.

UPDATE: As arkascha suggested I tried hardcoding HTTPS and this works.

RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}/$1/ [R=308,L]

This is extremely odd as the variable approach should produce the same result. The initial request is HTTPS so the variable should end up using HTTPS the same way the hardcoded approach does.

So I guess I'll have to use a domain-based check or some other workaround and duplicate the logic for HTTP vs HTTPS.

I feel like this is all unnecessary gymnastics to get a 308 redirect to work as documented.

Can anyone explain this odd behaviour?


Solution

  • It occurred to me that if RewriteCond %{HTTPS} on or %{REQUEST_SCHEME} was behaving as if the request was HTTP, then it means Apache must somehow be receiving an HTTP request regardless of the initial protocol.

    The test server is hosted by AWS and It seems the HTTPS request must be decrypted by AWS and then sent unencrypted to the Apache instance. (I don't know how this server was originally set up).

    The solution was to check for the X-Forarded-Proto and set a variable to use in the redirect:

    # Assume HTTP by default for local devs.
    RewriteRule .* - [E=PROTO:http]
    # Change to HTTPS if forwarded/current request is HTTPS.
    RewriteCond %{HTTP:X-Forwarded-Proto} =https [OR]
    RewriteCond %{HTTPS} on
    RewriteRule .* - [E=PROTO:https]
    
    # Recreate the DirectorySlash directive with 308
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(.+[^/])$ %{ENV:PROTO}://%{HTTP_HOST}/$1/ [R=308,L]
    

    As for why this was only my POST 308 redirects, my only theory is that the browser silently corrects the protocol for us when it's a GET request, and follows the 308 directive more closely if it is not a GET request. Otherwise, the GET requests should have had the same problem.

    I would be very interested in other answers explaining whats happening in more detail, especially the browser's behaviour (Chrome && Firefox).