Which asset URLs are acceptable in a “vanilla” MU install?

Short Answer

example.com/bob/files/picture.jpg is the preferred, canonical URL for images in a WordPress Multisite installation. The two URLs with blogs.dir in the URL are essentially identical, and both leverage the filesystem structure. The path with ‘bob’ exists because you did a sub-directory install, not a subdomain install. Other paths would exist based on your other sites, e.g. example.com/fred/wp-content/blogs.dir/5/files/pictures.jpg. Otherwise, no other paths exist.

Long Answer

There’s a lot one can explain about this process, and I’m not 100% certain of the level of detail you’re looking for, so I’ll cove the basics here.

WordPress Multisite stores files by blog_id (the “5” after “/blogs.dir/”) to keep things organized and separate different sites’ files. This directory structure isn’t intended to be public. WordPress uses rewrite rules to route ^files/(.+) to wp-includes/ms-files.php?file=$1, and then wp-includes/ms-files.php processes and outputs the image and/or some helpful headers. There are a few advantages to this:

  • In terms of security, less information is always better. “wp-content/blogs.dir/5” is a little TMI — it says you’re running WordPress Multisite and that the blog_id is 5.
  • The URL structure is identical to that of a single-site installation. If you were to ever move a site from a Multisite installation to its own, you wouldn’t have to update those references in the database or 301 the old paths for external references.
  • You can move the files directory out of public access or deny from all through .htaccess so, for instance, people can’t access original image sizes if you don’t want.
  • You can add access control to specific files

There’s one main disadvantage, which is that the images/files are passing through PHP (and perhaps even require a few MySQL queries), so it requires more resources. If you have a caching plugin installed, the additional resources should be negligible.

With regards to filters, you can’t easily filter anything in the process for one reason: neither mu-plugins, plugins, or your theme get loaded*. The best you can do is override constants in wp-config.php. Here are the most useful/relevant constants you can override:

if ( !defined( 'UPLOADBLOGSDIR' ) )
    define( 'UPLOADBLOGSDIR', 'wp-content/blogs.dir' );

if ( !defined( 'UPLOADS' ) ) {
    // Uploads dir relative to ABSPATH
    define( 'UPLOADS', UPLOADBLOGSDIR . "/{$wpdb->blogid}/files/" );
    if ( 'wp-content/blogs.dir' == UPLOADBLOGSDIR )
        define( 'BLOGUPLOADDIR', WP_CONTENT_DIR . "/blogs.dir/{$wpdb->blogid}/files/" );
}

/**
 * Optional support for X-Sendfile header
 */
if ( !defined( 'WPMU_SENDFILE' ) )
    define( 'WPMU_SENDFILE', false );

/**
 * Optional support for X-Accel-Redirect header
 */
if ( !defined( 'WPMU_ACCEL_REDIRECT' ) )
    define( 'WPMU_ACCEL_REDIRECT', false );

*Even though plugins don’t get loaded, drop-ins do. Therefore, while you can’t use standard plugins, WordPress still lays the foundation to do whatever you need, like (as mentioned above) adding access control to sensitive files. The drop-in sunrise.php would be a good place to add such code.

Leave a Comment