Change the WordPress login URL without a plugin

The default WordPress login page sits at /wp-login.php or /wp-admin/, and every automated scanning bot on the internet knows this. Within hours of a new WordPress site going live its login page begins receiving brute-force attempts from botnets that cycle through millions of username and password combinations around the clock. These attempts do not just risk account compromise — they consume server resources, inflate your server access logs, and can trigger rate limiting or temporary bans from security plugins and hosting platforms that mistake the load for legitimate traffic. Moving the login URL to a non-standard path is a low-effort security hardening measure that eliminates the vast majority of automated login attacks simply because the bots have no way to know your custom URL. It is not a complete defense on its own — a determined attacker who knows your site can find the login page regardless, and the measure does nothing to strengthen weak passwords or prevent session hijacking — but as one layer in a defense-in-depth strategy it measurably reduces server noise. Popular plugins like WPS Hide Login implement this with a single option and excellent reliability, and for most sites installing the plugin is the recommended approach. However, on environments where you want to minimize the installed plugin count or where the hosting provider restricts certain plugin categories, you can implement the same behavior purely in PHP and .htaccess. The approach involves rewriting requests to a custom slug to route to wp-login.php internally while making direct access to wp-login.php return a 404 response. WordPress’s built-in rewrite system via the rewrite_rules_array filter handles the routing side, and the login_url and site_url filters ensure that all internal login form actions, redirects, and plugin-generated links use your custom URL rather than the default. This approach also requires blocking direct requests to wp-login.php at the .htaccess level, which complements the security rules we covered for protecting sensitive WordPress files. Additionally, redirecting users after login based on their role pairs naturally with a custom login URL to provide a fully tailored authentication experience.

Problem: The default /wp-login.php URL receives constant automated brute-force attempts that waste server resources and create security risk.

Solution: Define a custom login slug and add the following to your functions.php file:

<?php
define( 'HA_LOGIN_SLUG', 'my-login' ); // change to your preferred slug

// Rewrite custom slug to wp-login.php internally
add_filter( 'rewrite_rules_array', 'ha_add_login_rewrite_rule' );

function ha_add_login_rewrite_rule( $rules ) {
    $new_rule = array( HA_LOGIN_SLUG . '$' => 'index.php?ha_login=1' );
    return $new_rule + $rules;
}

// Register custom query var
add_filter( 'query_vars', function( $vars ) {
    $vars[] = 'ha_login';
    return $vars;
} );

// Serve wp-login.php for requests to the custom slug
add_action( 'template_redirect', 'ha_serve_custom_login' );

function ha_serve_custom_login() {
    if ( get_query_var( 'ha_login' ) ) {
        require_once ABSPATH . 'wp-login.php';
        exit;
    }
}

// Rewrite login URL throughout WordPress
add_filter( 'login_url', 'ha_rewrite_login_url', 10, 3 );

function ha_rewrite_login_url( $login_url, $redirect, $force_reauth ) {
    $login_url = home_url( HA_LOGIN_SLUG );
    if ( $redirect ) {
        $login_url = add_query_arg( 'redirect_to', urlencode( $redirect ), $login_url );
    }
    return $login_url;
}

// Block direct access to wp-login.php (return 404)
add_action( 'init', 'ha_block_default_login' );

function ha_block_default_login() {
    if ( isset( $_SERVER['REQUEST_URI'] ) ) {
        $uri = wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
        if ( '/wp-login.php' === $uri && ! get_query_var( 'ha_login' ) ) {
            global $wp_query;
            $wp_query->set_404();
            status_header( 404 );
            get_template_part( 404 );
            exit;
        }
    }
}

NOTE: Change my-login in the HA_LOGIN_SLUG constant to something unique and not obvious — avoid common alternatives like login, admin-login, or sign-in that bots also try. After adding this code, flush permalink rules by visiting Settings → Permalinks and clicking Save. Make absolutely certain you record your custom login URL before saving — if you lose it and are logged out, you will need FTP access to remove the code. For most production sites, the WPS Hide Login plugin is a safer choice as it handles edge cases and plugin conflicts automatically.