# WordPress Security

WordPress is a nightmare if you don't secure it properly, esp. when the site starts to grow and gets 'known' in the internet. I myself have moved to OctoberCMS but a whole lot of others still prefer WordPress over anything else. So here are 3 tips to secure your WordPress site from attackers.

1.  Install Wordfence plugin (currently version 9.0.0 as of this writing) from [wordfence.com](http://wordfence.com) or via your WordPress plugin dashboard. Wordfence Free is free - the paid version is $149 USD per year which is only required if your WordPress site grows exponentially with heavy traffic - otherwise the free edition should suffice. Wordfence protects your site from bots and attacks.
    
2.  If you're using Cloudflare for your domain then go to your domain and under DNS enable proxy under proxy status - in many cases we've been told to use DNS and not enable proxy. This is probably because it prevents server errors like error 500 to be caught on your application server and instead gets caught up at Cloudflare which will show you an error page from Cloudflare instead. Once you're done debugging, change this
    

![](https://cdn.hashnode.com/uploads/covers/5e5e8afd205a551d167f94ef/1d7dcc91-6a4a-425e-b278-e1d82bb8489c.png align="center")

to :

![](https://cdn.hashnode.com/uploads/covers/5e5e8afd205a551d167f94ef/4376ffe8-e2d3-45b1-a047-0df2da94c886.png align="center")

3.  Change the /wp-login.php to something else that only you and your users would know. Create a `custom_404.html` pure HTML+CSS page at the root of your WordPress installation directory.
    

If you're using **nginx**: Add this in /etc/nginx/sites-available/mydomain.com

```shell
server {
	# Add the below code here 	
}
```

```shell
    location = /wp-login.php {
        error_page 404 /custom_404.html;
        return 404;
    }
    
    # 2. Secret access point at /backend
    location = /backend {
    # Internally process this as wp-login.php without changing the browser URL
    fastcgi_param SCRIPT_FILENAME $document_root/wp-login.php;
    
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Ensure PHP version matches your setup
    }

	# 3. Allow public static CSS/JS/Image assets inside /wp-admin/
	location ~* ^/wp-admin/.*\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
	    try_files $uri =404;
	    expires max;
	    log_not_found off;
	}

    
	# 4. Protect /wp-admin (Return 404 if not logged in)
	location /wp-admin/ {
	    # Check if the WordPress logged-in cookie exists
	    if ($http_cookie !~* "wordpress_logged_in_") {
	        error_page 404 /custom_404.html;
	        return 404;
	    }

	    # If logged in, process PHP normally
	    try_files $uri $uri/ /index.php?$args;
	}
```

Restart nginx, if in the terminal via SSH, execute `sudo systemctl reload nginx`

And if you're using **Apache** with htaccess: (above the standard `# BEGIN WordPress` block)

```shell
# 1. Custom 404 error document mapping
ErrorDocument 404 /custom_404.html

# 2. Block direct access to /wp-login.php with a 404 error
<Files "wp-login.php">
    RewriteEngine On
    RewriteCond %{ENV:REDIRECT_STATUS} !=200
    RewriteRule ^ - [R=404,L]
</Files>

# 3. Secret login URL (/backend -> wp-login.php)
RewriteEngine On
RewriteRule ^backend/?$ wp-login.php [PT,L]

# 4. Protect /wp-admin (Return 404 if NOT logged in, BUT allow CSS/JS/Image assets)
RewriteEngine On
# Condition A: Request is targeting /wp-admin/
RewriteCond %{REQUEST_URI} ^/wp-admin/ [NC]
# Condition B: Request is NOT an allowed static file extension
RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ [NC]
# Condition C: User does NOT have the WordPress logged-in cookie
RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_ [NC]
# Rule: Return 404 error
RewriteRule ^ - [R=404,L]
```

Now, finally, add this PHP code to `wp-content/themes/themeName/functions.php` file:

```php
// 1. Rewrite internal wp-login.php URLs to /backend
add_filter('site_url', 'custom_login_url', 10, 4);
function custom_login_url($url, $path, $scheme, $blog_id) {
    if ($path === 'wp-login.php' || strstr($url, 'wp-login.php')) {
        return site_url('/backend', $scheme);
    }
    return $url;
}

// 2. Fix the Logout URL generation to pass a valid nonce with the custom path
add_filter('logout_url', 'custom_logout_url', 10, 2);
function custom_logout_url($logout_url, $redirect) {
    return wp_nonce_url(site_url('/backend?action=logout'), 'log-out');
}

// 3. Catch logout action early and force cookie destruction (Bypasses "Do you really want to log out?")
add_action('init', 'custom_force_logout');
function custom_force_logout() {
    // Intercept anytime /backend is called with action=logout
    if (isset($_GET['action']) && $_GET['action'] === 'logout') {
        // Log out the current user and destroy all session cookies
        wp_logout();
        
        // Clear auth cookies explicitly as a fallback
        wp_clear_auth_cookie();
        
        // Redirect cleanly to /backend without any query parameters
        wp_safe_redirect(site_url('/backend?loggedout=true'));
        exit;
    }
}

// 4. Ensure logout action redirects properly if triggered elsewhere
add_action('wp_logout', 'custom_logout_redirect');
function custom_logout_redirect() {
    wp_safe_redirect(site_url('/backend?loggedout=true'));
    exit;
}
```

PS: Assisted help with Google Gemini Flash 3.6 for code.
