Login Security changelog
Every version, newest first, with the date it was released. Updates install from your Plugins screen.
1.6.0
- New Skip the code on a browser you choose. Off until you switch it on in Settings → A second step. The code screen, on wp-login.php and at My Account, then offers "Do not ask on this browser for 30 days". You choose the days (1 to 90) and the roles it is never offered to; administrators start left out, and it needs HTTPS. People see and forget their remembered browsers on their profile or the My Account tab, an administrator can forget anybody's, Settings can forget everybody's, and a new password ends them. WP-CLI:
wp login-security two-factor forget <user>. - Fixed Recovery codes owed after bringing a second step over can no longer be used up on a page that hides notices. They were made, and counted as shown, on the first admin page opened, even the block editor or a WooCommerce page. Now every admin page says they are owed and links to the profile, where Make new recovery codes asks for the password and shows them. My Account does the same with its tab.
- Fixed Someone with two Wordfence set-ups brings over the one Wordfence uses. The import took the newest row; Wordfence signs people in with the first.
- Fixed Boxes on the Two-step sign-in tab. On themes such as Twenty Twenty-Five, the Current password and six-digit boxes on My Account were a sliver 150 pixels wide. They are now full width, drawn like the boxes on Account details.
1.5.0
- New Bring second steps over from Wordfence or Two Factor. Settings lists the people who set up an authenticator app with Wordfence, Wordfence Login Security or Two Factor, and Bring them over copies each one across, so their app keeps working and nobody scans a new QR code. Nothing of the other plugin's is changed, anybody with a second step here keeps theirs, and each person brought over is shown ten new recovery codes once (until 1.6.0 on the first admin page opened, which could hide them; from 1.6.0 they make them on their profile). WP-CLI:
wp login-security two-factor import --from=wordfence --dry-run. - Improved One query fewer on every admin page and every sign-in attempt. Whether the log's table is there is now known from a stored setting. If the table goes missing, for example after a restore without it, the plugin notices on its first write, still counts wrong passwords towards a lockout, and makes the table again on the next admin page.
- Improved The plugin's screens load less. They no longer load the media library and the colour picker, which none of them uses.
- Improved Faster pages on sites in Spanish or French. The translations now also ship as PHP files, which WordPress 6.5 and later reads from PHP's code cache instead of reading the translation file again on every page. Measured on a French site with all fifteen of our plugins: 6.3 ms and 0.8 MB of memory less on every request. Older WordPress reads the translations as before.
- Improved Opening the plugin's screens asks less of your server. To make switching tabs instant, the plugin loads its other screens in the background while you read the first one. It now loads at most four, the plugin's own tabs first, and none while the browser tab is in the background; a tab past the fourth loads as your pointer reaches it.
- Fixed Admin pages no longer wait on the lockout count. The lockout notice counted the whole day's attempts on every admin page to show one number, about a quarter of a second a page on a site with 300,000 attempts. It now counts only lockouts, at most every five minutes and right away after a new one. Its own setting is created when the plugin is activated or updated, so it is no longer looked up on every page.
- Fixed Removing the plugin from a network clears everybody's two-step data when the sites that used it asked for that. A site on the network that had never run the plugin counted as "keep", so the two-step secrets and recovery codes stayed in the user records, and a later reinstall asked those people for a code again.
- Fixed Notes that should be hidden stay hidden. A hint or note a screen had hidden could still show, because the style that lays hints out outranked the browser's own rule for hidden things. Hidden things on the plugin's screens now always stay hidden.
1.4.1
- Fixed Shops that leave WooCommerce out of some pages no longer have their permalinks rebuilt again and again. With the My Account Two-step sign-in tab on, a plugin such as Perfmatters that unloads WooCommerce on blog pages made Login Security rebuild the permalinks repeatedly. The tab's address is now learnt on a wp-admin page, never a visitor's.
- Improved One database query fewer on every page of a site that does not use the My Account tab.
- Fixed An old link to a button now asks again, however old it is, instead of showing WordPress's "The link you followed has expired.", and still changes nothing. The lockout notice's old Not now link goes back to the notice.
Earlier versions
September 2026 37 versions
1.4.0
- New Customers can set up a second step in WooCommerce My Account, on a Two-step sign-in tab with the QR code, recovery codes, new codes and switching it off. With Required of set to Everybody, a customer who has not set one up is kept on that tab while in My Account; the cart and checkout are never held up. On for new sites; existing shops that require a second step are offered it on the Dashboard.
- Fixed Signing in on My Account with a second step now asks for the code and then goes where WooCommerce would have sent the customer, instead of showing WordPress's own sign-in form. The sign-in then finishes like any other, so WooCommerce brings back the saved cart.
- Fixed After the code on the sign-in page, people land where WordPress would have sent them, a shop's customer on My Account.
- Changed Making new recovery codes and turning your own second step off now ask for your current password. On your profile, the code that turns the second step on has its own button.
- Changed Let in, Let everybody back in, Send a password reset and the Dashboard's Switch it on and Not now are now buttons. Opening one in a new tab does nothing, and the screen asks again. (Until 1.4.1, a link more than half a day old could show WordPress's "The link you followed has expired." instead of the question.)
1.3.3
- Fixed Make a new one and Empty the log always ask first. Opened in a new tab, with the middle mouse button or from a saved link, Make a new one used to replace the way back in you had written down, and Empty the log deleted the record of who has tried, with no question. Neither does anything that way now, and the screen asks again.
1.3.2
- Fixed A made-up name now gets exactly the answer a real account gets. With "Stop somebody after too many wrong passwords" on, a wrong password, an unknown username or email address, and a name turned away by "Refuse usernames nobody here has" all get the same sentence, keep the name in the box and shake the form, on wp-login.php, on a moved sign-in page and in the sign-in box wp-admin shows when a session runs out. A password reset for a name that is not an account goes on to "Check your email", and nothing is sent. A CAPTCHA plugin's own message is now shown beside the sentence instead of being replaced. How long the answer takes can still differ, and the documentation lists what else does.
- Fixed WooCommerce's sign-in and lost-password forms give nothing away either. On My Account and at checkout, "The username X is not registered" and "The password you entered for the username X is incorrect" now both read "That username and password do not match an account on this site." Lost password shows WooCommerce's own "Password reset email has been sent" page for a name that is not an account, and sends nothing.
- Fixed Apps signing in with an application password no longer learn which usernames exist. The REST API now gives an unknown username or email address and a wrong application password the same error and the same sentence.
- Changed A mistyped name on a password reset also gets "Check your email". A customer who mistypes their address sees the same page as everyone else, so if a reset email never came, check what they typed. On WooCommerce a name that is not an account now counts towards the password reset limit, as it already did on wp-login.php.
1.3.1
- Fixed Lowering the shortest password stops the old requests. People whose password was found too short for the old length are no longer asked to change it or kept on their profile; their next sign-in measures it against the new length.
- Fixed The registration form says what to fix again, and a moved sign-in page gives nothing away. With "Stop somebody after too many wrong passwords" on, WordPress's own registration errors, such as "The email address is not correct", read as WordPress wrote them again. At a page moved with "Move the sign-in page", a wrong password, an unknown username and a reset for no account now get the same one sentence. Until 1.3.2 the page could still tell them apart in other ways, such as the name left in the box.
- Fixed The recovery link works with a moved sign-in page. It opens again instead of an empty page, and the small sign-in box wp-admin shows when a session runs out closes itself once you have signed in there.
1.3.0
- New Fake sign-ups turned away. The sign-up forms on wp-login.php and WooCommerce My Account now turn away bots that make fake accounts, with no CAPTCHA and nothing sent to another service. At checkout only the limit on new accounts from one connection applies, and an order that goes over it is refused, so a shop whose customers share one connection can raise the number in Settings. It is on for new sites, and existing sites are offered it on the Dashboard.
- Changed Weak passwords are asked about at sign-in. With "Refuse passwords a machine would find" on, the password of each person the rules apply to (by default the people who can change the site, not your customers) is now checked the next time they sign in, and anyone whose password the rules refuse is asked to change it. Sites that already have the rule on will see these notices after updating. The Users screen, Settings and
wp login-security weaklist the people being asked. The password itself is never stored.
1.2.2
- Fixed French counts zero in the singular. French writes "0 message", not "0 messages", and the plugin's French now counts the way WordPress's own French does.
1.2.1
- Changed Lockouts now reach visitors on servers that write IPv4 addresses the IPv6 way. If you switched the plugin on through such a server behind Cloudflare or a proxy of your own, the address it trusted was the proxy's, such as 127.0.0.1 or a Cloudflare address, so nobody who came through that proxy was ever locked out. Each visitor now has their own address, so lockouts reach them, and you too if your own address is not on the Never locked out list: the Dashboard shows whether the address you are on is trusted, with a button to add it, and the old proxy line can come off that list on the Settings screen.
- Fixed Each visitor gets their own count on servers that write IPv4 addresses the IPv6 way. Some servers write a visitor's IPv4 address as ::ffff:203.0.113.7, and behind Cloudflare or a proxy of your own every visitor on such a server was taken for the proxy, so they all shared one count of wrong passwords and one person guessing could lock everybody out. The address is now read as 203.0.113.7 however it is written, so each visitor has their own count for lockouts, password reset emails and the hour of trust after the recovery link. On such a server, anyone locked out when you update can try again straight away, once, and anyone who used the recovery link in the hour before needs it again; Let in on a log row from before the update, and
wp login-security unlockwith the address written either way, also lift a lockout made after it.
1.2.0
- New Password reset emails are limited. Each account now gets at most three reset emails an hour and each address can ask five times an hour, on wp-login.php and WooCommerce's My Account page alike, so a machine working through a list of usernames can no longer fill your customers' inboxes or use up your site's mail allowance. An address locked out for guessing passwords gets no reset email and is told how long to wait, the refusal reads the same whether or not the name typed is an account, and an email your site could not send does not count. Requests are listed on a new Password resets tab in Who has tried, and the Dashboard shows how many were refused in any week that had some.
- New See who has a second step. Users > All Users has a new Second step column showing who has set one up and how many recovery codes they have left, and a "No second step" link that lists the people who can change the site and have not, shown to administrators whenever there are any, whether or not you require one. Settings > A second step gives the same count, and
wp login-security two-factor listprints the list. On the Dashboard, a real account being guessed at that has no second step gets "Send a password reset" and "Open profile" buttons, for anybody allowed to edit that account. - Changed The password reset limit is on for every site from this version. After the update, a fourth reset email for one account within the hour, or a sixth request from one address such as a busy office, is refused with "Try again later", and an address that is locked out is told to wait for its lockout to end. Trusted addresses and signed-in administrators are never limited, nor is a shop manager sending a reset to a customer from Users, and "Let in" on the request's row in Who has tried lets that person ask again straight away. You can switch the limit off or change the two numbers in Settings > Guessing at passwords.
- Fixed Usernames being tried recognises an account tried by its email address. WordPress lets people sign in with their email address, and a machine guessing at a real account that way was marked "no such account" on the Dashboard.
- Fixed "Let in" on one row says what it did. It used to say everybody who was locked out could try again, when only that address had been let in.
- Fixed The buttons' column on Who has tried has a name for screen readers. Its heading was empty, so a screen reader announced a column with no purpose.
1.1.20
- Fixed Signing in no longer shortens the next lockout for the address it came from. Before, anybody with an account of their own on your site, such as a registered customer, could sign in to it between runs of guesses at other accounts and keep that address's lockouts at the shortest length instead of growing longer each time. A correct password now clears the count of wrong passwords against the username that signed in, and leaves the address's count and lockouts as they were.
- Fixed Making a new way back in stops trusting everybody who used the old one, straight away. Before, an address that had already opened the old link, for instance after it leaked, stayed exempt from lockouts for up to an hour after you pressed Make a new one.
- Fixed A lockout no longer keeps the visitor's IP address outside the log. The last lockout's address used to be kept in a setting that the retention period and Empty the log never reached, even with "Record every attempt" switched off. With recording off, a lockout now keeps no visitor's address on the site, though the lockout email still names it, and the daily tidy-up removes the one an older version kept.
- Fixed "Let everybody back in" works on hosts that keep lockouts in an object cache. On a site whose host uses Redis or Memcached this way, it and
wp login-security unlockwith no address lifted no lockouts at all, while the screen said everybody could try again and the command said 0 lockouts lifted. They now lift every lockout there, and elsewhere the number the command prints no longer includes lockouts that had already run out. - Fixed Letting somebody back in gives them their full number of tries. Before, the wrong passwords that caused the lockout still counted after Let in, "Let everybody back in" or
wp login-security unlock, so one more locked the person straight back out. - Fixed The lockout email no longer reports 0 refused attempts when nothing is being recorded. With "Record every attempt" switched off, it now says attempts are not being recorded, so it cannot say how many were refused that day.
- Fixed
wp login-security unlock <address>checks the address. It refuses anything that is not an IP address, and says when the address was not locked out instead of saying it can try again. An IPv4 address typed there also lets in the ::ffff: form of it that a dual-stack server records. - Fixed
wp login-security logdoes what its options ask.--outcomerefuses a word it does not know instead of listing every attempt, and--limitprints exactly that many rows. - Fixed Licensing and updates run on the newest licence code among our plugins. With several of our plugins on one site, they all now use the newest licence code any of them carries, whatever order WordPress loads them in. The exception is while the first of ours that WordPress loads (usually the alphabetically first) is on a release from before this one: its older code serves them all until that plugin is updated. An older plugin of ours can also no longer stop this plugin's admin screens loading.
- Improved The documentation says the limit on one username tried from many addresses works with "Record every attempt" switched off. It used to say that turning the log off turned that limit off too.
1.1.19
- Fixed Turning off "Record every attempt" no longer switches the lockouts off with it. A machine working through a password list is stopped as before, and nothing about an attempt is written down: only a count, under a scrambled key that clears itself at the end of the counting window. That count starts at the first failure rather than sliding along with each one, so a run of guesses that straddles the end of a window can reach up to twice your allowance before the lockout.
- Fixed The plugin now notices when the database refuses to make its table. It used to mark the job done whatever happened, so on such a site nothing was ever recorded, counted or tidied away again while the settings screen showed the protection switched on. It tries again later now, and switching the plugin off and on again still repairs it there and then.
- Fixed An address on the never-locked-out list works however it was typed. An IPv6 address written in capitals, or written out in full, is now read as the same machine as the short form, so a line you add to keep yourself out of a lockout does that. On servers that report addresses in the IPv4-inside-IPv6 form nothing on the list matched at all, and those work now too.
- Fixed The address shown to you is written the way the list stores it. On the first screen and beside the "Add the address I am on now" button, the address you are on could be printed in a different form from the one the never-locked-out list holds, so the two read as two different machines.
- Fixed Trusting the same range twice no longer puts it on the list twice. A range that was already on the never-locked-out list was added again as a second copy of the same line.
- Fixed A line on the list that is not really an address is dropped the same way on every kind of server. Whether something like 010.0.0.1 was accepted or refused used to depend on the kind of server your shop runs on.
- Fixed The first screen no longer reports a quiet week it cannot see. With "Record every attempt" switched off, or with the plugin's table missing, the week's four numbers were all zero and read as a week in which nothing had happened. The screen now says which of the two it is, shows no numbers, and tells you that somebody guessing is still being stopped.
- Fixed
wp login-security statussays when nothing is being recorded. It carries a "Recording attempts" row now, and shows a dash for the week's counts instead of reporting zero refusals and zero lockouts. - Fixed The "somebody was locked out" email is written in your site's language. It is composed on the request that caused the lockout, so a cookie or a wp_lang on the sign-in page could have your one alert of the day arrive in the language of whoever was locked out.
- Improved Filtering the attempts list goes straight to the filtered page. Pressing Filter used to send a wasted request to the server first and only then load the page you had asked for.
- New A "Check again" button on the licence screen. Renew your licence, or have it put right, and one press picks it up instead of waiting hours for the next automatic check. If the check cannot be made, the screen says why and changes nothing.
- Fixed An activation that fails because our licence server could not be reached no longer wipes your key. The key you had entered, and the list of sites using it, were thrown away on any failed attempt, leaving you to go and find the key again. They are kept now until the server actually passes judgement on the key.
- Fixed A key our licence server does not recognise can be typed over. The key box stays editable and the button still says Activate, instead of the screen offering nothing but "Deactivate this site" just after a notice told you to enter the key again.
- Fixed Check again, Deactivate this site and Free work with the key box empty. None of the three reads what is in the box, but all three used to answer "Enter your licence key first." and make no request.
1.1.18
- Fixed The moved sign-in page could be walked past by typing one extra slash. A request for //wp-login.php, or for an address with /./ or /../ in the middle of it, was not recognised as the old sign-in page, so the web server served it as usual and the page you had moved was still reachable where everybody looks for it. Addresses are now read the way the web server reads them before the old page is refused.
- Fixed Wrong codes at the second step were only counted against the address they came from. Somebody who already had the password could go on guessing the six digits for as long as each guess arrived from a different address. "Also stop one username tried from many addresses" now stands behind the code as well as the password, a half-finished sign-in ends after five wrong codes and says so on the sign-in page instead of showing a blank form, and getting the code right lifts the lock again.
- Fixed A site installed in a folder could serve its home page as the sign-in page. A request for the site's own root was read as though the folder name had been typed as an address, so a site whose folder and moved sign-in address are spelt alike showed the wrong page. The old wp-login.php address was still served, too, when the folder was typed with different capitals. The folder is now matched as a folder, whatever the capitals.
- Fixed The uninstall switch left every person's second step behind. Deleting the plugin with it ticked kept each account's sealed authenticator secret and its ten recovery codes in the database for good, and reinstalling months later asked exactly those people for a code again while the settings screen showed the second step switched off. They now go with the settings and the log, and on a network only when every site has ticked the box. The switch says so as well: it now reads "Delete the settings, the log and everyone's second step when the plugin is deleted", because that part cannot be undone and everybody has to set a second step up again. Every lockout is still lifted whether it is ticked or not.
- Fixed Deleting the plugin clears away tables left behind by deleted sites. On a network, a site deleted while this plugin was switched on for that site alone left its log of sign-in attempts behind in the database, with nothing left to say the table had ever been ours. Deleting the plugin now finds such leftovers and removes them.
- Fixed "Check again" on the Updates screen looks for a new version straight away. Just after the site had had trouble reaching us, pressing it did nothing for up to a quarter of an hour, for this plugin and for the rest of ours.
1.1.17
- Fixed Saving the Settings screen keeps your moved sign-in page. It used to move the sign-in page back to wp-login.php and erase its secret address.
- Fixed Hide usernames also blocks the REST API user list when the address is typed with capital letters, such as /wp/v2/Users.
- Fixed New sites on a network start with their own settings. They used to start with a copy of the main site's, including its hidden sign-in address.
- Fixed On a network, a site that has not moved its own sign-in page keeps its wp-login.php link when another site on the network has moved its page.
- Fixed Activating from WP-CLI or a site-management tool no longer stops with a fatal error.
- Fixed Deactivating the plugin across a network stops its daily clean-up on every site, not just the main one.
1.1.16
- Improved Fewer database queries on every admin page. Each of our plugins checks its licence on every admin screen, to know whether to show the licence notice, and read its saved licence and its "Remind me later" flag with a query each. With the whole range installed that was thirty separate lookups on every admin page, most of them for rows that do not exist yet. They are now read together in one query (on WordPress 6.4 or later). Measured on a test site with all fifteen plugins: the WordPress Dashboard went from 41 queries of ours to 12. A small value this plugin reads on every admin screen is also now loaded with the rest of the settings, rather than on its own, once Not now has been pressed; from 1.5.0, from the start.
1.1.15
- Improved A download is made once, not twice. Pressing Export settings built the file twice: the admin's no-reload navigation fetched it first, found a file rather than a screen it could show, and then asked for it again the ordinary way. On a long list that was twice the wait, and twice the work for the server. Each download is now a single request. Measured in a browser: two requests per click before, one after.
1.1.14
- New Download the sign-in attempts as a spreadsheet. A button on the Who has tried screen saves what you are looking at, every page of it: the tab you are on and anything you searched for, with when, the address, the username tried, what happened and why. It is written a thousand rows at a time, so a site the machines have found, with hundreds of thousands of attempts, downloads without running out of memory. A username typed to look like a spreadsheet formula is written as plain text, so opening the file never runs it. Tried in a browser: the refused tab downloaded only the refused attempt, and a username of @SUM(1) arrived as text.
1.1.13
- New Usernames nobody can look up. A username is half of a sign-in, and a stock WordPress site hands it to anybody who asks. Stop usernames being looked up (Settings, on by default) closes the four ways machines ask:
/?author=1now answers not found instead of redirecting to/author/<username>/; the REST API's user list is refused to anybody who cannot write posts (editors keep it for the block editor, and a signed-in app can still ask who it is); embeds no longer carry the author's name and page; and the author sitemap is not offered. Author pages your theme links to, and bylines, stay. Because WordPress sets a new account's public name to its username, the Settings screen also lists anybody who can publish whose public name is still their username, with a link to change it. Checked from outside on a test site: with it off,/?author=1redirected to/author/admin/and the user list and embeds namedadmin; with it on, all three stopped, and/author/admin/still loaded.
1.1.12
- Fixed Switches that start on could not be switched off. Under Settings, "Stop somebody after too many wrong passwords", "Also stop one username tried from many addresses", "Refuse usernames nobody here has", "Email me when somebody is locked out", "Record every attempt" and "Record successful sign-ins too" came back on after every save. A browser sends nothing for a switch that is off, and the plugin read that as "use the default", which for these is on. And once the sign-in page had been moved, unticking "Move the sign-in page" on its own screen left it moved. Each now saves the way you left it. Tried in a browser: switched off, saved, reloaded, and it stayed off.
1.1.11
- Fixed Importing a settings file put the sign-in page back at wp-login.php. The address of the hidden sign-in page is left out of exported files, and importing a file put the blank it left over the site's own address, even when the file came from the same site. The sign-in page quietly went back to where every bot looks for it. Importing now keeps the address the site has. Checked by exporting and importing on a test site: before this, the address was gone; now it is kept.
1.1.10
- Fixed Import settings stopped with an error instead of importing. Choosing a settings file on the Settings screen and pressing Import ended on WordPress's critical error page, because the button never handed the file over. It now imports the file and says so: "Settings imported.", or which part of the file could not be used and why. Found by a static check of every plugin's code, and tried end to end on a test site: export a file, import it back, read the message.
1.1.9
- Improved One update check for all of our plugins on a site, instead of one each. Every plugin of ours used to ask our server about updates separately — fifteen requests in a row on a site with the whole range, twice a day and every time you press "Check again". The first plugin asked now asks for all of them at once, and the rest read the answer. Measured on a site with all fifteen: fifteen requests became one, and the whole check took a third of a second. If our server refuses or cannot be reached, every plugin waits the same fifteen minutes before trying again, rather than each trying in turn.
1.1.8
- Fixed A wp-admin that would not load when our licence server could not be reached. Each of our plugins checked for its own updates, and if the server was unreachable each one waited out its own fifteen-second timeout — so a site with several of them could spend minutes on a single admin page, twice a day, for something entirely outside its control. The first one to find the server unreachable now says so for the whole site, and the others stand down until it is worth trying again. Measured on a site with fifteen installed: fifteen waits became one.
1.1.7
- Fixed "Delete everything on removal" was leaving three rows behind. The removal list had drifted from the names the licence part of the plugin actually uses — it named two rows nothing has ever written, and looked for a third in the wrong place. Tested by putting all four rows in place and removing the plugin: nothing is left now.
1.1.6
- Fixed Emptying the log, and making a new recovery address, keep asking on sites with a strict security policy. Both questions were written into the page in a way that some sites block for safety, which quietly removed them — so one click emptied the record of who had been trying, or stopped the address you had written down from working.
1.1.5
- Fixed "deleted automatically after 1 days" in the privacy notice now reads "after 1 day" when you keep the log for a single day. In Spanish and French as well, where the sentence had been using the plural wording for both.
1.1.4
- Fixed "Move the sign-in page" now actually moves it. Since the very first release the setting saved, the new address was shown on screen, emailed, and printed by the command line — and the web server knew nothing about any of it. The new address returned "not found" and
/wp-login.phpwent on answering exactly as before. Everything that reported on the feature was reading the setting; the only thing that would have noticed had to ask the web server. If you switched this on and it seemed to do nothing, this is why, and it is fixed.
- Improved The moved address no longer minds a capital letter. Typing it with the wrong case used to give a "not found" page. The address was never a secret — the point is to stop the machines knocking, not to hide — so there was nothing to gain by turning away the owner who typed their own door slightly wrong.
1.1.3
- Fixed XML-RPC let the password through without the second step. A second step is worth nothing if the account password still gets in somewhere else, and
xmlrpc.phptook it: one request with the username and password came back signed in as an administrator, with the second step switched on. It was true of the sign-in form and false of a URL one directory up. An account with a second step can no longer use its account password there.
- New Application passwords still work, on purpose. They are the right mechanism for an app: made deliberately, one per application, revoked one at a time, and no use for signing in to the admin. Jetpack signs its own calls with a token and is unaffected. The REST API was already safe, because WordPress only accepts an application password there.
- Fixed A refusal is written down once, and says what it was. It was being recorded twice, and the second row said "wrong username or password" about an attempt whose password was right.
1.1.2
- Fixed There were two ways of asking whether a code is valid, and only one was used. The unused one was never exercised by anything a customer does, which is how two implementations of the same question inside a security plugin drift apart. There is now one.
1.1.1
- Fixed "Require a code from an authenticator app" now requires one. In 1.1.0 the setting worked out correctly who it applied to and then nothing asked, so anybody who had not volunteered was never prompted and never stopped. A security setting that says require and does not is the worst kind, and it went out in the version that introduced it.
- New Somebody it applies to who has not set one up is sent to their profile, with a line saying why, and kept there until they have. They are never refused entry, because somebody who cannot get in cannot set one up either. The front of the site is untouched.
- New This plugin's own screens stay reachable while that is happening, for anybody who can change settings. An administrator who switches it on with no phone to hand can always switch it straight back off — without that, the setting is a trap whose only exit is the database.
1.1.0
- New A second step at sign-in. Six digits from an authenticator app after the password — the only thing in this plugin that helps against a password somebody else already has, from a reuse somewhere that leaked or a convincing copy of your sign-in page. Counting wrong guesses cannot help with a right one. Set one up under Users → Profile; requiring it of other people is a setting.
- New Nothing is sent anywhere to do it. No text messages, no account with a service, no third party. Your phone and your site share a secret and both look at the clock. The QR code is drawn on your own server rather than fetched from a chart service — plenty of plugins do that, which sends the secret for somebody's second factor to a stranger in a URL.
- New Four ways not to be locked out by it. Ten recovery codes, shown once. An administrator can switch it off for anybody from their profile.
wp login-security two-factor off <user>does the same from a terminal, for when the administrator is the one shut out. And it is never required of anybody until you say so.
- New The secret is sealed in the database, using your site's own salts, so a copy of the database on its own is not somebody else's second factor.
- New A wrong code is counted like a wrong password, so guessing at six digits earns the same lockout — and the log says "right password, wrong second-step code", which is worth being able to tell apart.
1.0.7
- Fixed A setting that is waiting on another one is readable again. A field held back until you switch something else on is dimmed, so it does not look identical to one you simply have not used — but it was dimmed so far that its label and explanation fell below the contrast a person with ordinary eyesight needs. Measured on the rendered screen at 2.55:1 against the 4.5:1 minimum, and now 4.75:1 and 5.02:1. The greying, the sunken background and the blocked cursor carry the rest of the signal.
1.0.6
- Fixed The dashboard did the same sums twice. It asked for the week's figures once for the sentence at the top and again for the four cards underneath, and worked them out from scratch both times. On a site with 300,000 attempts in the window that was three quarters of a second of the screen's loading time spent recalculating numbers it already had. Measured: 11 queries and 726ms, now 3 queries and 366ms.
- Fixed It stopped asking the database whether its own table exists, over and over. Every part of the plugin checks before reading, which is right, but one screen was asking seven separate times — about eight milliseconds of round trips to establish something that cannot change while a page is loading. It now asks once.
1.0.5
- Fixed The daily tidy-up no longer holds the table while it runs. Clearing old rows from the log of attempts was a single statement, however many rows matched. Measured on 300,000 of them: 6.63 seconds, and for all of it nothing else could write to the table. Worse, a host that cuts long queries off rolled the whole thing back — so nothing was deleted, there was more to delete the next day, and it quietly never caught up. It now goes a thousand rows at a time and stops after five seconds, leaving the rest for the next run.
- Fixed Emptying the log of attempts is no longer a wait. The button ran one delete over every row, which on a large table left the screen looking like it had hung for several seconds. It now empties the table outright: 161,000 rows in under a third of a second.
1.0.4
- Fixed The Who has tried screen no longer reads the whole table to show one page. It sorts newest first, and although the table had two or three indexes, none of them began with the column being sorted on — so the database read every row and sorted the lot to hand back twenty-five. Measured: a log with 400,000 attempts in it took 292ms to draw its first page and now takes half a millisecond. Nothing changes on a small site; the index is added automatically the first time the plugin loads after updating.
1.0.3
- Fixed The password rules now cover WooCommerce's own forms. Registration, Account details and Lost password on a shop are the forms customers actually meet, and none of them were checked. The reset form was the bad one: it fires the same WordPress hook, but posts the password under a different name — so the rules ran, found nothing, and approved every password on the shop. A check that silently approves is worse than no check, because the setting says it is on. Tested on a real shop through all three forms.
- Fixed No more "Error: Error:" on a shop. WooCommerce puts its own "Error:" in front of a registration message, so ours read it twice. Its account page does not, so that one is unchanged.
1.0.2
- New It can now refuse a password a machine would find. Everything else in this plugin counts wrong passwords, and none of it helps if the right one is
password123. Set a minimum length and turn away the usual list — decoration and all, becausePassword1!ispasswordwith a shift key — along with keyboard runs, rows of digits, and anything holding the username or the site's name. Checked when somebody sets or resets one, entirely on your own server, with nothing sent anywhere. Off until you switch it on, and it applies either to everybody or only to the people who can actually change the site.
- New It says plainly what it cannot do. The passwords already on your site are hashes and nothing can read them, this plugin included, so the rules apply from the next time each person sets one. The settings screen says so rather than implying a sweep that is not possible.
1.0.1
- Fixed A site deleted from a network no longer leaves its tables behind. WordPress asks every plugin for the tables it owns when a site is removed, and this one never answered, so its log of attempts stayed in the database for ever with nothing left to say which site it had belonged to. On a network that creates and removes sites regularly that is thousands of orphan tables and a database that stops fitting in its backup window. Deleting a site now takes its log of attempts with it.
1.0.0
- New Wrong passwords are counted, and the address is locked out. Five tries in fifteen minutes by default. The first lockout is twenty minutes, then an hour, then three, then nine, then a day — and one correct password clears the count entirely, so somebody who mistyped and then remembered is not on a hair trigger for the rest of the afternoon.
- New The slow, spread-out kind is caught too. One password list worked through from a thousand different machines never reaches five tries from any of them. Failures are counted against the username as well, across every address they came from.
- New A locked-out address is refused before the password is checked. A password hash is deliberately slow, so a site that checks every guess before saying no is taken down by its own defence. This answers at the very first hook WordPress offers, before it has looked anything up.
- New Usernames nobody on your site has are refused outright. admin, administrator, root, test and a few more — checked against your own users first, so a site that genuinely has somebody called support never turns them away.
- New You cannot lock yourself out. Signed-in administrators are never locked out. The address you switch the plugin on from is trusted automatically. There is a recovery link that trusts whatever address you open it from for an hour, so it works from a train. And WP-CLI can unlock anything.
- New Every attempt is written down and searchable. When, from where, which username was typed, and what happened to it. Filter by outcome, search by address or username, let one address back in from its row, or let everybody back in with one button.
- New The first screen shows which usernames are being tried, with a note beside each saying whether it is a real account on this site — which is how you find out the machines are working on a name that exists.
- New XML-RPC, without breaking Jetpack. One XML-RPC call can carry hundreds of password guesses. The default removes that one call and leaves the rest working; you can also switch XML-RPC off entirely, or leave it alone.
- New The sign-in page can be moved, and the screen is honest that it is not a lock — it stops the machines finding the page, which on most sites is thousands of requests a month. Off by default; the new address is shown and emailed the moment you save it.
- New Both halves of a failure get the same answer. WordPress tells a machine which usernames exist by wording the two failures differently. Here they read the same, and so does a password-reset request.
- New One email a day, not forty. A site the machines have found is locked out dozens of times a day. One email, saying how many and from where, is the version somebody actually reads.
- New Trusted addresses, including ranges. A single address, 203.0.113.0/24, or 203.0.113.* — an office, a VPN, a monitoring service. Their attempts are still recorded, because trusted means never locked out, not invisible.
- New WP-CLI for all of it. status, unlock, trust, door and log — the way back in that works when the admin screens are the thing you cannot reach.
- New It says what it keeps. IP addresses are personal data, so the retention setting is on the settings screen and suggested privacy-policy wording is added to Settings → Privacy for you to edit.
Pricing
Every plan includes every feature. Choose how many domains, and whether to pay yearly or once.
Pay yearly renews until you cancel
1 domain
One year of updates and support
5 domains
One year of updates and support
Unlimited domains
One year of updates and support
Pay once (lifetime) no renewals
5 domains
Updates and support for as long as we publish it
Unlimited domains
Updates and support for as long as we publish it
- Every plan includes every feature. You choose only the number of domains.
- 30-day refund, no reason needed. Refund policy
- Licence key and download straight after payment, by Stripe.
- A lapsed licence stops updates and support. It never switches a feature off.
- Local, development and staging copies are free and do not count against your domains.
- Yearly plans renew at the then-current price until you cancel. Cancel any time from your account.
- Where tax applies it is calculated at checkout and shown before you pay.