Working on a WordPress support site which has registered only user content, including uploaded PDF and ZIP files.

I'm looking for a way to prevent direct access to those PDFs and ZIP files inside the wp-content/uploads directory without the use of a plugin.

Reading through older questions, this is REALLY close (but comments are closed): https://wordpress.stackexchange.com/a/37743/18608 as it detects direct access to the file, then does some login to save the file requested and check to see if the user is logged in. If not, they get redirected to the WordPress login. If they are logged in, then it loads the file.

This is the original htaccess:

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*)$ dl-file.php?file=$1 [QSA,L]

However, the script blocks access to all files inside the wp-content/uploads directory, including images - so unless the user is logged in, even blog post images (which don't need protected) are hidden on the front-end.

I'm trying to revise the RewriteRule to check for only PDF or ZIP files, so that the dl-file.php file only gets called if it's a PDF or ZIP being requested.

I've tried the following, but it returns "404-File not found." when accessing a PDF or ZIP when the user is logged in, so even though the file type check appears to be working, the dl-file.php check is failing.

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/([^/]*\.(pdf|zip))$ filecheck.php?file=$1 [QSA,L]

How would I revise this so that it only calls the dl-file.php if it's a pdf or zip file being requested, but still pass the correct info to dl-file.php?

Thank you, Jonathon

 * dl-file.php
 * Protect uploaded files with login.
 * @link http://wordpress.stackexchange.com/questions/37144/protect-wordpress-uploads-if-user-is-not-logged-in
 * @author hakre <http://hakre.wordpress.com/>
 * @license GPL-3.0+
 * @registry SPDX


is_user_logged_in() || auth_redirect();

list($basedir) = array_values(array_intersect_key(wp_upload_dir(), array('basedir' => 1)))+array(NULL);

$file =  rtrim($basedir,'/').'/'.str_replace('..', '', isset($_GET[ 'file' ])?$_GET[ 'file' ]:'');
if (!$basedir || !is_file($file)) {
    die('404 &#8212; File not found.');

$mime = wp_check_filetype($file);
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
    $mime[ 'type' ] = mime_content_type( $file );

if( $mime[ 'type' ] )
    $mimetype = $mime[ 'type' ];
    $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );

header( 'Content-Type: ' . $mimetype ); // always send this
if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
    header( 'Content-Length: ' . filesize( $file ) );

$last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
$etag = '"' . md5( $last_modified ) . '"';
header( "Last-Modified: $last_modified GMT" );
header( 'ETag: ' . $etag );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );

// Support for Conditional GET
$client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;

if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )

$client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
// If string is empty, return 0. If not, attempt to parse into a timestamp
$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;

// Make a timestamp for our most recent modification...
$modified_timestamp = strtotime($last_modified);

if ( ( $client_last_modified && $client_etag )
    ? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
    : ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
    ) {
    status_header( 304 );

// If we made it this far, just serve the file
readfile( $file );
  • Note that if you put this comment at the top of the answer /** Plugin Name: Zip Plugin **/ it becomes a plugin, but what you're asking looks like it has nothing to do with WordPress, a pure Apache HTAccess question – Tom J Nowell Sep 29 '17 at 18:45
  • Thanks - the original post I was referencing contains the dl-file.php with login check / redirect that is WordPress specific - that's what I'm having trouble getting to work. Trying to check for pdf and zip, and still getting the conditional login check to work for the redirect portion. I've added more details for clarity. – d38 Sep 29 '17 at 19:47
  • I just noticed, you changed dl-file.php to filecheck.php? Presumably this is the same file? – MrWhite Sep 29 '17 at 21:54
  • Thank you, it is - I had changed it here, but kept it the same for the example. I missed my reference to it. – d38 Sep 29 '17 at 22:16
RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/([^/]*\.(pdf|zip))$ filecheck.php?file=$1 [QSA,L]

This actually looks OK, except if you have additional subdirectories within the /uploads directory? An alternative is to include an additional condition on the original rule that only rewrites the request if the request ends in .pdf or .zip. For example:

RewriteCond %{REQUEST_URI} \.(pdf|zip)$ [NC]
RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*)$ dl-file.php?file=$1 [QSA,L]

It shouldn't really matter, but make sure this goes before the WordPress front-controller.

  • Thank you - this makes sense. I'm testing here, but for some reason it's not picking up on the login condition, so the PDF is loading when logged out? – d38 Sep 29 '17 at 22:18
  • 1
    Thank you, MrWhite - this was the right direction. I couldn't get REQUEST_URL to work in the first RewriteCond, but changing to RewriteCond %{REQUEST_FILENAME} \.(pdf|zip)$ did the trick. – d38 Sep 29 '17 at 23:32
  • Ahhh, sorry that was a typo, it should be REQUEST_URI, not REQUEST_URL! (I've updated my answer.) Glad you got it working! – MrWhite Sep 29 '17 at 23:45
  • Hello, Thanks for your trick. I have a question : If i'm not logged, i'm redirected to login page when i put a URL with PDF file on the browser. Is it possible to redirect to 404 page? thanks again. – sampaii Apr 26 '19 at 9:01
  • @sampaii You would need to do that in your download/authentication script (in PHP)... if the user is not logged in then respond with a 404 instead of redirecting - which is presumably currently being triggered by your script - if you have followed the example above? – MrWhite Apr 26 '19 at 9:59

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.