Restrict access to the WordPress dashboard by IP address in Nginx

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.

6 Comments

  1. Thank you for sharing this. I was faced with a similar problem and this did exactly what I needed! Hopefully the performance isn’t too bad, but much better than doing allow/deny on directories.

  2. Any idea on how to hide/change the name of the “wp-admin/” folder? So if someone goes to domain.com/wp-admin it will return a 404 or 403. But then if they go to “secret-admin/” it will work and allow you to login and administrator the wordpress setup.

  3. > You can either modify the $request_uri check to allow access to wp-admin/admin-ajax.php

    Bjørn, could you please write this rule for request_uri.

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

  4. Aren’t IF conditions suboptimal in Nginx? Must be a simpler location block for the same purpose.

  5. didn´t work, also didn´t find where to add

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

Comments are closed.