Nginx + Wordpress in subdirectory

I have recently migrated our home site to ASP.NET Core 1.0. This allowed me to move website in linux environment. We also have /blog under this site which is a wordpress blog. All migrated properly except W3 Total Cache. Here is what I did.

Installed PHP-FPM and DNX both are behind reverse proxy on Nginx. Here is folder hierarchy. /var/www/aspnet /var/www/wordpress

Here are all Nginx related config files

/etc/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    log_format scripts '$document_root$fastcgi_script_name > $request';
    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

}

/etc/nginx/sites-available/xyz.com

server {
    listen 443 ssl http2;
    server_name xyz.com www.xyz.com;
    ssl_certificate /etc/ssl/certs/cert_chain.crt;
    ssl_certificate_key /etc/ssl/private/xyz.private.txt;   
    access_log /var/log/nginx/scripts.log scripts;

    # Global restrictions configuration file.
    # Designed to be included in any server {} block.
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
    # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
    location ~ /\. {
        deny all;
    }

    # Deny access to any files with a .php extension in the uploads directory
    # Works in sub-directory installs and also in multisite network
    # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
    location ~* /blog/(?:uploads|files)/.*\.php$ {
        deny all;
    }

    gzip_types text/css text/x-component application/x-javascript application/javascript text/javascript text/x-js text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;
    location ~ ^/blog/\.(css|htc|less|js|js2|js3|js4)$ {
        expires 31536000s;
        add_header Pragma "public";
        add_header Cache-Control "max-age=31536000, public";
    }
    location ~ \.(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml)$ {
        expires 3600s;
        add_header Pragma "public";
        add_header Cache-Control "max-age=3600, public";
    }
    location ~ \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|json|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|wav|wma|wri|woff|xla|xls|xlsx|xlt|xlw|zip)$ {
        expires 31536000s;
        add_header Pragma "public";
        add_header Cache-Control "max-age=31536000, public";
    }
    location / {
        proxy_pass http://unix:/var/www/aspnet/kestrel.sock;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }
    location /phpmyadmin/ {
        alias /var/www/phpMyAdmin/;
        index index.php;
    }

    location ~ ^/phpmyadmin/(.+\.php)$ {

        alias /var/www/phpMyAdmin/$1;
        fastcgi_pass   unix:/run/php/phpmyadmin.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $request_filename;

        # From fastcgi_params
        fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;
        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      /var/www/phpMyAdmin;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;
        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;
        fastcgi_param  REDIRECT_STATUS    200;
    }

    location /blog/ {           
        try_files $uri $uri/ index.php?q=$request_uri;
        alias /var/www/wordpress/;
        index index.php;                        
    }   

    location ~ \.php$ {             
        include /var/www/wordpress/nginx.conf;
        try_files $uri $uri/ index.php?q=$request_uri =404;
        alias /var/www/wordpress/$1;
        fastcgi_pass   unix:/run/php/phpmyadmin.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;


        # From fastcgi_params
        fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;
        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      /var/www/wordpress;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;
        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;
        fastcgi_param  REDIRECT_STATUS    200;          
    }
}

/var/www/wordpress/nginx.conf - This is the file generated by W3 Total Cache plugin.

# BEGIN W3TC Page Cache core
set $w3tc_rewrite 1;
if ($request_method = POST) {
    set $w3tc_rewrite 0;
}
if ($query_string != "") {
    set $w3tc_rewrite 0;
}
if ($request_uri !~ \/$) {
    set $w3tc_rewrite 0;
}
if ($http_cookie ~* "(comment_author|wp\-postpass|w3tc_logged_out|wordpress_logged_in|wptouch_switch_toggle)") {
    set $w3tc_rewrite 0;
}
if ($http_cookie ~* "(w3tc_preview)") {
    set $w3tc_rewrite _preview;
}
set $w3tc_ssl "";
if ($scheme = https) {
    set $w3tc_ssl _ssl;
}
set $w3tc_enc "";
if ($http_accept_encoding ~ gzip) {
    set $w3tc_enc _gzip;
}
set $w3tc_ext "";
if (-f "$document_root/var/www/wordpress/wp-content/cache/page_enhanced/$http_host/$request_uri/_index$w3tc_ssl$w3tc_rewrite.html$w3tc_enc") {
    set $w3tc_ext .html;
}
if (-f "$document_root/var/www/wordpress/wp-content/cache/page_enhanced/$http_host/$request_uri/_index$w3tc_ssl$w3tc_rewrite.xml$w3tc_enc") {
    set $w3tc_ext .xml;
}
if ($w3tc_ext = "") {
  set $w3tc_rewrite 0;
}
if ($w3tc_rewrite = 1) {
    rewrite .* "/var/www/wordpress/wp-content/cache/page_enhanced/$http_host/$request_uri/_index$w3tc_ssl$w3tc_rewrite$w3tc_ext$w3tc_enc" last;
}
# END W3TC Page Cache core

Once I have done above configuration. I am able to run permalinks of wordpress. However when I open /blog/ or /blog/wp-admin/ it shows not found. For troubleshooting purpose I added some custom logging in nginx as follows.

log_format scripts '$document_root$fastcgi_script_name > $request';

Here is what logs showed

/var/www/wordpress//blog/wp-admin/index.php > GET /blog/wp-admin/index.php HTTP/2.0 /var/www/wordpress//blog/index.php > GET /blog/ HTTP/2.0

I tried many solution. All of them assume that parent site would be wordpress. In my case parent site is built on DotNet Core 1. /blog is my wordpress blog. The issue must be one of the wrong rewrite rules.

To summarise the issue, I W3 Total Cache is not an issue. I can live without that. The issue on hand is regarding rewriting rules when you host a wordpress site as a subdomain to a static site. One can ignore W3 total cache configuration part. I have hosted site like mydomain.com/blog and rewriting rules are not getting applied. I tried many alternatives as of now. If somebody has implemented Wordpress as a pure subdirectory and not multisite subdirectory. They can provide their successful configuration.


This doesn't directly answer your question, but is probably a better option you may not have considered. Instead of using a plugin to do caching, using Nginx page caching. It's much faster because you don't have to make a call to PHP, which eliminates a lot of overhead.

The downside is it's tricky to invalidate the Nginx cache unless you pay for the commercial version of Nginx. You can build Nginx with plugins that do the job, but the Wordpress / Nginx caching integration is not great. None I've found work well. So you need to set your cache maximum life carefully. Interestingly, on a busy site caching for even a few seconds can have benefits. My sites change rarely, and if I need to I can just rm -rf the correct directories where the nginx page cache lives - which is actually in memory.

I have a tutorial on this here, and there will be many others around. There's a great article on Nginx microcaching here.

SF likes actual data on the page in case websites disappear.

In your nginx.conf

fastcgi_cache_key "$scheme$request_method$host$request_uri";

At the top of your site file, or in your nginx config

fastcgi_cache_path /dev/shm/nginxcache levels=1:2 keys_zone=CACHENAME:10m inactive=1440m; # Centos / Amazon Linux in RAM, 1440 minutes = 24 hours

In your location block that calls PHP

fastcgi_pass php56-fpm;
include        fastcgi_params;
fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
fastcgi_cache CACHENAME;
fastcgi_cache_valid 200 1440m;
fastcgi_cache_valid 403 404 405 410 414 301 302 307 60m;
add_header X-Cache $upstream_cache_status; # This can be removed if desired

fastcgi_cache_methods GET HEAD;
fastcgi_keep_conn on;

That tutorial I linked to has a lot more information and explanation.


You need a few steps along the way to fix the issue (from the current situation)...

In the filesystem...

See: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory

  1. Move WordPress core files from /var/www/wordpress to /var/www/wordpress/blog. (Hint: mv /var/www/wordpress /var/www/blog && mkdir /var/www/wordpress && mv /var/www/blog /var/www/wordpress)

  2. Copy /var/www/wordpress/blog/index.php to the directory /var/www/wordpress/.

  3. Edit the /var/www/wordpress/index.php file and change the line require( dirname( __FILE__ ) . '/wp-blog-header.php' ); to require( dirname( __FILE__ ) . '/blog/wp-blog-header.php' );.

Nginx:

Now, the Nginx part. Here are the changes in the relevant part/s...

location /blog {           
    root /var/www/wordpress;
    index index.php;
    try_files $uri $uri/ /blog/index.php?q=$request_uri;                        
}   

location ~ \.php$ {             
    root /var/www/wordpress;
    index index.php;
    try_files $uri $uri/ /blog/index.php?q=$request_uri =404;

    # other configuration directives
}

There are two primary changes:

  1. The alias directive becomes root
  2. The try_files directive contains /blog/ before index.php.

I hope this helps!