nginx Installing WordPress In A Subdirectory

We have recently run into a number of customers using Store Locator Plus® that are having issues with the new REST based geocoding system. It turns out that an overwhelming percentage of people that are having issues have WordPress installed in a subdirectory. Apparently not all subdirectory installs are created equal — if it is not done properly things break.

What is a subdirectory install?

A subdirectory install is one in which WordPress is installed in a directory within the document root of a website. Sometimes this is done when WordPress is only managing one part of a website such as the newsfeed or blog. Other users use this install to separate WordPress core code from the add-on and upload code (plugins, themes, uploads) and the site configuration.

For the sake of this explanation we’ll reference the document root as being in the public_html directory and WordPress inside of a /wordpress directory within.

Our example install structure

Subdirectory versus normal URLs

In a fairly routine subdirectory installation the /wordpress/ content would manage something simple like the blog or news feed. I may even name the directory “blog”. When a user goes to my site, I’ll use “wpslp.test” as my example domain — they may go to http://wpslp.test/blog/ to get to my WordPress posts. This is the simple solution with no special expertise needed. You go to your WordPress admin and configure Settings | General | WordPress Address and Site Address and set them both to http://wpslp.test/wordpress/ (or blog or whatever). Done.

The problem becomes more complex when you want WordPress to “live” in a subdirectory but PRETEND it is the main directory. There are a few reasons to do this — one of which is the ability to easily test a new version of WordPress and quickly “flip back” to an older version without a lot of fuss. This is particularly helpful if you want to use source control to manage your site deployments or even upgrade WordPress directly from the code servers versus the install system.

WordPress In A Subdirectory As The Main Site

This particular installation requires some web server configuration wizardry. If not done exactly right there will be problems. Especially when it comes to the REST API in WordPress — and with WordPress 5 relying heavily on the REST API for the Block Editor (aka Gutenberg editor) system this can wreak havoc if not done properly.

What we are trying to accomplish is making WordPress serve all content as the “main site” while remaining in the /public_html/wordpress subdirectory. When people type in http://wpslp.test/ the goal is to have it serve up the WordPress site with no hint of a /wordpress/ subdirectory in the URL.

The Easy Hack

The easiest way to make this happen is to set your document root for the domain to be pointing to the WordPress subdirectory. In our example we would set our nginx (or Apache) config file for the wpslp.test domain to set the root (document root) directive to /srv/www/public_html/wordpress.

That works great if your wordpress subdirectory is serving up everything — but you’ll soon find you cannot have your web apps — like WordPress access anything outside of that directory without some major code hacking and issues with server ownership and permissions on directories and files.

If you want to tell WordPress to run code in a “sister directory” , like load all your plugins and themes from a sibling wp-content directory — you’ll need to do things differently.

Configuring nginx for a true WordPress subdirectory install

To get WordPress running in a subdirectory but leaving document root alone you need to tell the web server how to route traffic. When a user types http://wpslp.test/ you want them to actually run the /public_html/wordpress/index.php and start the app to serve up content.

If done properly ALL routing will go through the subdirectory with no hint of what is going on with your regular site visitors. Users login to your admin side of the site may notice a /wordpress/ URL in some links or in backend code or traffic — but for the vast majority of visitors it looks like a normal site. However, unknown to them — you have the ability to move stuff around and serve up non-WordPress content from the main /public_html directory or even better a new /public_html/static_content directory without burdening WordPress.

Our example nginx configuration

server {
    listen       80;
    listen       443 ssl;
    server_name  wpslp.test;

    root         /srv/www/public_html;
    index index.php;

    set          $upstream php56;
    proxy_buffers 8 1024k;
    proxy_buffer_size 1024k;

    error_log    /srv/www/log/nginx-error.log notice;
    access_log   /srv/www/log/nginx-access.log;

    ssl_certificate /vagrant/certificates/wpslp/dev.crt;
    ssl_certificate_key /vagrant/certificates/wpslp/dev.key;

        # Redirect all root requests to wordpress subdir
        #
        # note: if you have other things you want directly accessed in document_root
        # by the general public via a web URL you need to add a new location block to
        # explicitly allow that.
        #
        location / {
                rewrite_log on;
                rewrite ^(.*?)$ /wordpress$1;
        }

        # Do not use rewrite rules on things already going to the subdir
        # Unless the URL has /wp-json in it -- then rewrite it for permalink support
        location /wordpress/ {
            rewrite ^/wordpress/wp-json/(.*?)$ /wordpress/index.php?rest_route=/$1 last;
        }

        # PHP standard FPM processing for all files in /wordpress/ directory
        # note: if you want PHP to run in other directories you need to specify that.
        #
        location ~ /wordpress/.*\.php$ {
            error_log    /srv/www/wpslp/log/php-error.log info;

            fastcgi_split_path_info ^(.+\.php)(.*)$;
            fastcgi_param   SCRIPT_FILENAME         $document_root$fastcgi_script_name;
            fastcgi_param   PATH_INFO               $fastcgi_path_info;

            fastcgi_read_timeout 3600s;
            fastcgi_buffer_size 128k;
            fastcgi_buffers 4 128k;
            fastcgi_index  index.php;

            include        /etc/nginx/fastcgi_params;
            fastcgi_pass   $upstream;
        }
}

This file is the foundation of how to get nginx to route all content through the WordPress directory including our JSON (REST API) calls. The key components:

Our server_name is the main site URL (wpslp.test).

The root is /srv/www/public_html

This part of the server block sets the overall document root. This allows minor additions to this config file, for example adding a location /static/ {} block to serve up static content like http://wpslp.test/static/file.txt

The location / {} block

This block ells nginx to rewrite anything served up that starts with the main URL http://wpslp.test/* to do a “hidden” URL rewrite of http://wpslp.test/wordpress/* — as long as there is not a better location rule in the file (there are some…).

The location /wordpress/ {} block

Tells nginx , hey just serve up any URL with http://wpslp.test/wordpress/ exactly as requested. This keeps nginx from re-routing the URLS sent to http://wpslp.test/wordpress/ from the rule we just discussed and ending up giving us http://wpslp.test/wordpress/wordpress/wordpress… as our URL.

REST API Permalink Support

If you have enabled “pretty permalinks” — any link format other than “Plain” in WordPress Settings | Permalinks, then you must have a rewrite rule for your subdirectory to catch the wp-json URL and route it to the REST API router inside WordPress.

This is the rewrite ^/wordpress/wp-json/(.*?)$ /wordpress/index.php?rest_route=/$1 last; entry for JSON permalink routing.

The location ~ /wordpress/.*\.php$ {} block

This block ells nginx to do some special PHP processing of any URL that starts with /wordpress/ and ends with .php… our PHP application files like /wordpress/index.php to kick things off.

The PHP location block details

This block is CRITICAL to get right.

One of the most important parts is fastcgi_split_path_info ^(.+.php)(.*)$; directive. Look it up. This tells nginx WHAT SCRIPT to call – the first matching group in the regex. It also defines the parameters to pass to the script — the second matching group.

The fastcgi_split_path_info directive needs to be paired with TWO additional directives to set Fast CGI parameters.

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

This will expand $document_root to the MAIN document root (the /public_html) folder. However if you’ve set your regex properly it will extract the /wordpress/<script>.php part via the first parenthesis and send that along as the FULL $fastcgi_script_name variable. You should NOT need to hard-code any paths in here despite numerous (and IMO incorrect, Stack Overflow and online resources showing hard-coded paths).

fastcgi_param PATH_INFO $fastcgi_path_info;

This is fairly standard and will pass along the remaining path info which helps get your arguments to the PHP script. It is often missing from online examples of nginx WordPress subdirectory install instructions.

Configuring WordPress

Once you’ve set this up, restart nginx and PHP FPM using your standard server restart commands for these services.

Now you’ll need to set your site_url and home options in WordPress. You can do this with WP CLI options commands or a tool like phpMyAdmin.

site_url aka “WordPress Address” should be the actual path to WordPress http://wpslp.test/wordpress/

home aka “Site Address” should be the document root URL — the main URL you want people to “see” as your main site. http://wpslp.test in our example.

If you managed to get into WordPress admin without crazy redirects from within the WordPress PHP code redirecting you this can also be set via Settings | General from the admin interface.

WordPress subdirectory install settings.

A REST API Test

If everything is configured correctly, AND you are using “pretty permalinks” as your setting — you should be able to visit http://wpslp.test/wp-json/ and get a list of REST endpoints.

If you cannot do so, try setting your Permalinks to “plain”. This will set your REST API route to /index.php?rest_route=/ which SHOULD be processed by the standard routing in your web server. Going to this address should show your REST API routes: http://wpslp.test/index.php?rest_route=/

If that does NOT work then you either have a fundamental URL routing issue or the REST interface has been disabled. Check for proxy servers, proper web server rules and routing, or similar issues.

5 thoughts on “nginx Installing WordPress In A Subdirectory

    1. The answer is wrong.

      The correct method for setting up a localized JavaScript variable is to use the rest_url() function. Using home() as suggested can point the REST route to a non WordPress application. This opens up the possibility of routing to a insecure endpoint that does not validate the request correctly.

      Yes, the hack will work for the cases where people serve up WordPress from a redirected home URI, but breaks when another app is hosted there.

      IMO the correct fix is to properly set the web server configuration so the WordPress REST API url created when permalinks are enabled is redirected.

      It begs the question – why does the WordPress rest_url() function not account for WordPress in a subdir Tory when most other functions do. It seems like the older AJAX and general wp-admin URL functions use site_url yet rest_url() uses home.

  1. Hello! Great article!

    I almost got everything working, except that I must have Permalinks set to plain.
    Do you have any idea on why when my url has for e.g. /about or anything else, the rewrite doesn’t work?
    And if I just keep with plain option, could it be that in the future I have other issues which I may not be seeing right now? E.g.: different plugins/themes/etc…

    Thanks!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.