Daily Email Summary of Failed Logins
Counts failed login attempts during the day (per username and IP address) and emails the administrator a short summary each morning, so you notice brute-force attacks without a flood of alerts.
Opens WPIDE on
Code
// Send the summary only when at least this many attempts failed.
$wpide_digest_minimum = 10;
add_action( 'wp_login_failed', function ( $username ) {
$day = wp_date( 'Y-m-d' );
$data = get_option( 'wpide_failed_logins' );
$data = is_array( $data ) && isset( $data['day'] ) && $data['day'] === $day ? $data : array( 'day' => $day, 'total' => 0, 'users' => array(), 'ips' => array() );
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
$user = substr( sanitize_user( (string) $username ), 0, 60 );
$data['total']++;
if ( count( $data['users'] ) < 200 || isset( $data['users'][ $user ] ) ) {
$data['users'][ $user ] = ( isset( $data['users'][ $user ] ) ? $data['users'][ $user ] : 0 ) + 1;
}
if ( count( $data['ips'] ) < 200 || isset( $data['ips'][ $ip ] ) ) {
$data['ips'][ $ip ] = ( isset( $data['ips'][ $ip ] ) ? $data['ips'][ $ip ] : 0 ) + 1;
}
update_option( 'wpide_failed_logins', $data, false );
} );
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'wpide_failed_login_digest' ) ) {
wp_schedule_event( strtotime( 'tomorrow 07:00' ) - (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ), 'daily', 'wpide_failed_login_digest' );
}
} );
add_action( 'wpide_failed_login_digest', function () use ( $wpide_digest_minimum ) {
$data = get_option( 'wpide_failed_logins' );
if ( ! is_array( $data ) || empty( $data['day'] ) || $data['day'] === wp_date( 'Y-m-d' ) ) {
return;
}
delete_option( 'wpide_failed_logins' );
if ( $data['total'] < $wpide_digest_minimum ) {
return;
}
arsort( $data['users'] );
arsort( $data['ips'] );
$lines = array( sprintf( '%d failed login attempts on %s.', $data['total'], $data['day'] ), '', 'Most tried usernames:' );
foreach ( array_slice( $data['users'], 0, 10, true ) as $name => $count ) {
$lines[] = sprintf( ' %s: %d%s', $name, $count, username_exists( $name ) ? ' (existing account)' : '' );
}
$lines[] = '';
$lines[] = 'Busiest IP addresses:';
foreach ( array_slice( $data['ips'], 0, 10, true ) as $ip => $count ) {
$lines[] = sprintf( ' %s: %d', $ip, $count );
}
wp_mail( get_option( 'admin_email' ), sprintf( '[%s] Failed login summary', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ), implode( "\n", $lines ) );
} );
Notes
No email is sent on days with fewer failed attempts than $wpide_digest_minimum.
How to add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.