I've been running WP core 4.4.2 for a while, and just upgraded to 4.6.1. I'm running a sub-directory multi-site installation.

I can't use automated updates. So I deactivate all plugins, copy the new core files, login as super-admin and then run www.example.com/wp/wp-admin/network/upgrade.php . When I click the link to Upgrade Network, it cycles through a couple of pages listing all sites (about 5 sites per page), and then says it's all done.

If I use phpMyAdmin and check the value of db_version in the various wp_nnn_options tables, the value does not change from the upgrade. But it does change in just the "root" site's wp_options table. At least for this update. It seems that for some other previous updates, the db_version value did change in the wp_nnn_options tables.

And for this update, if I manually run www.example.com/wp/wp-admin/sitename/upgrade.php for a particular sub-site, then the value of db_version does change in the wp_nnn_options table for that particular sub-site.

Is this normal/expected behavior? Should I refrain from manually running upgrade.php on the individual sub-sites and just trust the network upgrade to do the right thing?

  • Tricky one this - could be a multisite bug, but you'd have to provide conclusive tests that the devs could replicate. How many sites are we talking? Is the option of "manually" updating each one to 4.6.1 (or ideally now 4.7) a no go? You could always write a script to bump the db_version for every install - that might trick WP & solve things going forward i.e. future updates. Commented Dec 11, 2016 at 11:30
  • @TheDeadMedic: Are you saying you're confident that this is not expected behavior? One explanation that came to mind after posting was that if there were schema changes that only impacted the use of the root wp_options table, but not the use of the wp_nnn_options tables, then the site upgrade intentionally did not change the db_version value in the sub-site tables. I guess I could review the appropriate upgrade.php scripts to see if I can determine that. There are only a handful of sites at this time, so I could do them manually. But there should be more in future, so (cont'd)
    – sootsnoot
    Commented Dec 11, 2016 at 12:30
  • ...if this is expected behavior for upgrades, I'd rather not get into doing that. I suppose there's even a possibility that manually upgrading the sub-sites when the network upgrade doesn't do it could cause some sort of problem. My initial peek at the network upgrade script suggested that it basically loops over all sub-sites and runs each one's upgrade.php script (echoing the url of each sub-site in the list on the network upgrade page as it goes). So it surprised me that running each one manually produced a different result. But there are parameters passed by network upgrade...
    – sootsnoot
    Commented Dec 11, 2016 at 12:38
  • My experience with multisite is rusty, but I'm confident that all option tables should have their schema version upgraded, as an MS upgrade should loop over all sites and apply the update to the db - otherwise you're rocking sites with a db that's out of sync with the filesystem version of WP Commented Dec 11, 2016 at 13:11
  • Okay, thanks. That is what the code looks like when reading it - but it's not happening, and it's not reporting an error. So I'm stepping through code in the debugger. It looks like what's happening is that the site updates are getting done through calls to curl accessing my site. But my site runs under a Zend Framework application that requires login - so the curl responses redirect to a login page. I need to get the curl requests to use my Zend Framework session. I'll post an answer and ask a new question about getting that done. Thanks!
    – sootsnoot
    Commented Dec 12, 2016 at 18:14

1 Answer 1


Thanks to helpful prodding from TheDeadMedic, I see my problem is caused by my own application environment, which I failed to provide in the question because I didn't think it was relevant. Sorry about that! I usually do briefly describe the environment when posting a question, but it has never been relevant before, so I skipped it this time :-(

I'm using WordPress as a sort of subsystem to a Zend Framework MVC application that requires login. Access to nearly all WordPress pages requires that the user is logged-in to the ZF application; if there is not a logged-in ZF session when a request is made, the request is redirected to the login page, and the login page is displayed with a message saying login is required - but that page is displayed with a 200 status code (oddly enough, I've recently considered a change to give a 401 or 403 status when displaying the login page with that message, but I haven't done that yet).

The network upgrade.php script uses cURL to request the upgrade.php script for each sub-site in the network. Those cURL requests are actually getting a response (after redirect) that is just the login page for the ZF application; but since it has a status code of 200, the network upgrade believes that each site upgrade succeeded, and everyone is happy, even though nothing was changed in the wp db.

I was confused/mistaken when I said that the root site was upgraded. I must have updated it manually and forgotten I had done so when I used phpMyAdmin to see what happened to the database. Starting over from a backup, I can see that when running the network upgrade, none of the wp_*options tables got their db_version updated (nor did the wp_blog_versions table get updated).

At the moment I could update each sub-site manually since there are only 10 of them, but that number should grow substantially. I guess I'll ask a new question to see if there's a way to get the cURL requests to use the calling code's PHP session (the ZF application stores login state in the session).

The underlying problem and solution

As noted above, the cURL requests (which occur within WP_Http::request(), called by network/upgrade.php to upgrade each individual sub-site) were getting redirected to the ZF application's login page. This was because they were not accessing the same PHP session as the network upgrade script, because they were not being passed the PHPSESSID cookie. cURL and WP_Http::request() provide a means of passing cookies with a request, and there is a filter called 'http_request_args' that can be used to add cookies to a request. I could use that to pass the PHPSESSID cookie, which improved the behavior, but it still wasn't quite right. It turned out there were two additional problems:

  1. My code to integrate WordPress into the ZF application prevents access to the root site unless the WordPress user is a super-admin. And since WordPress uses cookies to maintain login state, those cookies also need to be passed in the requests that run the upgrade scripts. Apparently the individual upgrade scripts themselves don't check WordPress login status(!). So I used the 'http_request_args' filter to merge in the $_COOKIE superglobal.

  2. When a PHP session is active, the file holding the session data is locked. So in order for the request running an upgrade script to use the calling script's session, the calling script needs to call session_write_close() to unlock the session file. Not an issue for WordPress itself since it doesn't use the session, but essential for my integration with the ZF application. There was no hook I could easily find to add that call, so I edited it into the core class-http.php file immediately before its call to Requests::request, protected by tests that my ZF application integration is active, a PHP session is active, and the request is being made to the same server as specified in the current request:

    if (ZF_PLUGGABLE_RUN && session_status() == PHP_SESSION_ACTIVE && $defaults['cookies'] == $_COOKIE) {
        // PHP keeps session file locked, need to close it to allow the request to use it

I do try to avoid editing core files, but there are a handful of places where I haven't been able to find a suitable hook. I maintain the core files under git and separately track each file I change in each release, so that picking up a new release involves a fairly small well-defined merge process.

Final solution without editing core file

Although there wasn't a hook immediately before the call to Requests::request(), it turned out that the 'http_request_args' filter was close enough. So now I've removed the core file edits and just define the 'http_request_args' filter as follows (within a class that defines other filters for integrating WordPress with my ZF application). I only add this filter in the case where I want my ZF application integration to be active, so I don't need to test the ZF_PLUGGABLE_RUN constant:

public function http_request_args($args, $url) {
    $retval = $args;
    if (@parse_url($url, PHP_URL_HOST) == $_SERVER['SERVER_NAME']) {
        // The request is going to our own server, so we need to provide it our current cookies,
        // including both PHPSESSID for the session holding the ZF user, and the WordPress cookies
        // holding the WordPress user.
        if (! array_key_exists('cookies', $args)) {
            // The default array settings in WP_Http::request() include a 'cookies' element,
            // so that element should always be present in $args.
            throw new Exception("Unexpected: \$args does not contain an element name 'cookies'");
        if (! is_array($args['cookies'])) {
            // The default value of $args['cookies'] is an empty array, not null
            throw new Exception("Unexpected: \$args['cookies'] is not an array");

        // In known example of self-directed requests for database update, $args['cookies']
        // will be empty. But for generality, instead of assigning it a value, just merge in
        // the $_COOKIE superglobal, letting any user-specified cookies in $args['cookies'] override.
        $retval['cookies'] = array_merge($_COOKIE, $args['cookies']);

        // PHP keeps the session file locked, so we need to close it to allow the request to use it
        if (session_status() == PHP_SESSION_ACTIVE) {
    return $retval;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.