← Login Delay Shield Docs

Developer Reference

Hooks, public functions, database schema, transient keys, and internals for Login Delay Shield v2.6.0.

Architecture

Login Delay Shield is a single-plugin WordPress security plugin. Its main files:

FilePurpose
wp-login-delay.phpMain plugin file — all security logic, hooks, database management, dashboard widget
wldelay-settings.phpSettings registration via WordPress Settings API, sanitization, validation
wldelay-settings-view.phpLDS_Settings_View class — admin UI rendering (collapsible sections, status badges, summary box, telemetry, audit log)
wldelay-features.phpLDS_Features — single source of truth for every option key, type, and default
wldelay-pipeline.phpwldelay_process_failed_attempt() — the single failure-processing pipeline shared by wp-login, XML-RPC, REST, application-password, and password-reset
wldelay-botnet.phpDistributed-attack (botnet) detection — per-username distinct-IP tracking, alert fan-out, recent-detection feed
wldelay-recovery.phpEmergency Recovery URL — opt-in secret token that lets a locked-out admin clear their own IP lockout without admin or server access
wldelay-persistence.phpDurable lockout store (WLDelay_DB_Persistence) and the lockout database table
wldelay-audit.phpAdmin/security audit log — table, write path, health/recovery tracking
wldelay-async.phpEvent firehose and deferred-task queue (shutdown/cron flush)
wldelay-fail2ban.phpfail2ban log writing, path validation, and rotation
wldelay-enumeration.phpUsername/author enumeration hardening
wldelay-privacy.phpGDPR personal-data exporter and eraser registration
wldelay-migration.phpSettings-version migration runner
wldelay-changelog.phpIn-admin changelog page parsed from readme.txt
admin.jsAdmin JavaScript — collapsible sections, tooltips, and UI interactions
admin.cssAdmin styles for the settings page and dashboard widget
uninstall.phpUninstall routine — removes options, the log table, registered transients, and the plugin-owned fail2ban log directory on plugin deletion

Failed-attempt counters live in WordPress transients. Active lockouts are written to a dedicated database table through a filterable persistence store (WLDelay_DB_Persistence), with a transient layer for fast lookups and a graceful fallback to transient-only state when the durable write fails. Three custom tables back the failed login log, the lockout store, and the admin/security audit log. A lightweight deferred-task queue moves non-critical writes (audit rows, expired-lockout purges) off the request hot path to shutdown or a daily cron backstop. Every authentication surface (wp-login, XML-RPC, REST, application passwords, password reset) routes failures through one pipeline, wldelay_process_failed_attempt(), which emits a failed_attempt event; distributed-attack detection subscribes to that event and defers its cross-IP query to the same queue. Custom Login URL uses WordPress rewrite rules. No custom REST API endpoints or post types are registered.

When the plugin is deleted, uninstall.php removes its options, the failed-login log table, registered transients, and the plugin-owned fail2ban log directory. The cleanup is multisite-aware and leaves any custom fail2ban log path untouched. Since v2.3.4.

Constants

ConstantValueDescription
WLDELAY_VERSION'2.6.0'Plugin version
WLDELAY_PLUGIN_FILE__FILE__Main plugin file path
WLDELAY_OPTION_NAME'wldelay_options'WordPress option name for all settings
WLDELAY_DB_VERSION'7'Schema version, tracked in the wldelay_db_version option to drive dbDelta() upgrades

Emergency Constants

Define these in wp-config.php to recover access. They are read with defined() — the plugin never sets them.

ConstantEffect
WLDELAY_SAFE_MODEWhen true, disables every delay, lockout, and tracking path (login, XML-RPC, REST, application passwords, password reset) — the plugin behaves as if every IP were whitelisted. A persistent, non-dismissible admin notice shows while it is active. Since v2.4.0.
WLDELAY_DISABLE_CUSTOM_LOGINWhen defined, bypasses the Custom Login URL feature so /wp-login.php works again. Since v2.4.0.

Emergency Recovery Constants

Tuning constants for the Emergency Recovery URL. Each is defined with a defined() guard, so you can override it in wp-config.php. Since v2.6.0.

ConstantValueDescription
WLDELAY_RECOVERY_QUERY_VAR'wldelay_recovery'Query var carrying the recovery token: /?wldelay_recovery=<token>
WLDELAY_RECOVERY_NAG_DAYS90Token age (days) after which the settings page nags you to rotate it
WLDELAY_RECOVERY_RL_MAX5Failed token attempts allowed per IP inside the rate-limit window
WLDELAY_RECOVERY_RL_WINDOW900Rate-limit window in seconds (15 minutes)
WLDELAY_RECOVERY_REVEAL_TTL300Seconds the freshly generated URL stays available to view/download (5 minutes)

Filter Hooks

wp_authenticate_user

filter · priority 1

Main authentication filter. Applies delays and lockouts to login attempts. Parameters: $user (WP_User|WP_Error), $password. Returns WP_Error if locked out, otherwise applies sleep() delay on failure and returns $user.

rest_authentication_errors

filter · priority 20

REST API authentication protection. Applies delays and lockout to failed REST API authentication when wldelay_rest_enabled is on. Returns a 403 WP_Error if the IP is locked out. Respects the IP whitelist and skips application-password attempts (handled separately). Since v2.1.2.

authenticate

filter · priority 25

Application-password authentication protection. Applies delays and lockout to failed Basic Auth attempts when wldelay_application_password_enabled is on. Detects app-password attempts via PHP_AUTH_USER/PHP_AUTH_PW server headers. Parameters: $user, $username, $password. Since v2.1.2.

authenticate

filter · priority 99

Blocks XML-RPC authentication when the “block XML-RPC” setting is enabled. Parameters: $user, $username, $password. Returns WP_Error('wldelay_xmlrpc_blocked') for XML-RPC requests.

wldelay_2fa_providers

filter

Customize the list of recognized 2FA plugins for the settings-page health check. Receives an associative array of slug => [ plugin_basename, … ]. Return the modified array to add or remove providers. The default map includes two-factor, wp-2fa, mini-orange, and google-authenticator. Since v2.1.4.

wldelay_2fa_coverage_checkers

filter

Register provider-specific callbacks that report privileged-user 2FA coverage for the settings-page notice. Receives a map of provider_slug => callable (the built-in entry covers the two-factor plugin). Each callback returns an array with keys supported, privileged_total, protected, unprotected, and unknown.

wldelay_normalize_username

filter

Customize how usernames are normalized for failure tracking and lockout identity. Receives the normalized username and the original input. Return your own normalized value to support LDAP, email-as-login, or SSO backends where the default lowercase/sanitize behavior is not enough. Since v2.3.0.

wldelay_persistence_store

filter

Swap the lockout persistence backend. Receives null; return a WLDelay_Persistence implementation to replace the default DB-backed store (for example a Redis or external store) without touching call sites. Falls back to WLDelay_DB_Persistence when the filter returns anything else.

wldelay_login_help_url

filter

Set the “Need help getting in?” link shown in the login-page lockout feedback block. Defaults to the site’s lost-password URL (respecting any custom-login-slug rewrite); point it at a support or docs page instead. Returned value is escaped at output.

wldelay_fail2ban_allowed_log_dirs

filter

Extend the set of directories a custom fail2ban log path may live in. By default only the plugin-owned protected directory is allowed; add server-protected directories here. Web-server protection files (.htaccess, index.html, index.php) are written to every allowed directory. A custom path outside the allowed set disables logging rather than writing elsewhere.

wldelay_fail2ban_max_log_bytes

filter

Control the size at which the fail2ban log is rotated to a single .log.1 backup. Defaults to 5 MB. Return 0 to disable in-plugin rotation and rely on system logrotate instead. Since v2.3.4.

wldelay_botnet_ip_threshold

filter

Number of distinct source IPs targeting one username that triggers a distributed-attack alert. The stored option is clamped to [2, 100] before the filter runs, and the result is re-floored at 2 so a filter cannot drop below the minimum. Default 5. Since v2.5.0.

wldelay_botnet_window

filter

Sliding detection window, in seconds. The stored minutes option is clamped to [5, 60] and converted to seconds before the filter; the result is floored at MINUTE_IN_SECONDS. Default 900 (15 minutes). Since v2.5.0.

wldelay_botnet_alert_cooldown

filter

Per-username cooldown, in seconds, that suppresses repeat distributed-attack alerts after one fires. Default HOUR_IN_SECONDS. Since v2.5.0.

wldelay_cloudflare_ip_ranges

filter

The list of Cloudflare CIDR ranges used to validate the CF-Connecting-IP header. The header is honored only when REMOTE_ADDR falls inside one of these ranges, so it cannot be spoofed. Return your own array to extend or replace the built-in IPv4/IPv6 ranges. Since v2.4.0.

wldelay_send_custom_login_email

filter

Whether to email the new login URL to the site admin when the Custom Login URL feature is enabled or its slug changes. Default true; return false (e.g. __return_false) to suppress the recovery email. Since v2.4.0.

wldelay_help_base_url

filter

Base URL for the “Learn more” documentation links on each settings card. Defaults to https://damoiseau.xyz/docs/login-delay-shield/user-guide/. Return an empty string to hide the links, or point them at your own mirror. Since v2.5.0.

wldelay_export_login_log_should_exit

filter

Controls whether the CSV export handler calls exit() after streaming. Default true. Set to false in test environments. Since v2.1.2.

plugin_action_links_{basename}

filter

Adds a “Settings” link to the plugin’s entry on the Plugins page.

Action Hooks

wp_login_failed

action

Hooked to log failed login attempts to the database. Parameters: $username. Records the IP address, username, timestamp, and source (wp-login, xmlrpc, rest, application-password, or password-reset).

wldelay_cleanup_logs

action · custom

Custom cron hook that runs daily. Deletes log entries older than the configured retention period. Processes in batches of 1,000 rows.

wp_dashboard_setup

action

Registers the “Recent Failed Login Attempts” dashboard widget.

admin_post_wldelay_unlock_current_ip

action

Handles the “Unlock Current IP” button on the settings page. Removes the lockout and failure counters for the requesting admin’s IP address. Nonce-protected and requires manage_options capability.

admin_post_wldelay_export_login_log

action

Handles CSV export of the failed login log. Streams results in 1,000-row batches with formula-injection protection. Reads optional filters from $_GET (source, IP, username, date range). Nonce-protected and requires manage_options capability. Since v2.1.2.

admin_post_wldelay_recovery_generate

action

Mints a new Emergency Recovery token, stores its hash, emails the URL to the site admin, and sets the one-time reveal. Nonce-protected, requires manage_options. Since v2.6.0.

admin_post_wldelay_recovery_download

action

Streams the once-revealed recovery URL as a .txt download. Nonce-protected, requires manage_options; only works inside the reveal window. Since v2.6.0.

admin_post_wldelay_recovery_confirm / admin_post_nopriv_wldelay_recovery_confirm

action

Handles the confirm POST from the recovery landing page. Re-validates the token and nonce, then clears the caller’s own IP lockout (it never logs the user in). Registered for both authenticated and unauthenticated callers so a locked-out admin can use it. Since v2.6.0.

wldelay_event_{event}

action · custom

Per-event hook fired for each plugin event (e.g. wldelay_event_cron_tick). Receives the event payload array. Subscribe with wldelay_on_event(), or hook the generic wldelay_event action (which receives $event_name, $payload) to observe every event as an audit/SIEM firehose.

wldelay_persistence_write_failed

action · custom

Fires when a lockout could not be written to the durable store. Parameters: $ip, $type (login or password-reset). The lockout falls back to transient-only state until the store recovers.

wldelay_audit_write_failed

action · custom

Fires when an audit-log row could not be written. Parameters: $action (the action key whose row was lost), $error (DB error string).

Error Codes

Error CodeContextDescription
wldelay_ip_lockedLogin formIP or IP+username is locked out. Message includes a countdown.
wldelay_xmlrpc_blockedXML-RPCXML-RPC authentication is disabled on this site.
wldelay_attempts_remainingLogin formShows remaining attempts before lockout (appended to existing error).

Core Functions

FunctionReturnsDescription
wldelay_get_options()arrayGet cached plugin options. Uses a global cache to avoid repeated get_option() calls.
wldelay_clear_options_cache()voidClear the in-memory options cache. Called automatically when settings are updated.
wldelay_get_delay_value( $failure_count )intCalculate the delay in seconds based on current settings (fixed, random, or progressive) and the number of failures.
wldelay_get_client_ip()stringGet the client IP address. Proxy headers are read only when wldelay_trust_proxy_headers is enabled: CF-Connecting-IP (honored only when REMOTE_ADDR is a Cloudflare edge), X-Sucuri-ClientIP, Client-IP, X-Real-IP, then X-Forwarded-For. Each candidate is validated as an IP; a garbage value falls through to REMOTE_ADDR.
wldelay_get_cloudflare_ip_ranges()arrayBuilt-in Cloudflare IPv4/IPv6 CIDR ranges, filterable via wldelay_cloudflare_ip_ranges. Since v2.4.0.
wldelay_is_cloudflare_remote_addr( $remote_addr )boolWhether the TCP peer is within a Cloudflare range (gates CF-Connecting-IP trust). Since v2.4.0.
wldelay_detect_proxy_headers()arrayHuman-readable names of any proxy/CDN forwarding headers present on the current request. Since v2.4.0.
wldelay_get_proxy_health_status()arrayReturns status (misconfigured-cdn, spoofable, ok, or none) and the detected headers, powering the settings-page proxy health notice. Since v2.4.0.
wldelay_process_failed_attempt( $username, $source, $context )voidUnified failure-processing pipeline shared by all authentication surfaces. Logs the attempt, updates counters/lockout, and emits the failed_attempt event. Since v2.5.0.
wldelay_get_lockout_attempt_strategy( $options )stringReturns 'ip' or 'ip_username'.
wldelay_normalize_username( $username )stringLowercases and sanitizes a username for consistent tracking.
wldelay_get_attempt_identifier( $ip, $username, $options )stringReturns $ip or $ip|$username based on strategy.
wldelay_get_requested_login_username()stringGets the normalized username from $_POST['log'].
wldelay_is_xmlrpc_request()boolChecks XMLRPC_REQUEST constant and REQUEST_URI.
wldelay_is_rest_request()boolChecks if the current request is a WordPress REST API request. Since v2.1.2.
wldelay_is_application_password_attempt()boolChecks for PHP_AUTH_USER and PHP_AUTH_PW server headers (Basic Auth). Since v2.1.2.
wldelay_get_login_source()stringReturns 'xmlrpc', 'rest', 'application-password', 'password-reset', or 'wp-login'.
wldelay_get_login_source_label( $source )stringHuman-readable label for a source key, used by the dashboard widget and log views.
wldelay_custom_login_is_active()boolWhether the Custom Login URL feature is enabled and the slug is valid. Since v2.2.3.
wldelay_get_custom_login_slug()stringReturns the configured custom login slug (default 'my-login'). Since v2.2.3.

Lockout Functions

FunctionReturnsDescription
wldelay_is_ip_locked( $ip, $username )boolCheck if an IP (or IP+username) is currently locked out.
wldelay_lock_ip( $ip, $username )voidLock an IP/username. Sets a transient with the lockout duration.
wldelay_get_failure_count( $ip, $username )intGet the current failure count for an IP/username.
wldelay_get_lockout_remaining_seconds( $ip, $username )intSeconds remaining on a lockout (0 if not locked).
wldelay_get_lockout_duration_seconds( $options )intGet the configured lockout duration in seconds.
wldelay_get_lockout_error_message( $ip, $username )stringBuild the lockout error message with human-readable countdown.
wldelay_track_failed_attempt( $username )intIncrement failure counter, trigger lockout and/or email if thresholds are met.
wldelay_send_notification_email( $ip, $username, $attempts )voidSend email alert (respects site-wide cooldown).

Whitelist Functions

FunctionReturnsDescription
wldelay_is_ip_whitelisted( $ip )boolCheck if an IP is in the whitelist. Returns false if whitelist is disabled.
wldelay_ip_in_range( $ip, $range )boolCheck if an IP matches a single address or CIDR range. Supports both IPv4 and IPv6.

Recovery Functions

FunctionReturnsDescription
wldelay_delete_lockout_for_ip( $ip )intRemove all lockout and failure transients for a specific IP. Returns the count of removed entries. Cleans up both ip and ip_username strategy keys.
wldelay_flush_lockout_transients()intClear all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Returns total entries removed.
wldelay_register_transient_key( $key )voidAdd a transient key to the registry (wldelay_transient_registry option). Called automatically when failure/lockout transients are created.
wldelay_unregister_transient_key( $key )voidRemove a transient key from the registry. Called automatically during cleanup.

Botnet Detection Functions

Distributed-attack detection (wldelay-botnet.php) watches distinct source IPs per targeted username inside a sliding window and alerts — it never blocks. The COUNT query is deferred to the shutdown task queue off the request hot path. Since v2.5.0.

FunctionReturnsDescription
wldelay_botnet_is_enabled()boolWhether distributed-attack detection is on (default on).
wldelay_botnet_get_ip_threshold()intDistinct-IP threshold that triggers an alert. Clamped to [2, 100]; default 5.
wldelay_botnet_get_window_seconds()intDetection window in seconds. Option clamped to [5, 60] minutes; default 900.
wldelay_botnet_get_recent_detections()arrayRolling list of recent detections (newest first, up to 20, 24h TTL) that feeds the dashboard banner.

On detection the plugin writes a synchronous botnet_detected audit-log row, pushes an entry onto the dashboard feed, and — when email notifications are enabled — sends an alert email naming the targeted username and up to five sample IPs.

fail2ban Config & Coherence

FunctionReturnsDescription
wldelay_fail2ban_generate_filter_config()stringBuilds the fail2ban filter.d config (with the current failregex) for download. Since v2.5.0.
wldelay_fail2ban_generate_jail_config()stringBuilds the matching jail.local stanza for the active log path. Since v2.5.0.
wldelay_handle_fail2ban_config_download()voidadmin-post handler that streams the generated filter/jail config as a download. Nonce-protected, requires manage_options. Since v2.5.0.
wldelay_buffer_fail2ban_line( $event, $ip, $username, $source )voidBuffers a fail2ban line for the request instead of writing immediately. Since v2.5.0.
wldelay_flush_fail2ban_buffer()voidWrites the buffered fail2ban lines once per request (one disk write instead of one per attempt). Since v2.5.0.
wldelay_settings_coherence_warnings( $options )arrayReturns non-blocking warnings for contradictory settings (empty whitelist, unwritable fail2ban path, alert thresholds that can never fire), shown on the settings page. Since v2.5.0.

Emergency Recovery URL

An opt-in, time-boxed secret URL (/?wldelay_recovery=<token>) that lets a locked-out admin clear their own IP lockout with no admin or server access. Only the token’s sha256 hash is stored; the raw token is shown once. A GET renders a confirm page and the unlock fires from a nonce-protected POST, so email and antivirus link-prefetchers cannot trigger it. It never logs the user in and never disables the shield. Since v2.6.0.

FunctionReturnsDescription
wldelay_recovery_is_enabled()boolWhether the Emergency Recovery URL feature is switched on (default off).
wldelay_recovery_generate_token()stringMint a 256-bit token, store only its sha256 hash plus the generation timestamp, and return the raw token (the only time it exists in plaintext).
wldelay_recovery_hash( $token )stringsha256 hex of a raw token.
wldelay_recovery_token_matches( $token )boolConstant-time (hash_equals) comparison of a candidate token against the stored hash.
wldelay_recovery_build_url( $token )stringBuild the full recovery URL for a raw token.
wldelay_recovery_generated_age_days()int|nullAge of the current token in whole days, or null when never generated.
wldelay_recovery_needs_rotation()boolWhether the token is older than WLDELAY_RECOVERY_NAG_DAYS (90) and should be rotated.
wldelay_recovery_rate_limit_hit( $ip )boolRecord a failed attempt for an IP and report whether it is now over the limit (WLDELAY_RECOVERY_RL_MAX per WLDELAY_RECOVERY_RL_WINDOW). Only failed attempts count.
wldelay_recovery_send_email( $url )voidEmail the recovery URL to the site admin address.

The feature audits every event through the audit log: recovery_url_generated, recovery_used, recovery_failed, and recovery_rate_limited. Handlers run on the admin_post_* hooks listed under Action Hooks.

Database Functions

FunctionReturnsDescription
wldelay_get_log_table_name()stringReturns the full table name: {$wpdb->prefix}wldelay_login_log.
wldelay_create_log_table()voidCreates or upgrades the log table using dbDelta(). Called on activation and version upgrades.
wldelay_log_failed_attempt( $ip, $username, $source )voidInserts a row into the log table and invalidates the dashboard cache.
wldelay_get_recent_failed_attempts( $limit )arrayReturns recent failed attempts ordered by time (used by the dashboard widget).
wldelay_cleanup_old_logs()voidDeletes entries older than the retention period in batches of 1,000.

Export & Log Query Functions

Added in v2.1.2–2.2.3 for CSV export, filtered log queries, and trend analytics.

FunctionReturnsDescription
wldelay_get_login_log_attempts( $limit, $offset, $filters )arrayQuery log entries with optional filters (source, IP, username, date range) and pagination. All filters are applied at the SQL level.
wldelay_get_export_login_log_url( $filters )stringBuild a nonce-protected URL for the CSV export endpoint. Accepts optional filter array.
wldelay_handle_export_login_log()voidMain CSV export handler. Streams results in 1,000-row batches with flush() after each batch. Sets Content-Type: text/csv and Content-Disposition: attachment headers.
wldelay_sanitize_login_log_filters( $filters )arrayValidate and sanitize filter input. Checks IP format, date format (Y-m-d), and auto-swaps reversed date ranges.
wldelay_get_login_log_filters_from_request()arrayExtract filters from $_GET using expected keys only (wldelay_log_source, wldelay_log_ip, wldelay_log_username, wldelay_log_from, wldelay_log_to).
wldelay_csv_sanitize_cell( $value )stringMitigate CSV formula injection by prepending a single quote to values starting with =, +, -, @, or whitespace.
wldelay_get_top_ips( $days, $limit )arrayReturns the top attacking IP addresses over the last $days days (default 7), limited to $limit results (default 10). Each row has ip_address and attempt_count. Since v2.2.3.
wldelay_get_top_usernames( $days, $limit )arrayReturns the most targeted usernames over the last $days days (default 7), limited to $limit results (default 10). Each row has username and attempt_count. Since v2.2.3.
wldelay_get_daily_attempts( $days )arrayReturns daily failed attempt totals for the last $days days (default 7). Each row has day (Y-m-d) and attempt_count. Since v2.2.3.

Database Schema

{$wpdb->prefix}wldelay_login_log

ColumnTypeKeyDescription
idbigint(20) unsignedPRIMARYAuto-increment ID
ip_addressvarchar(45)INDEXIP address (supports IPv4 and IPv6)
usernamevarchar(60)INDEXUsername attempted
attempted_atdatetimeINDEXTimestamp of the attempt
sourcevarchar(20)INDEXwp-login, xmlrpc, rest, application-password, or password-reset
username_attemptedCOMPOSITE INDEXComposite (username, attempted_at) index — added in schema gen 7 to count distinct IPs per username within a time window for distributed-attack detection

Created on plugin activation via dbDelta(). Schema version is tracked in the wldelay_db_version option.

{$wpdb->prefix}wldelay_lockouts

Durable lockout store behind WLDelay_DB_Persistence. Lockouts survive object-cache eviction and transient expiry; the transient_key column links a row to its companion transient.

ColumnTypeKeyDescription
idbigint(20) unsignedPRIMARYAuto-increment ID
lockout_keyvarchar(64)UNIQUEHashed attempt identifier
ip_addressvarchar(45)INDEXIP address (IPv4 or IPv6)
usernamevarchar(255)Username, for the ip_username strategy
lockout_typevarchar(20)login or password-reset
sourcevarchar(20)Originating auth surface
transient_keyvarchar(191)Companion transient name
generationvarchar(32)Generation token for bulk-clear matching
created_atdatetimeWhen the lockout was set
expires_atdatetimeINDEXWhen the lockout expires

{$wpdb->prefix}wldelay_audit_log

Admin/security audit trail. Rows record settings changes, lockout clears, and whitelist edits. Writes are deferred to shutdown through the task queue so they stay off the request hot path.

ColumnTypeKeyDescription
idbigint(20) unsignedPRIMARYAuto-increment ID
actor_idbigint(20) unsignedINDEXWP user ID, or 0 for system
actor_loginvarchar(60)Actor login name
actionvarchar(50)INDEXAction key, e.g. settings_changed
objectvarchar(191)The thing acted on
old_valuetextPrevious value (JSON for arrays)
new_valuetextNew value (JSON for arrays)
ip_addressvarchar(45)Actor IP address
created_atdatetimeINDEXWhen the action occurred

Transient Keys

All transient keys use MD5 hashing of the attempt identifier for security (prevents enumeration).

Key PatternTTLValueDescription
wldelay_fails_{md5(id)}1 hourint (count)Failed attempt counter per IP or IP+username
wldelay_lockout_{md5(id)}Lockout durationint (timestamp)Lockout start time per IP or IP+username
wldelay_email_cooldownCooldown minutesint (timestamp)Site-wide email rate limiter
wldelay_dash_recent1 minutearrayCached recent failed attempts for the dashboard widget
wldelay_dash_trends5 minutesarrayCached 7-day trend snapshot for the dashboard widget
wldelay_botnet_detections24 hoursarrayRolling feed of recent distributed-attack detections (newest first, max 20) for the dashboard banner (v2.5.0+)
wldelay_botnet_cd_{md5(username)}1 hourint (timestamp)Per-username alert cooldown that suppresses repeat distributed-attack alerts (v2.5.0+)
wldelay_recovery_rl_{md5(ip)}15 minutesint (count)Failed recovery-token attempts per IP, for the rate limiter (v2.6.0+)
wldelay_recovery_reveal_{user_id}5 minutesstring (URL)One-time reveal of a freshly generated recovery URL for the settings page / .txt download (v2.6.0+)

The id in failure and lockout keys is the attempt identifier: either the IP address (ip strategy) or IP|username (ip_username strategy).

Options Reference

All settings are stored in a single serialized array under the wldelay_options WordPress option.

KeyTypeDefaultDescription
wldelay_delayint1Fixed delay in seconds (0–10)
wldelay_delay_randomboolfalseUse random delay
wldelay_delay_random_minint1Min random delay (1–10)
wldelay_delay_random_maxint5Max random delay (1–10)
wldelay_progressive_enabledboolfalseEnable progressive delay
wldelay_progressive_incrementint1Increment per attempt (1–10)
wldelay_progressive_maxint30Max progressive delay (5–60)
wldelay_email_enabledboolfalseEnable email notifications
wldelay_email_thresholdint5Failures before email (1–100)
wldelay_email_addressstring''Notification email (fallback: admin_email)
wldelay_email_cooldownint5Minutes between emails (0–60)
wldelay_lockout_enabledboolfalseEnable IP lockout
wldelay_lockout_thresholdint10Failures before lockout (1–100)
wldelay_lockout_durationint60Lockout duration in minutes (1–1440)
wldelay_lockout_attempt_strategystring'ip''ip' or 'ip_username'
wldelay_trust_proxy_headersboolfalseTrust X-Forwarded-For headers
wldelay_whitelist_enabledboolfalseEnable IP whitelist
wldelay_whitelist_ipsstring''Newline-separated IPs/CIDR ranges
wldelay_log_retention_daysint30Log retention in days (0–365, 0 = forever)
wldelay_xmlrpc_enabledboolfalseEnable XML-RPC protection
wldelay_xmlrpc_blockboolfalseBlock all XML-RPC auth
wldelay_rest_enabledboolfalseEnable REST API protection (opt-in, injected default) (v2.1.2+)
wldelay_application_password_enabledboolfalseEnable application-password protection (opt-in, injected default) (v2.1.2+)
wldelay_password_reset_enabledboolfalseEnable password-reset protection (opt-in) (v2.3.2+)
wldelay_enumeration_hardening_enabledboolfalseEnable username/author enumeration hardening (opt-in)
wldelay_fail2ban_enabledboolfalseEnable fail2ban log writing (opt-in) (v2.3.4+)
wldelay_fail2ban_log_pathstring''Custom fail2ban log path; empty uses the plugin-owned protected directory (v2.3.4+)
wldelay_fail2ban_include_lockoutsbooltrueAlso write lockout lines, not just failed-login lines (v2.3.4+)
wldelay_botnet_enabledbooltrueDistributed-attack detection — alert-only, never blocks (injected default) (v2.5.0+)
wldelay_botnet_ip_thresholdint5Distinct IPs per username before an alert (clamped 2–100) (v2.5.0+)
wldelay_botnet_window_minutesint15Detection window in minutes (clamped 5–60) (v2.5.0+)
wldelay_protection_profilestring''Active Security Setup Wizard profile (conservative, balanced, aggressive, or empty/custom) (v2.3.3+)
wldelay_custom_login_enabledboolfalseEnable custom login URL (v2.2.3+)
wldelay_custom_login_slugstring'my-login'Custom login URL slug (lowercase, numbers, hyphens only) (v2.2.3+)
wldelay_recovery_enabledboolfalseEnable the Emergency Recovery URL (opt-in) (v2.6.0+)
wldelay_recovery_token_hashstring''sha256 hash of the current recovery token (never the raw token) (v2.6.0+)
wldelay_recovery_generated_atstring''UTC timestamp the token was generated (drives the rotation nag) (v2.6.0+)
wldelay_recovery_last_used_atstring''UTC timestamp the recovery URL was last used (v2.6.0+)

Other WordPress Options

Option NameDescription
wldelay_db_versionDatabase schema version (tracks WLDELAY_VERSION)
wldelay_plugin_versionCurrent plugin version
wldelay_previous_versionPrevious version (for upgrade detection)
wldelay_name_change_notice_dismissedWhether the rename notice was dismissed
wldelay_whats_new_dismissedVersion whose “What’s New” notice was dismissed
wldelay_settings_versionSettings-schema version, drives the migration runner
wldelay_fail2ban_default_tokenRandom token for the plugin-owned fail2ban log directory name
wldelay_transient_registryArray of active transient keys (used by flush-lockouts for cleanup)

Cron Jobs

HookScheduleFunctionDescription
wldelay_cleanup_logsDailywldelay_cleanup_old_logs()Deletes log entries older than the retention period. Processes in batches of 1,000 to avoid table locks. Scheduled on wp hook, unscheduled on deactivation.
wldelay_async_cronDailywldelay_run_async_cron()Emits the cron_tick event and flushes the deferred-task queue, giving expired-lockout purges and other queued work a backstop on low-traffic sites. Scheduled on wp hook, cleared on deactivation.

WP-CLI

Login Delay Shield registers commands under the login-delay-shield namespace. Available when WP-CLI is installed.

CommandArgumentsDescription
wp login-delay-shield unlock-ip <ip><ip> — IPv4 or IPv6 addressRemoves lockout and failure transients for the specified IP. Validates IP format. Returns count of removed entries.
wp login-delay-shield flush-lockoutsClears all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Also clears the registry itself.