Paul Draper
Paul Draper

Reputation: 83343

How to set up proxy in .htaccess

The Apache documentation states that RewriteRule and the should be put in the server configuration, but they can be put in htaccess because of shared hosting situations. I am in such a situation.

I am trying to set up a transparent proxy:

 RewriteEngine On
 RewriteCond %{REQUEST_URI} ^/foo [OR]
 RewriteCond %{REQUEST_URI} ^/bar
 RewriteRule ^(.*)$ http://example.com/$1 [P]

This is working fine...except for redirects (like if /foo redirects to /bar). Redirects go back to example.com, not my server.

I understand the the ProxyPassReverse directive will solve this, but I get an "Internal Server Error" page when I add this to .htaccess

Unlike the Rewrite directives, ProxyPassReverse will not work in htaccess.

How do I set up a transparent proxy in shared hosting situation, or is this not possible?

(This seems reasonable, since Rewrite already gets 80% of the way there, and having a transparent proxy in one htaccess would not interfere with having it in another.)

Upvotes: 20

Views: 50681

Answers (2)

Mike J-P.
Mike J-P.

Reputation: 141

I managed to gather a few sources to figure out how to do this. I use a shared hosting provider, so I don't have access to server configuration (httpd.conf). I can only use .htaccess to accomplish the proxying. This example is for a WordPress site where I want most of the content served by origin.example.com, but will have some pages served locally, sort of like an overlay. You could go the other way and ONLY proxy specific subdirectories using different RewriteCond rules.

Things to know:

  1. You can’t use ProxyPass or ProxyPassReverse in .htaccess, so we have to use other methods to mimic what they do.
  2. You can’t make proxy calls over HTTPS if SSLProxyEngine is not turned on by your provider, so you will lose some security if you have concerns about MITM attacks. If the origin server is internal, this may not be an issue. You could also use .htaccess on the origin server to enforce HTTPS from everywhere except the proxy server.
  3. You need to rewrite headers
  4. You need to rewrite the HTML that comes back from the origin server, and that needs to be done on the origin server. You can restrict it to certain IPs (i.e. the IP of the proxy) so it won’t break if you access it elsewhere.

What I want:

I want calls to proxy.example.com to serve content origin.example.com. In my case, I want to map everything with a few exceptions. If you only want to map a portion of your site, adjust your rules accordingly.

How to do it:

  1. Configure the .htaccess file on proxy.example.com to proxy all URIs to origin.example.com. I want to be able to log into proxy.example.com, so I don’t rewrite /wp-admin or /wp-login.php. In my case, I have a /programs/ section that I want served by the proxy server itself (also a WordPress instance). Prevent looping by checking REDIRECT_STATUS.
# I force everything coming into proxy.example.com to be HTTPS <IfModule mod_rewrite.c>
RewriteEngine On

RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] </IfModule> <IfModule mod_proxy.c>
# Redirect access for / (or any index) to the origin. NOTE target is http:// without SSLProxyEngine
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(index\.(php|html|cgi))?$ http://origin.example.com/ [P]

# Do NOT redirect these patterns
RewriteCond %{REQUEST_URI} !^/wp-admin/
RewriteCond %{REQUEST_URI} !^/wp-login.php
RewriteCond %{REQUEST_URI} !^/programs/

# Redirect everything else. NOTE target is http:// without SSLProxyEngine
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.+)$ "http://origin.example.com/$1" [P]

# Mimic ProxyPassReverse. Fix the headers. Force to be https.
Header edit  Location ^https?://origin\.example\.com/(.*)$ https://proxy.example.com/$1
Header edit* Link https?://origin\.example\.com/ https://proxy.example.com/ </IfModule>
  1. ONLY for the IP of the PROXY server, rewrite any references in the HTML itself. This example is for a WordPress site.

Stolen from WordPress filter to modify final html output

2a) Add a Must Use plugin to add a ‘final_output’ hook. Add a file in wp-content/mu-plugins/buffer.php:

<?php

/**  * Output Buffering  *  * Buffers the entire WP process, capturing
the final output for manipulation.  */

ob_start();

add_action('shutdown', function() {
    $final = '';

    // We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
    // that buffer's output into the final output.
    $levels = ob_get_level();

    for ($i = 0; $i < $levels; $i++) {
        // NOTE: Use only one of the two lines below
        // that has your output come out in the correct order.
        //$final .= ob_get_clean();
        $final = ob_get_clean() . $final;
    }

    // Apply any filters to the final output
    echo apply_filters('final_output', $final); }, 0); ?>

2b) Add the following PHP to the wp-content/themes/yourthemenamehere/functions.php. It uses the ‘final_output’ hook above. (PHP 5.3 or later required for use of anonymous function.)

add_filter('final_output', function($output) {
    // IP of the proxy server
    $WWW_IP = “4.4.4.4”; 
    //$WWW_IP = “4.4.2.2”;  // My workstation, for testing purpose only
    if ($_SERVER['REMOTE_ADDR'] == $WWW_IP) {
        // Force HTTPS when rewriting
        $output = str_replace('http://origin.example.com', 'https://proxy.example.com’, $output);
        // Catch anything that wasn’t a URL
        return str_replace(‘origin.example.com, 'proxy.example.com', $output);
    }
    return $output;
});

If all goes well, you should now see the content from origin.example.com served from proxy.example.com.

I'm still testing this, so if you find errors or omissions, please add a comment.

Upvotes: 2

Michael Sallaway
Michael Sallaway

Reputation: 411

Unfortunately, I'm fairly sure what you want to do isn't possible: I'm trying to do the exact same thing! From my research, I'm fairly confident it's not possible.

Put simply, you need to use ProxyPassReverse, which is only available at a VirtualHost level (or similar); not a htaccess level.

Edit: the only way I have achieved this is by also configuring the responding server/application to know it's behind a proxy, and serving pages appropriately. That is, I use .htaccess to redirect to another server as follows:

  RewriteEngine on
  RewriteRule  (.*)  http://localhost:8080/$1  [P,L] 

Then on the application server -- in this case, a JIRA installation -- I configured the Java Tomcat/Catalina appropriately to serve pages with the proxied information:

 proxyName="my.public.address.com"
 proxyPort="80"

However, that's not completely transparent; the app server needs to serve pages in a proxied manner. It might be of some use, though.

Upvotes: 25

Related Questions