Posted about 6 years ago. Visible to the public.

Nginx Virtual Store Subdirectories

Note - this article only applies to nginx and PHP-FPM!

If you want to have stores which are accessed via subdirectories as opposed to domain names, the standard method I've found is to make a directory with a copy of index.php in it, using one of the following approaches:

  • Leaving index.php exactly as-is, and calling fastcgi_param MAGE_RUN_CODE us; in your nginx config
  • Changing index.php to call a hardcoded string of us, either by altering the call to Mage::run(), or by setting $mageRunCode

I'm not happy with either of the approaches. It's pedantic, no question about it, but it really irritates me to a single entry point, and a method of dynamically setting the store being accessed (MAGE_RUN_CODE) and not using it. The solution is below - obviously you need to change the two occurrences of us to your store code, and /var/www to your Magento document root.

location ~* ^/us(?<store_uri>.*) { root /var/www; include /etc/nginx/fastcgi_params; fastcgi_param REQUEST_URI $store_uri?$args; fastcgi_param SCRIPT_FILENAME $document_root/index.php; fastcgi_param SCRIPT_NAME /index.php; fastcgi_param DOCUMENT_URI /index.php; fastcgi_param MAGE_RUN_CODE us; fastcgi_param MAGE_RUN_TYPE store; fastcgi_pass; }

With the above code, if not accessed via /us/* then our default store will be served, but if access via /us/* then the us store will be served, and /us/ will be stripped from the URI so that Magento doesn't try to serve a page like us/some/product.html from the us store.

The Attempt That Went Wrong

I tried initially using rewrites and failed dow to how Magento finds out its base URL (well, actually, it's Zend) - here is what happens:

  • A customer would try to access /us/some/product.html
  • nginx would rewrite everything in us to be non-us
  • PHP-FPM would run index.php, but
  • nginx would have given PHP-FPM the document root of /var/www and a request URI of /us/some/product.html

Now what happens is that Magento would remove /var/www from /us/some/product.html - being as nothing was in common, Magento now thought that the customer wanted /us/some/product.html from the us store, which obviously doesn't exist, but it would if we stripped /us off the front, so that's what we do in the solution above.

If you're interested, the logic that causes this behaviour is Zend_Controller_Request_Http, which is called through the following stack:

  • Mage_Core_Model_App calls initRequest()
    • That calls setPathInfo() on Mage_Core_Controller_Request_Http
      • That calls getBaseUrl() on Zend_Controller_Request_Http
        • That calls setBaseUrl() on Zend_Controller_Request_Http

The method responsible is below:

public function setBaseUrl($baseUrl = null) { if ((null !== $baseUrl) && !is_string($baseUrl)) { return $this; } if ($baseUrl === null) { $filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : ''; if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) { $baseUrl = $_SERVER['SCRIPT_NAME']; } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) { $baseUrl = $_SERVER['PHP_SELF']; } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) { $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility } else { // Backtrack up the script_filename to find the portion matching // php_self $path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : ''; $file = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : ''; $segs = explode('/', trim($file, '/')); $segs = array_reverse($segs); $index = 0; $last = count($segs); $baseUrl = ''; do { $seg = $segs[$index]; $baseUrl = '/' . $seg . $baseUrl; ++$index; } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); } // Does the baseUrl have anything in common with the request_uri? $requestUri = $this->getRequestUri(); if (0 === strpos($requestUri, $baseUrl)) { // full $baseUrl matches $this->_baseUrl = $baseUrl; return $this; } if (0 === strpos($requestUri, dirname($baseUrl))) { // directory portion of $baseUrl matches $this->_baseUrl = rtrim(dirname($baseUrl), '/'); return $this; } $truncatedRequestUri = $requestUri; if (($pos = strpos($requestUri, '?')) !== false) { $truncatedRequestUri = substr($requestUri, 0, $pos); } $basename = basename($baseUrl); if (empty($basename) || !strpos($truncatedRequestUri, $basename)) { // no match whatsoever; set it blank $this->_baseUrl = ''; return $this; } // If using mod_rewrite or ISAPI_Rewrite strip the script filename // out of baseUrl. $pos !== 0 makes sure it is not matching a value // from PATH_INFO or QUERY_STRING if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) { $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); } } $this->_baseUrl = rtrim($baseUrl, '/'); return $this; }

The important line is this bit:

$filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : ''; if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) { $baseUrl = $_SERVER['SCRIPT_NAME'];

Owner of this card:

Mike Whitby
Last edit:
about 6 years ago
Posted by Mike Whitby to Magento
This website uses cookies to improve usability and analyze traffic.
Accept or learn more