Cookie nonce is invalid – Multisite

I found the problem.

The links to the Network Admin are incorrect. For example, network users have:

http://my-local-cms.dev/wp-admin/network/

It should be (if wordpress core is located in backend/)

http://my-local-cms.dev/backend/wp-admin/network/

If you visit the first link and add /backend/ before wp-admin in the browser, then the network admin will behave correctly.

Fix

Edit 2017-08-30
New solution:

public function fix_network_admin_url($path, $scheme) {
    $path = ltrim($path, "https://wordpress.stackexchange.com/");
    return str_replace('/wp-admin/network', '/backend/wp-admin/network', $path);
}
add_filter('network_admin_url','fix_network_admin_url', 10, 3);

end of 2017-08-30 edit

The way I chose to solve this is to make a separate network admin menu with corrected links. This is not an elegant solution, but I don’t know how to fix the URLs only for the native Network Admin menu and leave the rest of the links intact (see “Other potential solutions”).

My solution is similar to this (add to either to function.php in theme or create a plugin):

add_action('admin_head', 'admin_head_network_menu');
add_action('admin_bar_menu', 'custom_network_admin_bar_menu', 20);

function admin_head_network_menu()
{
    echo '<style type="text/css">#wp-admin-bar-my-sites-super-admin {display: none}</style>';
}

function custom_network_admin_bar_menu($adminBar)
{
    // Don't add network admin bar for non super admins
    if (!current_user_can('manage_network')) {
        return;
    }

    $mainMenuID = 'my-new-network-admin-menu';

    $adminBar->add_menu([
        'id' => $mainMenuID,
        'title' => __('Network Admin'),
        'href' => new_admin_url(),
    ]);

    $adminBar->add_menu([
        'id' => $mainMenuID.'-sites',
        'parent' => $mainMenuID,
        'title' => __('Network Sites'),
        'href' => new_admin_url('sites.php'),
    ]);

    $adminBar->add_menu([
        'id' => $mainMenuID.'-users',
        'parent' => $mainMenuID,
        'title' => __('Network Users'),
        'href' => new_admin_url('users.php'),
    ]);

    $adminBar->add_menu([
        'id' => $mainMenuID.'-plugins',
        'parent' => $mainMenuID,
        'title' => __('Network Plugins'),
        'href' => new_admin_url('plugins.php'),
    ]);

    $adminBar->add_menu([
        'id' => $mainMenuID.'-settings',
        'parent' => $mainMenuID,
        'title' => __('Network Settings'),
        'href' => new_admin_url('settings.php'),
    ]);
}

function new_admin_url($path="")
{
    $url = network_admin_url($path);
    return str_replace('/wp-admin/network', '/backend/wp-admin/network', $url);
}

Other potential solutions

If you have a (sub)domain multisite installation, checkout “felixarntz/multisite-fixes” on Github (mu-plugins/wpms-site-url-fixer.php).

I think maybe this can be fixed with a change in .htaccess. For number of reasons this was not an option for my project. But if someone else want to have a go, here is an example .htaccess for Bedrock:

RewriteEngine On
RewriteBase /wp/
RewriteRule ^index\.php$ - [L]

# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]