How to Load Production Images in WordPress Dev with .htaccess

The .htaccess file is a configuration file used by the Apache webserver to customize the server’s behavior. WordPress uses this file to manage its URL rewriting rules. Below is a code block that modifies the default WordPress rules to allow for the loading of images from a production site instead of the local development site.

Loading images from production instead of development can be useful in scenarios where you have a large number of media files on the production site and don’t want to download them to the development environment. This approach can help speed up the development process and reduce the amount of storage space required for the development site. Additionally, it can help ensure that the site’s design and layout look consistent with the production site since the same images are being used.

There may be endless reasons why would you like to do it, but let’s get to code.


In the .htaccess file, WordPress adds a small code block by default, which starts with # BEGIN WordPress and ends accordingly with # END WordPress.

Now, before this block, we need to insert the code lines that will rewrite the image URLs. It’ll basically show the normal image URLs, but will actually load them from production or a CDN, you name it…

Find the line that says # BEGIN WordPress and exactly before it, insert this code block by replacing with your prod server address:

<IfModule mod_rewrite.c>
# If images not found on development site, load from production
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^wp-content/uploads/[^/]+/.+\.(jpe?g|png|gif)$$0 [R=302,L]

If you want to get into technical details here’s how does it work:
This code checks if the requested file is not found on the local development site (RewriteCond %{REQUEST_FILENAME} !-f).
If it’s an image file in the wp-content/uploads/ directory (RewriteRule ^wp-content/uploads/[^/]+/.+.(jpe?g|png|gif)$), then the rule redirects the request to the corresponding URL on the production site ($0).
The $0 variable represents the entire matched string from the RewriteRule pattern.
The [R=302,L] flags indicate that it’s a temporary redirect (302) and that no further rules should be processed (L).

Example of a full code block

Please note that the code within the WordPress begin and end tags may differ on your site. Therefore, rather than copying and pasting the code below, use it as an example of how I implemented it on my site and modify it to suit your needs.

<IfModule mod_rewrite.c>
# If images not found on development site, load from production
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^wp-content/uploads/[^/]+/.+\.(jpe?g|png|gif)$$0 [R=302,L]

# BEGIN WordPress
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress

Make a must-use plugin

These rules will be overridden sometimes by plugins, wp core updates, and so on. To prevent this, we can create a mu-plugin, that will prepend this code each time is needed.

In WordPress, we have insert_with_markers function, which is usually used to write in .htaccess file, but it does not allow to insert before other rules. It’ll just insert our code at the end of the file. We can fix this by creating a custom function. Here is the full code of a mu-plugin like this:

Plugin Name: Prod to Dev Images Mirror
Description: Inserts custom rewrite rules into .htaccess

function zerowp_prod_to_dev_images_mirror() {
	$marker = 'Prod to Dev Images Mirror';
	$insertion = array(
		'<IfModule mod_rewrite.c>',
		'# If images not found on development site, load from production',
		'RewriteCond %{REQUEST_FILENAME} !-f',
		'RewriteRule ^wp-content/uploads/[^/]+/.+\.(jpe?g|png|gif)$$0 [R=302,L]',

	$filename = ABSPATH . '.htaccess';

	// check if file is writable
	if (!is_writable($filename)) {
		return false;

	$lines = explode("\n", file_get_contents($filename));
	$new_file = '';
	$found = false;
	$already_present = false;

	foreach($lines as $line_number => $line) {
		if (strpos($line, $marker) !== false) {
			$already_present = true;
		if (! $found && strpos($line, '# BEGIN WordPress') !== false) {
			$new_file .= "# BEGIN {$marker}\n";
			$new_file .= implode("\n", $insertion);
			$new_file .= "\n# END {$marker}\n";
			$found = true;
		$new_file .= $line . "\n";

	if ($already_present) {
		return true;

	// Insert at top if # BEGIN WordPress is not found
	if (! $found) {
		$insertion = "# BEGIN {$marker}\n" . implode("\n", $insertion) . "\n# END {$marker}\n";
		$new_file = $insertion . $new_file;

	// Write the changes to the file
	return file_put_contents($filename, $new_file);
add_action('admin_init', 'zerowp_prod_to_dev_images_mirror', -100);

The function above can be modified and used to achieve other tasks, and you’ll have your own insert_with_markers function that is able to prepend the code.

Again, don’t forget to replace with your domain.


Overall, this code block modifies the default WordPress URL rewriting rules to allow for the loading of images from a production site when they are not found on the local development site. This can be useful in scenarios where developers want to test their site’s functionality without having to download all the media files from the production site.

Member since January 2, 2019

As a seasoned WordPress developer with expertise in various tech stacks and languages, I bring years of experience to every project I handle. My passion for coding and dedication to delivering exceptional work ensures that each project I take on is of the highest quality. I specialize in creating custom themes, developing plugins, and building full-scale web systems. By staying up-to-date with the latest industry trends and best practices, I incorporate cutting-edge solutions into my work.


    Your email address will not be published. Required fields are marked *