Skip to content

Instantly share code, notes, and snippets.

@dknauss
Last active January 6, 2026 21:19
Show Gist options
  • Select an option

  • Save dknauss/08aa4226989fb3e92e086c4a7c2feb33 to your computer and use it in GitHub Desktop.

Select an option

Save dknauss/08aa4226989fb3e92e086c4a7c2feb33 to your computer and use it in GitHub Desktop.
Return 401 error response for failed WordPress logins.
add_action( 'wp_login_failed', function ():void {
status_header( 401 ); // Generates PHP header("HTTP/1.1 401 Unauthorized");
wp_die( 'Your login attempt failed.' ); // Kill WP/PHP execution with WSOD + error message.
});
// By default WordPress handles failed login attempts by reloading the login page and returning a HTTP 200 "OK" response
// with a message that reads "Error: The username/email address or password is incorrect. Please try again."
//
// By returning a HTTP 401 "Unauthorized" response instead, rate limiting tools watching the HTTP access log, like fail2ban
// and mod_security, will pick up on repeated bad login attempts from single IPs and ban them.
//
// Halting PHP execution immediately is a good way to shut the door on all mischief, with the explanation of your choice —
// or none at all. Otherwise, add a filter for the login_errors hook to override the default failed login message.
//
// A more robust approach to defending against brute force login attempts would also handle login requests made over XML-RPC
// and the REST API, which should be restricted or disabled if they're not being used. (Both tend to be available by default.)
//
// Example: https://github.com/amitrahav/WP-401-On-Failed-Login/blob/master/401-on-auth-fail-init.php
//
// "Why is this fence here?" Some Background: WordPress core comitters (and others) have disagreed that 401 (and 403) are the
// correct "modern standard" HTTP response for failed logins and that "all web application frameworks" use 401 (or 403) for
// this purpose.
//
// See WordPress core Trac ticket #25446 to understand why failed logins return HTTP 200 in WordPress, and why this has
// intentionally *not* been changed to 401 (or 403), despite requests to do so.
// https://core.trac.wordpress.org/ticket/25446
//
// It may be preferable to configure your rate limiting tools to catch HTTP POST requests to wp-login that result in HTTP
// STATUS CODE 200. (This only occurs when a login has failed and the login screen has been refreshed with login_errors
// displayed. Successfully posted logins get a 301 redirect, so they will not be confused with failed logins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment