WordPress logins are guessed continuously by automated systems, and a correct password is indistinguishable from a legitimate one. Two-factor authentication is what makes a stolen or guessed password insufficient on its own.
It is also the security measure most likely to lock you out of your own site, which shapes how to set it up.
Which method to use
An authenticator app generating a code every thirty seconds. Works offline, costs nothing, and is the right default.
Email codes. Better than nothing and weaker than the rest: anyone with access to that mailbox has your second factor, and mail can be delayed or filtered.
Hardware keys. The strongest option, and worth it for a site where a compromise would be serious.
SMS. Avoid where you can. Numbers can be taken over, and messages do not always arrive.
Save the recovery codes before you need them
The step that decides whether losing a phone is an inconvenience or an afternoon.
Setting up two-factor produces backup codes. Save them somewhere that is not the phone running the authenticator and not the site they protect. A password manager, or printed.
Without them, a lost device means editing files over FTP to disable the plugin, which is recoverable and not something to discover at a bad moment. How to Recover When You Are Locked Out of WP Admin goes into that route.
Enforce it by role, not for everyone
Administrators and editors are worth protecting. A site with hundreds of subscribers does not benefit from forcing two-factor on all of them, and doing so generates support requests that outweigh the risk those accounts carry.
Set it as required for the roles that can change the site, and optional below that. There is more on which roles can do what in WordPress User Roles and Permissions.
Give people a grace period rather than switching it on at once. A hard cutover on a multi-author site produces several locked-out people simultaneously, all contacting whoever set it up.
Application passwords are a separate thing
Worth understanding, because it looks like a hole and is not.
WordPress supports per-application credentials for API access. These deliberately bypass two-factor, because a script cannot type a code.
That means an application password is a full credential for the API. Create one per integration, name it after what uses it, and revoke ones you no longer need: the list is where forgotten access accumulates. Securing the WordPress REST API and XML-RPC explains the interface they use.
Block XML-RPC, or the second factor is bypassable there
The detail that undoes the whole measure on some setups.
xmlrpc.php authenticates with a username and password and does not prompt for a second factor. A site with two-factor on the login form and XML-RPC open is protected at the front door and not at the side one.
Block it at the server unless something you use needs it:
<Files xmlrpc.php> Require all denied </Files>
Blocking at the server is better than a plugin, because a plugin has to load WordPress to refuse the request.
What two-factor does not cover
It protects the WordPress login. It does not protect the other ways into your site.
cPanel, FTP and the database each have their own credentials, and someone with file access can disable the two-factor plugin by renaming a folder.
So enable it on the hosting account as well, which is a separate login. There is more on that side, and treat file access as the thing that ultimately matters in Two-Factor Authentication in cPanel.
Combine it with the cheap measures
Two-factor stops a correct password being enough. Two other settings stop most attempts reaching that point.
Limit login attempts, so automated guessing is throttled rather than unlimited. Make sure your own address is on the allow list before you need it.
Do not use admin as a username, and make sure display names differ from login names: otherwise the username is published on every post you write.
Together those three cover the login route almost entirely. There is more on the rest, which is mostly keeping software current in How to Secure WordPress Against Vulnerabilities.
Test it properly
Set it up on your own account first, log out, and log back in with the code. Then confirm a recovery code works, and generate a fresh set afterwards since most are single-use.
Only then enforce it for others, and tell them what to expect, with the setup instructions, before it becomes required rather than after.
Check what the login surface actually is
Adding a second factor to one door does nothing about the others, and most sites have several.
curl -sI https://example.com/wp-login.php | head -1
curl -sI https://example.com/xmlrpc.php | head -1
curl -sI https://example.com/wp-json/wp/v2/users | head -1
awk '$7 ~ /wp-login|xmlrpc/ {print $7}' ~/logs/example.com | sort | uniq -c | sort -rn | head
The log tells you which addresses are actually receiving attempts. Anything with substantial traffic is a route somebody is testing, whether or not the interface acknowledges it as a login.
The older application interface is the one that undermines this most often, since it authenticates with a password alone and is enabled by default.
Plan for the person who loses their device
Every arrangement here fails the same way, and the recovery has to exist before it is needed.
wp user list --role=administrator --fields=user_login,user_email wp plugin list --status=active --field=name | grep -i 'two\|2fa\|auth'
Somebody with shell or database access can disable the requirement for one account. That means the recovery depends on somebody else having that access, which is worth confirming rather than assuming.
Where one person is the only administrator and the only one with server access, the arrangement has a single point of failure that is a phone. Adding a second administrator is the cheap correction.
Confirm it is actually being required
A plugin that is active and a requirement that is enforced are different things.
wp user list --role=administrator --field=user_login | while read u; do printf '%-16s %s\n' "$u" "$(wp user meta get "$u" two_factor_enabled 2>/dev/null || echo 'bilinmiyor')" done
Read which accounts have it configured rather than which could. On most sites the answer is the person who set it up and nobody else.
Test by logging in from a private window as an ordinary administrator account. If the second step does not appear, the requirement applies to a role that account is not in, which is the usual configuration mistake.