If you have a static IP address, like from your office, or your own private VPN, you can increase your security tremendously by restricting all logins to that IP address. The effect is that even if an attacker knows your login credentials, they will not be able to log in or access any part of the WordPress Dashboard.

In our Nginx configuration we will do two checks: One for the request URI and one for the remote IP address. Nginx does not support neither nested if blocks nor a logical and operator in the config, but we can resort to a “clever hack”.

map $remote_addr $allowed_ip {

	#Your allowed IPs
	123.45.67.89 1;
	12.34.56.78 1;

	default 0;
}

server {
	[…]

	set $check '';

	if ( $allowed_ip = 0  ) {
		set $check "A";
	}

	if ( $request_uri ~ ^/wp-login\.php ) {
		set $check "${check}B";
	}

	if ( $check = "AB" ) {
		return 403;
	}

	[…]
}

For even better security, you can block the entire wp-admin area, effectually blocking access to any vulnerabilities or session hijacking. Change the $request_uri check from:

if ( $request_uri ~ ^/wp-login\.php ) {

To:

if ( $request_uri ~ ^/wp-(login\.php|admin/) ) {

The only issue is that you are probably using admin-ajax.php for all – or at least most – AJAX requests, even though you shouldn’t – as both Automattic and 10up have good arguments for (read them if you haven’t already done so). If you are using admin-ajax.php, and you want to block the entire wp-admin area, you have two options. You can either modify the $request_uri check to allow access to wp-admin/admin-ajax.php, or use a filter on admin_url to make sure admin_url() returns something else. Finally, add a corresponding rewrite rule for it in Nginx.

Here is some example code to fix the AJAX issue:

WordPress filter:

add_filter( 'admin_url', function( $url ) {
	if ( preg_match( '/\/admin-ajax\.php$/', $url ) ) {
		$url = '/ajax';
	}
	return $url;
}, 10, 1 );

Add this internal rewrite to your Nginx config:

server {
	[…]

	rewrite ^/ajax$ /wp-admin/admin-ajax.php last;

	[…]
}

This example code will change the output from WordPress so that all themes and plugins that use admin-ajax.php as an AJAX router will use the URL /ajax instead. The Nginx rewrite rule is an internal rewrite that works well with the configuration above and routes the requests to admin-ajax.php for full compatibility with your themes and plugins.