1

I'm trying to adapt the rewrite rule given in Develop Locally, Use Images from Production to do the very same thing on my local dev site (which runs on XAMPP).

This is how the development site's .htaccess looks, with the snippet added towards the end of it (URL replaced with example.com equivalent, obviously):

<IfModule mod_php.c>
php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value max_execution_time 300
php_value max_input_time 300
</IfModule>

#START WORDPRESS

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /wordpress/

# Redirect About and Library to first sub page 
RewriteRule ^about/?$ /wordpress/about/history/ [L,NC,R=301]
RewriteRule ^the-library/?$ /wordpress/the-library/gallery/ [L,NC,R=301]

RewriteRule ^index\.php$ - [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]

# If images not found on development site, load from production 
RewriteCond %{REQUEST_URI} ^/wp-content/uploads/[^\/]*/.*$
RewriteRule ^(.*)$ https://www.example.com/$1 [QSA,L]
</IfModule>

#END WORDPRESS

The rewrite rule doesn't seem to be making a difference, whether with the HTTPS or HTTP version of the URL. Images unavailable for the dev site simply don't display, even when the same images load fine for the production site.

I also had no luck trying to adapt the rewrite rule found in this slightly different question applying to Wordpress Multisite.

Additionally, I previously added a filter to stop Wordpress from modifying the .htaccess file, so am pretty sure that this isn't the cause of the problem.

What is wrong with my rewrite rule here that's causing it to fail?

2
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]

# If images not found on development site, load from production 
RewriteCond %{REQUEST_URI} ^/wp-content/uploads/[^\/]*/.*$
RewriteRule ^(.*)$ https://www.example.com/$1 [QSA,L]

The problem here is that "If images not found on development site" then the request has already been rewritten to index.php by the preceding RewriteRule (WordPress front-controller), so your rule block that follows does nothing.

NB: Whilst you've not explicitly included the R (redirect) flag on the RewriteRule, this will implicitly trigger an external 302 (temporary) redirect, the same as if you had explicitly included R=302 on the directive. Explicitly including this flag is preferable to more clearly communicate its intent. In fact, you might want to change this to a 301 so that images are cached, thus preventing external redirects to your production server on every request.

The QSA flag is not required here, since you are not including a query string on the RewriteRule susbstitution.

Aside: Your RewriteRule arguably matches too match... it matches everything, not just images, is that intentional?

You could resolve this by either...

  1. preventing all image URLs being processed by the front-controller. For example, add an additional condition to the WP front-controller to exclude images:

    RewriteCond %{REQUEST_URI} !\.(jpe?g|png|gif)$
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.php [L]
    
  2. OR, move your redirect to above the WP front-controller, and check for the non-existence of the requested file before redirecting to your production server. I would also be more restrictive on the regex and match only images (as mentioned above) - if that is the intention. For example:

    RewriteRule ^index\.php$ - [L]
    
    # If images not found on development site, load from production
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^wp-content/uploads/[^/]+/.+\.(jpe?g|png|gif)$ https://www.example.com/$0 [R=302,L]
    
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.php [L]
    

The additional condition is not required, as you can perform the URL comparison in the RewriteRule pattern (more efficient). The slash does not need to be escaped in the character class (or anywhere for that matter). And the QSA flag is not required here (as mentioned above).

Note that the regex matches URL-paths of the form /wp-content/uploads/<somedirectory>/<something>.jpg, where <something> can be any number of additional subdirectories. This is based on your regex.

The $0 backreference (as opposed to $1 in your original directive) is the entire URL-path that is matched by the RewriteRule pattern. $1 contains the first captured subgroup. In the revised directive, the first captured subgroup contains just the image file extension.

  • Definitely not intentional, the last two rewrite rules are those copy-pasted from CSS Tricks, and everything above is what existed in my .htaccess prior, so it's highly possible there's some overlap and inefficiencies. – Hashim Sep 6 at 21:41
  • 1
    Thank you for a well-written and comprehensive answer, it's refreshing to read about how this stuff actually works rather than just have the solutions given, especially when it's as complex a beast as Apache directives are. I opted for the second solution and now it works perfectly. Also, thanks for the reference to the Wordpress "front-end controller", I had always wondered what those default rules actually did and how they worked, but didn't know there was a term for them. I'm now reading this. – Hashim Sep 6 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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