{"id":392797,"date":"2026-11-09T02:25:01","date_gmt":"2026-11-09T02:25:01","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/doctorx-hardening\/"},"modified":"2026-11-09T06:15:11","modified_gmt":"2026-11-09T06:15:11","slug":"doctorx-hardening","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/doctorx-hardening\/","author":23592987,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.7.5","stable_tag":"0.7.5","tested":"7.1.4","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"DoctorX Hardening","header_author":"DoctorX.ca","header_description":"Small, plain protections for a public WordPress site: visitors can no longer list your user accounts or everything your plugins can do, each person can add a code from their phone to their sign-in, a security.txt file tells researchers whom to contact, and an activity record (off until you switch it on) keeps who did what on the site. Each one is on Settings \u2192 Hardening.","assets_banners_color":"0e221d","last_updated":"2026-11-09 06:15:11","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/www.doctorx.ca\/store\/doctorx-hardening\/","header_author_uri":"https:\/\/www.doctorx.ca\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":139,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.7.5":{"tag":"0.7.5","author":"xaviercode","date":"2026-11-09 06:15:11","revision":3791873}},"upgrade_notice":{"0.6.8":"<p>First release on WordPress.org.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.gif":{"filename":"icon-128x128.gif","revision":3791744,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.gif":{"filename":"icon-256x256.gif","revision":3791744,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3791744,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3791744,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.7.5"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3791744,"resolution":"1","location":"assets","locale":"","width":1280,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3791744,"resolution":"2","location":"assets","locale":"","width":1280,"height":760},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3791744,"resolution":"3","location":"assets","locale":"","width":1280,"height":760},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3791744,"resolution":"4","location":"assets","locale":"","width":1280,"height":900},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3791744,"resolution":"5","location":"assets","locale":"","width":1280,"height":900}},"screenshots":{"1":"Settings \u2192 Hardening: each protection with its own switch.","2":"Setting up two-step sign-in: scan the code with an authenticator app, then type the code it shows.","3":"The sign-in page asks for the six-digit code after the password.","4":"The activity record (Tools \u2192 Activity): who did what on the site, with filters and downloads.","5":"History insights: events a day, failed sign-ins and the most active people."}},"plugin_section":[262246],"plugin_tags":[8531,600,149750,1909,46130],"plugin_category":[54],"plugin_contributors":[291576],"plugin_business_model":[],"class_list":["post-392797","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-activity-log","plugin_tags-security","plugin_tags-security-txt","plugin_tags-two-factor-authentication","plugin_tags-user-enumeration","plugin_category-security-and-spam-protection","plugin_contributors-xaviercode","plugin_committers-xaviercode"],"banners":{"banner":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/banner-772x250.png?rev=3791744","banner_2x":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/banner-1544x500.png?rev=3791744","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/icon-128x128.gif?rev=3791744","icon_2x":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/icon-256x256.gif?rev=3791744","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/screenshot-1.png?rev=3791744","caption":"Settings \u2192 Hardening: each protection with its own switch."},{"src":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/screenshot-2.png?rev=3791744","caption":"Setting up two-step sign-in: scan the code with an authenticator app, then type the code it shows."},{"src":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/screenshot-3.png?rev=3791744","caption":"The sign-in page asks for the six-digit code after the password."},{"src":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/screenshot-4.png?rev=3791744","caption":"The activity record (Tools \u2192 Activity): who did what on the site, with filters and downloads."},{"src":"https:\/\/ps.w.org\/doctorx-hardening\/assets\/screenshot-5.png?rev=3791744","caption":"History insights: events a day, failed sign-ins and the most active people."}],"raw_content":"<!--section=description-->\n<p>DoctorX Hardening makes your WordPress site harder to break into. It closes a few doors that WordPress leaves open, lets people add a code from their phone when they sign in, and can keep a record of who did what on your site.<\/p>\n\n<p>You do not need to know anything about security to use it. Turn it on and the main protections work right away. Everything is free. Nothing is locked and there is no paid version.<\/p>\n\n<h4>What it does as soon as you turn it on<\/h4>\n\n<ul>\n<li><strong>Hides your usernames.<\/strong> Normally anyone can look up the usernames on a WordPress site. A username is half of what someone needs to break in. Visitors can no longer see them.<\/li>\n<li><strong>Hides your list of plugins.<\/strong> Normally WordPress tells anyone who asks which plugins your site runs. Attackers use that list to look for a weak one. Visitors no longer get it.<\/li>\n<li><strong>Keeps author pages from giving names away.<\/strong> Links to author pages now go to your home page. If your site shows author pages on purpose, switch this one off.<\/li>\n<li><strong>Stops a small WordPress guess.<\/strong> When someone types a short address like \/fr\/ that does not exist, they get \"page not found\" instead of being sent to a page with a similar name.<\/li>\n<li><strong>Offers two-step sign-in.<\/strong> Each person can choose to type a 6-digit code from a free phone app after their password. Then a stolen password alone is not enough to get in. Nobody has to use it unless you make it required.<\/li>\n<\/ul>\n\n<p>Each one has its own on\/off switch under Settings \u2192 Hardening.<\/p>\n\n<h4>Extra protections you can switch on<\/h4>\n\n<p>These are all off until you switch them on. Each one says how to undo it.<\/p>\n\n<ul>\n<li><strong>Lock out password guessers.<\/strong> An internet address that gets the password wrong 5 times in a day cannot sign in for 30 days. You choose addresses that are never locked out, such as your office.<\/li>\n<li><strong>Require two-step sign-in<\/strong> for the roles you choose, such as administrators, from a date you pick.<\/li>\n<li><strong>Email a code<\/strong> to someone who does not have their phone with them.<\/li>\n<li><strong>Close little-used doors:<\/strong> XML-RPC (an old way for apps to sign in), app passwords, and the WordPress version number shown in your pages.<\/li>\n<li><strong>Hide your sign-in page<\/strong> at an address only you know. Not for shops or member sites, where visitors sign in themselves.<\/li>\n<li><strong>Block scanners.<\/strong> A visitor who asks for secret files that only hacking tools look for is blocked from the whole site for 7 days. Search engines are never blocked.<\/li>\n<li><strong>Activity record.<\/strong> See who signed in, who failed to, and who changed posts, pages, plugins, settings or users, and when. Get an email, Slack, Teams, Discord or Telegram alert when something important happens.<\/li>\n<li><strong>security.txt.<\/strong> A small standard file that tells someone who finds a security problem on your site how to reach you.<\/li>\n<li><strong>Cloudflare rules,<\/strong> if your site uses Cloudflare. Add ready made blocking rules with one button and take them away with another.<\/li>\n<li><strong>Ready made lines for your web server,<\/strong> for you or your web host to copy in.<\/li>\n<\/ul>\n\n<h4>What it does not do<\/h4>\n\n<p>So you know where its job ends:<\/p>\n\n<ul>\n<li>It is not a virus or malware scanner. It does not clean a site that has already been hacked.<\/li>\n<li>It does not check every visit for attacks the way a firewall service does.<\/li>\n<li>It does not make backups.<\/li>\n<li>It does not update or fix your other plugins and themes. Keep them up to date.<\/li>\n<li>It does not change what visitors, search engines or AI assistants see on your pages. Your search ranking is not affected.<\/li>\n<li>Hiding things makes an attack harder, not impossible. Strong passwords and updates still matter.<\/li>\n<li>It sends nothing about your site or your visitors to DoctorX or anyone else. The outside services listed further down (such as Cloudflare or Slack) are only used if you set them up yourself.<\/li>\n<\/ul>\n\n<h4>Who it is for<\/h4>\n\n<p>Any WordPress site owner who wants the common holes closed without learning security. It works with any theme and needs no other plugin.<\/p>\n\n<p>Made by <a href=\"https:\/\/www.doctorx.ca\/\">DoctorX.ca<\/a>.<\/p>\n\n<h3>External services<\/h3>\n\n<p>Nothing in this plugin talks to an outside service until you ask it to. None of the outside services below is used on a website set up not to send (DOCTORX_HARDENING_MAY_CONNECT set to false, or the doctorx_hardening_may_connect filter), such as a staging copy, except WordPress.org's checksums, which WordPress itself also fetches.<\/p>\n\n<ul>\n<li><p><strong>Cloudflare API<\/strong> (https:\/\/api.cloudflare.com\/client\/v4), only after you save a Cloudflare API token of your own on Settings \u2192 Hardening \u2192 Sign-in and server, and only for what you press there or switch on. That is:<\/p>\n\n<ul>\n<li>listing the zones the token can see<\/li>\n<li>reading a zone's custom firewall and rate-limit rules (Preview)<\/li>\n<li>reading the zone's cache rules, once a day, for the self-check (read only)<\/li>\n<li>adding, changing or putting back rules (Apply, Undo, Copy)<\/li>\n<li>sending the probe ban's addresses as IP access rules (Send them now)<\/li>\n<li>with \"When a page changes, have Cloudflare forget it\" (or DoctorX Command Center's \"Empty caches\"), the addresses of changed pages or a \"forget everything\" request.<\/li>\n<\/ul>\n\n<p>Sent: the token (as the request's authorisation), the zone's id, the rules this plugin generates (your host names, your trusted addresses, the hidden door's address when it is on), banned internet addresses, and page addresses. Nothing about your visitors or accounts is sent. Cloudflare's terms: https:\/\/www.cloudflare.com\/website-terms\/ and privacy policy: https:\/\/www.cloudflare.com\/privacypolicy\/<\/p><\/li>\n<li><strong>ipinfo.io<\/strong> (https:\/\/ipinfo.io), only when you choose \"ipinfo.io\" under \"Where addresses are\" on the activity record's More settings screen, save your own token, and an administrator opens an address in the record: that one internet address and your token. ipinfo's terms: https:\/\/ipinfo.io\/terms-of-service and privacy policy: https:\/\/ipinfo.io\/privacy-policy<\/li>\n<li><strong>Google Maps Static API<\/strong> (https:\/\/maps.googleapis.com), only when you switch on the sign-in map and save your own Google Maps key, and only when an administrator opens the map. Sent: up to 60 rounded map points (latitude and longitude to three decimals, about 100 metres) and your key. No addresses, names or accounts are sent. Google's terms: https:\/\/cloud.google.com\/maps-platform\/terms and privacy policy: https:\/\/policies.google.com\/privacy<\/li>\n<li><strong>Alerts<\/strong>, only to the destinations you add on the activity record's More settings screen (Alerts) and only for the rules you set. Sent: the alert's subject, text and a link to the record. For a webhook, also your site's address, the rule's name and the event (as below). The destinations can be:\n\n<ul>\n<li>Slack (the webhook address you paste). Terms https:\/\/slack.com\/main-services-agreement and privacy https:\/\/slack.com\/trust\/privacy\/privacy-policy<\/li>\n<li>Discord. Terms https:\/\/discord.com\/terms and privacy https:\/\/discord.com\/privacy<\/li>\n<li>Microsoft Teams. Terms https:\/\/www.microsoft.com\/en-us\/servicesagreement and privacy https:\/\/www.microsoft.com\/en-us\/privacy\/privacystatement<\/li>\n<li>Telegram (api.telegram.org with your bot's token and chat). Terms https:\/\/telegram.org\/tos and privacy https:\/\/telegram.org\/privacy<\/li>\n<li>any address of your own that takes JSON, signed with your secret if you set one.<\/li>\n<\/ul><\/li>\n<li><strong>Log forwarding<\/strong>, only when you switch it on on the activity record's More settings screen and switch on a destination with its connection details. Sent: every event as it is written, with its date, kind, level, message, who did it (user id, login and email address), the internet address and any addresses passed on by a proxy, the browser's user agent, what it was about, the event's details and your site's address. (Writing to this server's own system log is not an outside service and also happens on a website set up not to send.) The destinations can be:\n\n<ul>\n<li>this server's own system log<\/li>\n<li>a syslog server you name (UDP, TCP or TLS)<\/li>\n<li>Datadog's Logs API with your key. Terms https:\/\/www.datadoghq.com\/legal\/terms\/ and privacy https:\/\/www.datadoghq.com\/legal\/privacy\/<\/li>\n<li>Splunk's HTTP Event Collector with your token. Terms https:\/\/www.splunk.com\/en_us\/legal\/splunk-general-terms.html and privacy (Splunk is part of Cisco) https:\/\/www.cisco.com\/site\/us\/en\/about\/legal\/privacy.html<\/li>\n<li>any webhook address of yours.<\/li>\n<\/ul><\/li>\n<li><strong>WordPress.org<\/strong> (https:\/\/api.wordpress.org), only when you switch on \"Check WordPress's own files once a day\" in the activity record's settings: once a day, your WordPress version and language, to fetch the official checksums of WordPress's files (the same request WordPress makes for its own updates). WordPress.org's privacy policy: https:\/\/wordpress.org\/about\/privacy\/<\/li>\n<li><strong>Your server's DNS resolver<\/strong>, only when you switch on \"Also ask for the address's host name\" and an administrator opens an address in the record: that one internet address, to look up its host name (reverse DNS).<\/li>\n<li>The emails this plugin sends (sign-in codes, the hidden door's address, notes about two-step sign-in) go through your site's own mail setup to your own people.<\/li>\n<li><strong>Your own site's address<\/strong>, once a day, for the self-check: a few requests to your own site (a probe file in each private folder and a private probe page), which pass through your proxy or CDN if you have one. Nothing is sent to anybody else.<\/li>\n<li>The crawler-list script on the \"Lines for your web server\" screen is a file you download and run on your server yourself: it fetches the address lists Google, Bing and Cloudflare publish (developers.google.com, www.bing.com, www.cloudflare.com). The plugin itself does not fetch them.<\/li>\n<\/ul>\n\n<h3>Source code and credits<\/h3>\n\n<p>All code is included unminified. The picture you scan when setting up two-step sign-in is drawn in the browser by QR Code Generator for JavaScript by Kazuhiko Arase (MIT licence), included unchanged in assets\/vendor\/qrcode-generator.js. The word \"QR Code\" is a registered trademark of DENSO WAVE INCORPORATED.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>In your WordPress dashboard go to Plugins \u2192 Add New, search for \"DoctorX Hardening\", press Install, then Activate.<\/li>\n<li>That is all. The protections in \"What it does as soon as you turn it on\" are now working. Open Settings \u2192 Hardening to see them, and switch off any you do not want.<\/li>\n<li>To add a phone code to your own sign-in, go to Users \u2192 Two-step sign-in (or Profile \u2192 Two-step sign-in). You need a free authenticator app on your phone, such as Google Authenticator or Microsoft Authenticator. It takes about two minutes. Keep the backup codes it gives you somewhere safe.<\/li>\n<li>Optional: to keep an activity record, tick \"Keep an activity record\" on Settings \u2192 Hardening. You will find it under Tools \u2192 Activity.<\/li>\n<li>Optional: for a security.txt file, type a contact email on Settings \u2192 Hardening and press Save.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"what%20are%20the%20limits%20of%20each%20protection%3F\"><h3>What are the limits of each protection?<\/h3><\/dt>\n<dd><p>The fine print, so that nobody relies on more than is there:<\/p>\n\n<ul>\n<li><strong>Hidden plugin list:<\/strong> only the list is hidden. A person who already knows or guesses a plugin can still find it in other ways.<\/li>\n<li><strong>Two-step sign-in<\/strong> works on normal sign-in pages, including most shop and membership plugins. Apps that sign in with a plain password are refused for an account that has it on. Give each app its own app password instead (at the bottom of your profile). Sign-in through another service, such as \"Sign in with Google\", does not pass through it.<\/li>\n<li><strong>Wrong codes are limited:<\/strong> 5 wrong codes in 15 minutes, or 20 in a day, pause the code step. The person is emailed. While it is paused the real person cannot sign in either, so set a new password if someone else knows yours. A new password ends the pause.<\/li>\n<li><strong>Any administrator can switch two-step sign-in off for everybody.<\/strong> When that is done on the settings screen or the Plugins screen, everybody who used it is emailed. If it is switched off in some other way (by editing files or the database), nobody is told.<\/li>\n<li><strong>The emergency line<\/strong> DOCTORX_HARDENING_NO_TWO_STEP in wp-config.php turns the phone code off for every account. It is for a locked-out owner, only for as long as needed.<\/li>\n<li><strong>Multisite networks<\/strong> do not get two-step sign-in or the hidden sign-in page.<\/li>\n<li><strong>The phone app's secret key<\/strong> is stored encrypted with your site's security keys. If your host changes those keys, everybody sets two-step sign-in up again (backup codes still work to get in).<\/li>\n<li><strong>security.txt<\/strong> is a notice, not a lock. It makes it easy for an honest person to reach you.<\/li>\n<li><strong>The password-guess lockout<\/strong> counts by internet address. Many people can share one address, such as an office, and a lockout holds for all of them. That is why you can list trusted addresses, and why an address an administrator used is only ever paused for an hour. A site behind a proxy or Cloudflare must name it in the settings, or every visitor looks like the same address.<\/li>\n<li><strong>The hidden sign-in page<\/strong> hides the page. It is not a lock. Anyone who learns the address can still try, and the lockout and phone code still apply.<\/li>\n<li><strong>The scanner block<\/strong> only sees requests that reach WordPress. Your web server may answer some of them first. The web server lines and Cloudflare rules cover those.<\/li>\n<li><strong>A code by email<\/strong> is weaker than the phone app. Whoever has both the password and the email account gets in.<\/li>\n<li><strong>Cloudflare's free plan<\/strong> allows five custom rules and one rate limit rule. The DoctorX rules use two or three and the rate limit.<\/li>\n<li><strong>The web server lines<\/strong> are text for a person to put in place. The plugin cannot check that they were.<\/li>\n<\/ul><\/dd>\n<dt id=\"will%20the%20block%20editor%20or%20the%20mobile%20app%20still%20work%3F\"><h3>Will the block editor or the mobile app still work?<\/h3><\/dt>\n<dd><p>The block editor, yes. People who work on the site, and apps that sign in as them with an application password, still get the full list of what the site can do. A signed-in customer or subscriber gets the answer without the list, like a visitor. The list of user accounts stays readable to anybody who is signed in, as WordPress has it. Only visitors lose it.<\/p>\n\n<p>One thing changes for apps. An app that signs in with the ordinary password of an account that has two-step sign-in on is refused: give it an application password instead (see \"What about apps and services that sign in with my password?\" below).<\/p><\/dd>\n<dt id=\"my%20theme%20shows%20author%20names%20under%20each%20post.%20do%20they%20disappear%3F\"><h3>My theme shows author names under each post. Do they disappear?<\/h3><\/dt>\n<dd><p>No. The author's display name still shows. Only the link behind it now points to the home page instead of an author page.<\/p><\/dd>\n<dt id=\"i%20use%20an%20seo%20plugin%20that%20makes%20its%20own%20author%20sitemap.\"><h3>I use an SEO plugin that makes its own author sitemap.<\/h3><\/dt>\n<dd><p>This plugin only removes authors from WordPress's built-in sitemap. Turn off author sitemaps in your SEO plugin too.<\/p><\/dd>\n<dt id=\"something%20on%20my%20site%20stopped%20finding%20its%20way%20after%20i%20updated.\"><h3>Something on my site stopped finding its way after I updated.<\/h3><\/dt>\n<dd><p>A few outside apps, and scripts in some themes, read the list at \/wp-json\/ without signing in, to find out what the site offers. Untick \"Hide the list of what your site can do from visitors\" and it is as before. A developer can instead keep one part of the list open with the <code>doctorx_hardening_open_rest_namespaces<\/code> filter.<\/p><\/dd>\n<dt id=\"which%20authenticator%20app%20do%20i%20need%3F\"><h3>Which authenticator app do I need?<\/h3><\/dt>\n<dd><p>Any app that makes six-digit sign-in codes that change every 30 seconds. Your phone's app shop has several free ones, and most password managers do it too. Nothing has to be bought and no account has to be opened anywhere.<\/p><\/dd>\n<dt id=\"i%20lost%20my%20phone.%20how%20do%20i%20get%20in%3F\"><h3>I lost my phone. How do I get in?<\/h3><\/dt>\n<dd><p>Type one of your backup codes where the sign-in page asks for the six-digit code. Each works once. Then switch two-step sign-in off under Users (or Profile) \u2192 Two-step sign-in, with another backup code, and set it up again on the new phone.<\/p><\/dd>\n<dt id=\"i%20lost%20my%20phone%20and%20my%20backup%20codes.\"><h3>I lost my phone and my backup codes.<\/h3><\/dt>\n<dd><p>Somebody who may edit your account (on most sites, another administrator) can switch two-step sign-in off for you: Users, your name, \"Switch it off for this account\". An email about it goes to your address.<\/p>\n\n<p>If you are the only administrator, add this line to wp-config.php, above the line that says \"That's all, stop editing\" (your web host can do this for you):<\/p>\n\n<pre><code>define( 'DOCTORX_HARDENING_NO_TWO_STEP', true );\n<\/code><\/pre>\n\n<p>Nobody is asked for a code while the line is there, on any account: every account opens with its password alone. So do it only when nobody else can know your password or the password of any other account that can change the site (if in doubt, have new passwords set first), and for as short a time as you can. Somebody who signs in while the line is there stays signed in after it is taken out, until they sign out or their password is changed. Sign in, open Users \u2192 Two-step sign-in, and press \"Switch two-step sign-in off\": while the line is there, that button asks for no code. Set it up again with your new phone, and take the line out.<\/p><\/dd>\n<dt id=\"it%20says%20%22too%20many%20wrong%20sign-in%20codes%22%2C%20and%20it%20was%20not%20me.\"><h3>It says \"too many wrong sign-in codes\", and it was not me.<\/h3><\/dt>\n<dd><p>Then somebody typed your password correctly and guessed at the code. The lock stopped the guessing. Use \"Lost your password?\" on the sign-in page to set a new password: that shuts them out and opens the lock, and you sign in with your new password and a code as usual.<\/p>\n\n<p>If the email with the link does not arrive, another administrator can set a new password for you on your profile (Users, your name), which opens the lock too. If you are the only administrator, ask your web host to set a new password for your account: hosts can do that without signing in to the site. The stranger is then out, and you sign in with the new password and a code (if the lock is still on, it opens at the time the page shows).<\/p>\n\n<p>Do not add the wp-config.php line from the answer above while somebody else knows your password. That line switches the second step off for every account, so the stranger would be signed in with the password alone. Change the password first. Use the line only for a lost phone.<\/p>\n\n<p>If your site's \"Lost your password?\" page belongs to a shop or membership plugin, and your WordPress is a release from before November 2024, a password set there may not open the lock. It then opens at the time shown, or when the password is changed on the profile screen.<\/p><\/dd>\n<dt id=\"nobody%27s%20codes%20work%20any%20more.\"><h3>Nobody's codes work any more.<\/h3><\/dt>\n<dd><p>The codes depend on the time. If everybody's codes fail at once, the clock of the website's server is probably wrong by more than half a minute: ask your web host to check it. If only yours fail, check that your phone sets its clock automatically.<\/p><\/dd>\n<dt id=\"does%20two-step%20sign-in%20apply%20to%20everybody%3F%20can%20i%20make%20it%20compulsory%3F\"><h3>Does two-step sign-in apply to everybody? Can I make it compulsory?<\/h3><\/dt>\n<dd><p>Each person switches it on for their own account. Settings \u2192 Hardening shows how many of the site's administrators have it on. To make it compulsory for some roles, from a date, use \"Two-step sign-in required\" on Settings \u2192 Hardening \u2192 Sign-in and server.<\/p><\/dd>\n<dt id=\"what%20about%20apps%20and%20services%20that%20sign%20in%20with%20my%20password%3F\"><h3>What about apps and services that sign in with my password?<\/h3><\/dt>\n<dd><p>An account with two-step sign-in can no longer be used with just its password by a program (XML-RPC, a script, a sign-in box that works without loading a page): there would be nowhere to ask for the code. Give each app its own application password, at the bottom of your profile (if there is no \"Application Passwords\" section there, another plugin or your web host has switched them off). That is safer anyway: you can take one app's key away without changing your password.<\/p><\/dd>\n<dt id=\"our%20host%20changed%20the%20site%27s%20%22security%20keys%22%20%28salts%29%2C%20and%20now%20nobody%27s%20codes%20work.\"><h3>Our host changed the site's \"security keys\" (salts), and now nobody's codes work.<\/h3><\/dt>\n<dd><p>The key each app shares with the site is kept encrypted with your site's security keys. When those keys change (after a break-in, or because a host or plugin does it on a schedule), the codes can no longer be checked. The plugin notices at the next visit to the sign-in page and writes to everybody with two-step sign-in, and the page that asks for the code says what happened. Settings \u2192 Hardening says for thirty days what was done and whether everybody could be reached. (If the site's address or its folder changed at the same time, as on a copy of the site, nobody is written to. The plugin keeps a mark of the place for this, not the address, so that a copy made by replacing the address throughout the database is still told from the site itself.) Sign in with a backup code, then set it up again or switch it off with another backup code: either takes two minutes. Backup codes are not affected by the change.<\/p>\n\n<p>If a host or a plugin changes the keys on a schedule, this happens every time. Ask them to stop, or two-step sign-in is not practical on that site.<\/p><\/dd>\n<dt id=\"where%20is%20the%20security.txt%20file%2C%20and%20why%20can%20i%20not%20see%20it%3F\"><h3>Where is the security.txt file, and why can I not see it?<\/h3><\/dt>\n<dd><p>At \/.well-known\/security.txt on your domain, which is where the standard puts it. It is served only when the site is on HTTPS and lives at the top of its domain, when a contact is set, and while its date is good. Some web servers refuse every address with \"\/.\" in it before WordPress is asked. The \"Check that visitors can read it\" button on the settings screen tells you, and your host can let that one address through. If a file of that name already lies on the server, it wins: the button tells you that too.<\/p><\/dd>\n<dt id=\"why%20does%20the%20security.txt%20file%20run%20out%3F\"><h3>Why does the security.txt file run out?<\/h3><\/dt>\n<dd><p>The standard requires a date, so that nobody reports a problem to an address that stopped being read years ago. A contact you give or change is good for 360 days. You get an email and a notice on Settings \u2192 Hardening 30 days before the end. Pressing Save on the settings screen then confirms the contact for another 360 days. If you do nothing, the file is taken down on that date and comes back the moment you press Save.<\/p><\/dd>\n<dt id=\"i%20am%20paused%20by%20the%20password-guess%20lockout%20on%20my%20own%20site.\"><h3>I am paused by the password-guess lockout on my own site.<\/h3><\/dt>\n<dd><p>Wait until the time the sign-in page shows, or sign in from another network (a phone without Wi-Fi): only the address is paused, not your account. Or have your web host (or anyone with access to the server) do one of these:<\/p>\n\n<ul>\n<li>run <code>wp doctorx-hardening unlock --all<\/code> (or <code>unlock YOUR_ADDRESS<\/code>), or <code>wp doctorx-hardening guard off<\/code><\/li>\n<li>add <code>define( 'DOCTORX_HARDENING_NO_LOCKOUT', true );<\/code> to wp-config.php, above \"That's all, stop editing\". Nobody is paused or counted while it is there, and Settings \u2192 Hardening reminds you to take it out.<\/li>\n<\/ul>\n\n<p>Add your office and home addresses to the trusted list so this cannot happen there.<\/p><\/dd>\n<dt id=\"my%20own%20address%20was%20banned%20by%20the%20probe%20ban.\"><h3>My own address was banned by the probe ban.<\/h3><\/dt>\n<dd><p>Sign in from another network (a phone without Wi-Fi) and lift the ban on Settings \u2192 Hardening \u2192 Sign-in and server, or run <code>wp doctorx-hardening unban --all<\/code> (or <code>wp doctorx-hardening unlock ADDRESS<\/code>, which lifts a sign-in pause and a ban of that address) on the server, or add <code>define( 'DOCTORX_HARDENING_NO_PROBE_BAN', true );<\/code> to wp-config.php: nobody is banned or counted while it is there.<\/p><\/dd>\n<dt id=\"i%20lost%20the%20address%20of%20the%20hidden%20sign-in%20door.\"><h3>I lost the address of the hidden sign-in door.<\/h3><\/dt>\n<dd><p>It was emailed to the site's administration address when it was set. Otherwise run <code>wp doctorx-hardening door<\/code> on the server to see it, <code>wp doctorx-hardening door off<\/code> to switch the door off, or add <code>define( 'DOCTORX_HARDENING_NO_DOOR', true );<\/code> to wp-config.php: wp-login.php answers again at once.<\/p><\/dd>\n<dt id=\"will%20any%20of%20this%20hurt%20my%20search%20ranking%20or%20ai%20assistants%3F\"><h3>Will any of this hurt my search ranking or AI assistants?<\/h3><\/dt>\n<dd><p>No part of it changes what visitors, search engines or AI assistants read on your pages. The probe ban does not count a crawler. The Cloudflare rules leave every crawler Cloudflare verifies alone. The server lines do not name a crawler to turn away. \"Hide the WordPress version\" changes a few characters in the addresses of WordPress's own script files, nothing that is read.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20i%20delete%20the%20plugin%3F\"><h3>What happens when I delete the plugin?<\/h3><\/dt>\n<dd><p>Its settings are removed (on a multisite network, on every site). So are the sign-in guard's settings, the paused and banned addresses and the sealed Cloudflare token. The rules already at Cloudflare stay there: remove them with Undo first if you want them gone. Everything kept for two-step sign-in on every account is removed too, and so is the activity record with its events. (Simple History's own tables, if you still have them, are left alone.) Signing in then takes only the password again. Deactivating without deleting keeps everything for when you switch it back on. While it is deactivated nobody is asked for a code, and when it is deactivated on the Plugins screen everybody who had two-step sign-in is told by email.<\/p><\/dd>\n<dt id=\"what%20does%20the%20activity%20record%20keep%2C%20and%20for%20how%20long%3F\"><h3>What does the activity record keep, and for how long?<\/h3><\/dt>\n<dd><p>Each event says what happened, when, who did it (a signed-in person, an anonymous visitor, WordPress itself, or WP-CLI), the address the request came from, the browser's name, and the details of what changed (a post's title before and after, a plugin's version before and after). Addresses are kept without their last part unless you choose to keep them whole. Events are removed after the number of days you choose (60 unless you change it), once a day, and the removal is itself recorded. Administrators read everything. Editors read the events about what they can change themselves. Addresses are shown to administrators only.<\/p><\/dd>\n<dt id=\"i%20used%20simple%20history.%20do%20i%20lose%20its%20log%3F\"><h3>I used Simple History. Do I lose its log?<\/h3><\/dt>\n<dd><p>No. Switch the activity record on, then press \"Bring in Simple History's events\" on Settings \u2192 Hardening (or run \"wp doctorx-hardening activity import\"). Every event comes over with its date, address, details and who did it. Pressing it again brings in only what is new. Simple History's own tables are not changed, so switching back is reactivating it. Code written for Simple History (\"do_action( 'simple_history_log', ... )\") keeps being recorded once Simple History is gone.<\/p><\/dd>\n<dt id=\"can%20it%20tell%20me%20when%20something%20happens%3F\"><h3>Can it tell me when something happens?<\/h3><\/dt>\n<dd><p>Yes. On the activity record's \"More settings\" screen, add a destination: an email address, a Slack, Discord or Microsoft Teams webhook, a Telegram bot, or any webhook. Then add a rule. It can be a ready one (security, content, updates, failed sign-ins, plugins installed, somebody made an administrator) or your own (kinds of events, the lowest level, who did it, words). \"Alert me about events like these\" on the record makes a rule from what you searched. Each rule sends at most the number of alerts an hour you set, and a run of failed sign-ins alerts at the 1st, 10th, 100th attempt. Nothing is sent until you switch alerts on.<\/p><\/dd>\n<dt id=\"can%20it%20send%20the%20record%20to%20our%20log%20service%3F\"><h3>Can it send the record to our log service?<\/h3><\/dt>\n<dd><p>Yes: syslog (this server's, or another over UDP, TCP or TLS), Datadog, Splunk or any webhook. Keys, tokens and passwords are kept sealed and are not shown again. Events are sent once the page has answered, at most 50 a request. What a request cannot send (more events, a destination that is down) waits, and is sent in order by a scheduled job that exists only while forwarding is on and something waits. At most 5,000 events wait for each destination. Past that the oldest are dropped, and the screen, <code>wp doctorx-hardening activity forward list<\/code> and the activity record say how many. Events made by backfill are not forwarded. Events dated in the past by hand also wait their turn (at most 500 a destination).<\/p>\n\n<p>Delivery is \"at least once\": after a failure half-way an event can arrive twice. So every event carries a delivery id that a receiver can use to drop repeats. It is the \"uid\": your site, the event's number and its count. Webhooks also get it in the X-DRX-Event-Id header. Destinations on this server itself or on link-local addresses (such as a cloud metadata service) are refused. A destination's name is looked up once and the connection goes to the address that was checked. A name that cannot be found is not sent to. TLS certificates are checked, for syslog over TLS and for the database with TLS, against the certificates WordPress trusts. Code can name your own CA file with the filter doctorx_hardening_activity_ca_file. A website set up not to send (the line <code>define( 'DOCTORX_HARDENING_MAY_CONNECT', false );<\/code> in wp-config.php) sends no alert, forwards nothing and looks nothing up.<\/p><\/dd>\n<dt id=\"does%20it%20look%20up%20where%20an%20address%20is%3F\"><h3>Does it look up where an address is?<\/h3><\/dt>\n<dd><p>Only if you choose how. It can use a database file on your server (MaxMind GeoLite2, DB-IP Lite or IPinfo's .mmdb), and then nothing leaves the server. Or it can use ipinfo.io with your own token, and then only when an administrator opens an address (not on a website set up not to send). Out of the box nothing is looked up.<\/p><\/dd>\n<dt id=\"does%20the%20activity%20record%20slow%20the%20site%20down%3F\"><h3>Does the activity record slow the site down?<\/h3><\/dt>\n<dd><p>Not for visitors: nothing of it runs on a page view. A failed sign-in costs one database write. A run of them from one address is counted on one line.<\/p><\/dd>\n<dt id=\"privacy%3A%20what%20does%20it%20keep%20about%20people%2C%20and%20where%20does%20it%20go%3F\"><h3>Privacy: what does it keep about people, and where does it go?<\/h3><\/dt>\n<dd><ul>\n<li><p>For each account with two-step sign-in the plugin keeps, in your own database:<\/p>\n\n<ul>\n<li>the key shared with the authenticator app (encrypted)<\/li>\n<li>hashes of the backup codes<\/li>\n<li>the day it was switched on, and which code was used last<\/li>\n<li>the times of recent wrong codes (those older than a day are removed the next time a code is typed for the account)<\/li>\n<li>when the account was last warned by email.<\/li>\n<\/ul>\n\n<p>While somebody sets it up, the new key is kept (encrypted) until they finish or cancel. New backup codes are kept (encrypted) for up to ten minutes, until the screen has shown them once. A sign-in that is waiting for its code can be used for ten minutes, and keeps the address the person was on their way to. WordPress clears the stored note away later, with its other short-lived notes. After a change of the site's security keys, the names of up to twenty people who could not be told by email are kept for the settings screen, which shows them for thirty days. They are removed at the first visit to the sign-in page after those thirty days.<\/p><\/li>\n<li>It emails the account's own address when two-step sign-in is switched on or off for the account, when a backup code is used, when a wrong code is typed after the right password (once a day at most), when wrong codes lock the second step, when the site's security keys were changed, and when two-step sign-in stops for the whole site.<\/li>\n<li>The contact you give for security.txt is published in that file for anybody to read. Use an address meant for that.<\/li>\n<li>The activity record (when you switch it on) keeps, in your own database, what is described under \"What does the activity record keep\" above, for the number of days you choose. It adds a paragraph about itself to the suggested text of your privacy policy (Settings \u2192 Privacy). It takes part in WordPress's \"Export Personal Data\" and \"Erase Personal Data\" tools: erasing takes the person's name, address and browser out of their events. Notes a person wrote on events, and notes or entries added by hand that name their email address, are in their export. Erasing takes them off as the notes' writer and takes the address out of those words, and leaves other people's events their own details. The weekly email (if you switch it on) goes to the addresses you give. The feed (if you switch it on) is readable by whoever has its secret. Alerts and forwarding (if you switch them on and set a destination up) send events, with the person's name and address, to the services you choose. The suggested privacy text then names them. Looking up where an address is sends it to ipinfo.io only if you choose that and only when an administrator opens it.<\/li>\n<li>Nothing is sent to DoctorX.ca or to anybody else. \"Check that visitors can read it\" makes your site fetch its own file.<\/li>\n<\/ul><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.7.5<\/h4>\n\n<ul>\n<li>Security: the activity record keeps an event that comes from a form (the file editor, settings screens, signing out everywhere, a new account and others) only after that form's own security check and the person's rights are confirmed.<\/li>\n<li>Fix: when two site self-checks start at the same moment, only one of them runs.<\/li>\n<li>Behind a proxy: once the proxy addresses are listed, the other DoctorX plugins on this site read the visitor's address the same way, and the screen says so.<\/li>\n<li>Fix: the activity record's More settings and a new feed secret are kept again.<\/li>\n<li>Fix: trusted addresses are left out of every Cloudflare rule, of the nginx lines and of the ban list sent to Cloudflare, as the guard does. A trusted range written as ::ffff: with a short prefix no longer holds every IPv4 address.<\/li>\n<li>Fix: Cloudflare's own addresses are spared from pauses and bans only on a site behind Cloudflare.<\/li>\n<li>Fix: alerts to Discord and Teams show typed names as plain words.<\/li>\n<li>A signed-in person's admin-ajax and REST requests sent from another website are refused. A request that names no website, a link followed from another website and an application password go on as before.<\/li>\n<li>The activity feed's secret is now sent in a header (the feed reader's password), not in the address, because addresses are written to server logs. The old secret is replaced once.<\/li>\n<\/ul>\n\n<h4>0.7.1<\/h4>\n\n<ul>\n<li>Fix: a sign-in waiting for its code is kept in the database with its own end time. On a site with an object cache, a cache flush in the middle of signing in no longer sends the person back to the sign-in page. The same ten minutes, one use and lock after five wrong codes apply.<\/li>\n<li>Fix: the backup codes shown once, the note after an administrator switches two-step sign-in off for someone, the Cloudflare preview waiting for Apply and the notes shown after a button are kept the same way.<\/li>\n<li>On a multisite network, a note or a Cloudflare preview waiting for Apply stays on the site it was made on.<\/li>\n<li>New: a daily self-check. Each private folder under uploads gets a small probe file that is fetched without a cookie and deleted at once. If a visitor can read it, the Sign-in and server tab names the folder with the nginx and Apache lines that close it.<\/li>\n<li>New: the same check fetches a private probe once with a one-time cookie and once without. If the answer without the cookie is the private one, or a cache hit, the tab says your proxy or CDN caches by address alone. With a Cloudflare token it also lists the zone's cache rules that make answers cacheable (read only).<\/li>\n<li>The check only reads. It never changes the web server or Cloudflare. A site that cannot reach its own address skips it, and the tab says so once. A \"Check now\" button runs it at once.<\/li>\n<li>After the update, a sign-in that was already waiting for its code ends. The person signs in again.<\/li>\n<\/ul>\n\n<h4>0.6.9<\/h4>\n\n<ul>\n<li>Fix: on a server that gives IPv4 addresses in IPv6 form (::ffff:203.0.113.9), each visitor is counted on their own. One visitor's failed sign-ins no longer pause everyone, and the trusted list and private addresses match.<\/li>\n<li>Fix: behind a proxy that adds the port to X-Forwarded-For (203.0.113.9:51234), the guard reads the proxy's entry. A visitor can no longer choose the address the guard counts.<\/li>\n<li>Fix: when a proxy writes something in X-Forwarded-For that is not an address, the guard counts the proxy's own address, not what the visitor wrote before it.<\/li>\n<li>Fix: the activity record notes changes made in Redirection when the web address uses capital letters.<\/li>\n<li>Fix: the activity record keeps IPv4 addresses given in IPv6 form as IPv4, and a forwarded address with a port, as the guard reads them. The trusted list, the proxies list and lifting a pause read every way of writing such an address.<\/li>\n<\/ul>\n\n<h4>0.6.8<\/h4>\n\n<ul>\n<li>First release on WordPress.org. Needs WordPress 6.2 or later.<\/li>\n<li>Hidden user list and route list, author pages kept private, no guessed pages for language-style addresses.<\/li>\n<li>Two-step sign-in with an authenticator app and backup codes.<\/li>\n<li>security.txt with its expiry date and reminders.<\/li>\n<li>Sign-in and server protections, each off until you switch it on: password-guess lockout, one message for wrong names and passwords, small doors shut, two-step sign-in required for chosen roles, a code by email, a hidden sign-in door, a probe ban, Cloudflare edge rules with your own token, and lines for your web server.<\/li>\n<li>Activity record (off until you switch it on) under Tools \u2192 Activity, with filters, downloads, insights, a weekly email, Simple History import and WP-CLI commands. Alerts, forwarding, core file checks and address lookups are each off until you switch them on.<\/li>\n<\/ul>","raw_excerpt":"Simple security for WordPress: hides your usernames and plugin list, adds a phone code to sign-in, and records who changed what.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/392797","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=392797"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/xaviercode"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=392797"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=392797"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=392797"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=392797"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=392797"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=392797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}