XenForo’s security model is boring on purpose. Passwords are one-way hashes you cannot read back. Four failed logins trip a limit you chose. Staff can be forced onto two-step verification. A compromised account can be locked in two different ways. HTTPS is not a style option if you want a PWA. debug and fullJs are how a “quick look” becomes a public SQL dump. src/ and internal_data/ are not public folders. Administrators keep Registered as their primary group.
This is not the configuration mistakes autopsy. That article is the switch you flipped last Tuesday. This one is the baseline you should have set on day one and re-checked after every staff hire. It is not the permissions primer either. Permissions decide who can see a node. This article decides who can become that person.
Official sources: Users → User security (docs.xenforo.com/manual/users/user-security), Setup → Service providers → Two-step verification, group permission Require two-step verification, Setup → PWA setup (HTTPS), config.php options (enableTfa, debug, fullJs), friendly URLs / nginx internals (docs.xenforo.com/manual/configuration/friendly-urls), staff chapter. No third-party WAF product is required. If you already run one at the edge, it is your edge, not a XenForo feature this article will name.
1. Passwords you cannot recover
Official Password storage: XenForo stores each password with a complex one-way hashing algorithm unique to each user. It is impossible to extract the original password from the hash. If a member asks you to “look up my password,” the answer is the lost password link on the login screen. There is no ACP “show password.” There is no email that contains the old secret.
Encourage password managers. Official says so. A 12-character unique password the browser stored is worth more than a reused “Forum2024!” you could have looked up in a worse product.
What this implies for staff process:
- You will never “read back” a password during a support ticket. You will send them through lost password, or you will use security lock (below).
- A database leak of
xf_user_authenticateis still an incident. Unique per-user hashes are not an excuse to leave SQL dumps on the desktop. - After a phpBB import, test a normal user’s login on staging. The importer’s job is to bring the account in. The security model on day two is still “hash + lost password,” not “staff knows the phpBB password.”
2. Login limits after four failures
Official Brute-force attack mitigation: XenForo counts failed logins. After four unsuccessful attempts to enter the correct password, it acts.
Setup → Options → User options → Login limit method — two official choices:
| Method | What happens after 4 failures |
|---|---|
| CAPTCHA | The next attempt must complete a CAPTCHA |
| Lockout | The account is locked out for fifteen minutes |
Pick one. CAPTCHA punishes bots and clumsy humans equally, and it depends on you having a working CAPTCHA type under Setup → Options → Basic board information. Lockout is simpler and gives a real attacker a clock; it also gives a legitimate member a 15-minute wait if they typo’d the new password manager entry.
Two related official controls, same User options group:
- Minimum time between password requests — seconds a user must wait between lost-password mails. This is flood control, not 2FA.
- CAPTCHA on lost password — official: you may force a CAPTCHA on the lost-password form to stop robots.
A board that leaves login limit on CAPTCHA but has no CAPTCHA provider configured has a hole that looks like a feature. Check both screens the same afternoon.
This is not a “block this IP after 3 tries” product. Official does not give you a per-IP ban table on that page. If you need edge rate-limits, that is your web server or your host. Do not invent a XenForo WAF.
3. Two-step verification (2FA)
Official name: two-factor authentication (2FA) or two-step verification. Extra proof at login.
Official methods, as the user-security page names them:
- A code from an app — Authy, Google Authenticator, or the iCloud Password Manager are the examples XenForo prints
- A link in an email sent to the user’s registered address
The list of methods that are enabled on your board is Setup → Service providers → Two-step verification. Open that screen. Disable methods you do not want to support. If email-link 2FA is on, your mail transport must actually deliver. A 2FA email that lands in spam is a lockout.
Members enable 2FA on their own account (front-end: Password and security — community wording). Staff should assume new members will not. That is why the next section exists.
enableTfa is the emergency brake
Official config.php:
$config['enableTfa'] = true;
Setting this false disables two-factor authentication entirely. Official use: you lost access to the ACP because 2FA is on and the device is gone, or you want to prevent any user from employing 2FA. It is a recovery tool and a nuclear option. It is not a “we don’t like 2FA” default. If you used it to get back in, set it back to true (or remove the line) the same hour, then fix the admin’s 2FA methods under their user record.
A config.php that permanently disables 2FA on a public board is a finding, not a preference.
4. Mandate 2FA on Moderating and Administrative
Official sentence: it is a good idea to protect moderator and administrator accounts with 2FA. Official switch:
Groups & permissions → User groups → (Moderating or Administrative) → General permissions → Require two-step verification = Yes
Do both groups. Doing only Administrative leaves every moderator as a password-only account with a moderator bar and the approval queue.
How this behaves, practically:
- The next time that user uses a privileged surface, XenForo requires them to set up 2FA if they have not
- The staff chapter also notes you can block control-panel access until they enable it — the permission above is the documented mandate
- This is a group permission, not a per-user checkbox you will remember to tick on the next hire
Because Administrative and Moderating must be secondary groups (Registered stays primary — see §9), the mandate applies to everyone who has that secondary group. Do not invent a third “Senior admin” primary to “make 2FA stick.” Put the new admin in Administrative and leave the permission Yes.
If a moderator cannot complete app-based 2FA, email-link is the official fallback — again, only if mail works. Do not disable the mandate because one person left their phone at home. Use backup codes the product offers when they enroll, and keep a staff procedure for “lost authenticator” that is security lock + email reset, not “turn off 2FA for the group.”
5. Security lock — two modes
Official: when you suspect a breach or unauthorized access, edit the user and set a Security lock.
Path:
- Admin control panel
- Users → Search for users → edit the account
- Security lock section
- Choose one of the two official values
- Save
| Lock | What the user must do next | When official says to use it |
|---|---|---|
| Locked: User must change password | On next login they enter the existing password and set a new, different one | You want a rotation; you still believe they know the current password |
| Locked: User must reset password | They must reset via a message to the registered email | Official note: use this if the original password may already be compromised or guessed |
The second mode is the incident mode. If an attacker has the password, “must change password” is a form the attacker can complete. “Must reset password” requires the inbox.
After a lock, check two-step verification on that account. If the attacker added their own TOTP, removing it is part of the same edit. Official user-security page does not walk every 2FA-reset control; the ACP user editor is where those methods appear. If you cannot get in as that user, you are the admin — you can clear methods on the record, then require setup again via the group mandate.
Do not confuse security lock with a ban. A ban stops them posting. A lock forces a credential ceremony. You often want both: lock, then ban or disable, then walk sessions.
6. Admins editing admins
Community ACP behaviour (and the brief you should treat as operational fact): when you edit another administrator, XenForo asks you to confirm your password. That is not a bug. It is so a stolen moderator session that somehow reached the user editor cannot quietly rewrite a founder.
If you are prompted for a password while editing a normal member, something else is wrong (you are not who you think, or an add-on is widening the check). Official user-security page does not dedicate a heading to this prompt. Do not disable it. Do not share founder passwords so “anyone on staff can confirm.”
Related: only as many Administrators as you need. A “temporary admin” for a style tweak who stays in the group for a year is a 2FA surface you forgot.
7. HTTPS is a security control, not a badge
Several official features require HTTPS:
- PWA / push — first absolute requirement on Setup → PWA setup: the site must be accessible over HTTPS / SSL. No certificate, no installable app, no push.
- Connected accounts — OAuth redirect URLs are HTTPS; Board URL must match. The Discord article already tripped over this.
- Session cookies on a modern browser that will mark HTTP cookies
Secureor refuse them
A board still on http:// in 2026 is not “fine until we add push.” Mixed content will break embeds. Password managers will warn. Get a certificate, force HTTPS at the vhost, set Board URL to the https:// form, then walk cookies.
This article will not recommend a specific CDN WAF. TLS at the origin (or at the proxy you actually control) is the requirement. If your proxy terminates TLS and talks HTTP to PHP, that is a deployment choice — XenForo still needs to generate HTTPS URLs via Board URL.
8. Debug, full JS, and the other config.php landmines
The mistakes article already printed these. They belong on a security checklist because debug prints SQL to visitors and fullJs ships unminified modules.
Production src/config.php:
$config['debug'] = false;
$config['development']['fullJs'] = false;
Official cache/options docs: debug on a live site slows page generation and prints SQL. That is an information leak, not just a slowdown.
Also official “never on live”: ipv4CidrMatch / ipv6CidrMatch = 0 — a development VPN session bypass. If you do not know why those keys are in your file, delete them.
$config['enableTfa'] — see §3. Leave it enabled.
$config['enableGzip'] is a performance switch, not a security one; it lives in the speed article.
install-lock.php missing is how the installer thinks you are a fresh install — already flagged in the import/backup notes. After install, that lock file must exist. After an import rehearsal you cloned, confirm you did not copy a tree without it onto production.
9. File permissions and nginx internal
XenForo’s web root contains directories that must not be served as static files. Official nginx snippet for friendly URLs is also the official protect these paths snippet (docs.xenforo.com/manual/configuration/friendly-urls). The /xf/ prefix is an example; change it to your install path.
location ^~ /xf/install/data/ {
internal;
}
location ^~ /xf/install/templates/ {
internal;
}
location ^~ /xf/internal_data/ {
internal;
}
location ^~ /xf/library/ { #legacy
internal;
}
location ^~ /xf/src/ {
internal;
}
internal means nginx will not answer a client request for those URLs. PHP can still read the files from disk. That is the point. src/config.php lives under src/. internal_data/ holds attachments, code cache, and other things that are not public routes.
Apache / LiteSpeed: the shipped htaccess.txt → .htaccess is the official deny/rewrite set. If friendly URLs work and you did not delete the .htaccess rules that protect those directories, you are in the default-safe shape. If a host “helpfully” emptied .htaccess, src/config.php may be fetchable. Request https://yourboard.example/src/config.php as a guest after every host move. You want a 403/404, not a PHP source dump, not a blank 200.
Filesystem permissions (community operations, not a single official chmod table on the security page):
- PHP must write
data/andinternal_data/ - PHP must not need world-writable
0777on the whole tree src/config.phpshould not be group-writable by every account on a shared host if you can avoid it
Official does not publish “chmod 640 config.php” as a magic number in the user-security chapter. Your host’s PHP user vs. FTP user is the real constraint. The test is: nginx/Apache cannot serve src/; PHP can read it.
10. Staff hygiene: do not change primary group
Official groups chapter, repeated until it sticks: Unregistered / unconfirmed, Registered, Administrative, Moderating cannot be removed. Every registered human — including admins and moderators — keeps Registered as primary. Administrative and Moderating are secondary.
Changing an admin’s primary to Administrative “so they look like staff” is how you:
- Split the Registered baseline add-ons assume
- Make Analyze permissions unreadable
- Lose the 2FA mandate or the registered permission set when a later promotion removes a group
- Create the exact incident the mistakes article leads with
Security-relevant extras:
- Fewer founders. Super-admin style access is a count, not a compliment.
- 2FA mandate on both staff groups (§4) before you announce the board.
- No shared staff accounts. 2FA and the security lock attach to a person. “mods@” as a login is how you cannot lock one leaver.
- Revoke on exit the same day: remove Administrative / Moderating, remove ACP admin record, invalidate remember-me by forcing a password reset (security lock → must reset), remove them from any user upgrade that granted extra nodes.
Analyze permissions (Groups & permissions → Analyze permissions) is how you confirm a leaver is actually a guest-shaped member.
11. What you should not invent
A XenForo WAF add-on as required kit. Core has login limits, CAPTCHA, 2FA, security lock, spam cleaner (different article), and the report system. Cloudflare / nginx rate-limit / fail2ban are your stack if you want them. This article will not name a paid WAF SKU.
A custom user field for “security level.” 2FA state is not a profile field. The password is not a field. Do not store backup codes in staff_notes.
Disabling lost password because you got flooded. Use the official minimum-time and CAPTCHA controls. Then fix why bots like your form (open registration + no CAPTCHA on guests).
HTTP “just for the LAN.” Staff laptops leave the LAN. Board URL is one string.
A worked hour on a new 2.3 board
- Certificate live. Board URL is
https://…. Guest request to/src/config.phpfails. config.php:debugfalse,fullJsfalse,enableTfanot false.- nginx (or
.htaccess) internals in place forsrc/,internal_data/,install/data,install/templates. - User options: login limit = 15-minute lockout or CAPTCHA with a working provider. Minimum time between password requests set to something other than zero if you have been mailed to death. Lost-password CAPTCHA on.
- Service providers → Two-step verification: TOTP on; email-link on only after mail delivers a test.
- Moderating and Administrative: Require two-step verification = Yes.
- Your own account: TOTP enrolled, backup codes printed to the same place you keep the registrar login — not a public staff forum.
- Create a throwaway test user. Fail login five times. Confirm the limit trips. Reset via lost password. Delete the user.
- Walk Analyze permissions as a fresh admin account that has Administrative secondary and Registered primary.
- If you will offer PWA, finish PWA setup the same week — HTTPS is already done.
Incident card (print this)
When you think an account is not the member any more:
- Security lock → User must reset password (not “change”).
- Clear unexpected 2FA methods on the user record; leave the group mandate on.
- If they have ACP access, remove Administrative / the admin record until they are back.
- Ban or disable if they are posting. Lock and ban are different tools.
- Check Users → Search for users for the same email / same last IP — do not invent a correlation product; use what Search already offers.
- Tell the real human via a channel that is not the compromised inbox if you can (the Discord staff room, a phone). The reset mail still goes to the registered address; if the inbox is the prize, you have an identity problem, not a XenForo toggle.
- After recovery: new unique password, TOTP re-enrolled, backup codes rotated, session list empty.
Checklist
- Passwords are treated as unrecoverable hashes; staff never “look them up”
- Login limit method is set; four failures do something
- Lost-password minimum interval and CAPTCHA are intentional
- Setup → Service providers → Two-step verification matches methods you can support
- Require two-step verification = Yes on Moderating and Administrative
$config['enableTfa']is not left false after a recovery- Staff know the two security lock modes and which one is for a stolen password
- Editing another admin prompts for your password
- HTTPS everywhere; Board URL matches
debugandfullJsare false on production- Guest fetch of
/src/config.phpand/internal_data/is denied - nginx
internal(or Apache equivalent) covers src, internal_data, install/data, install/templates - Staff keep Registered as primary
- No shared staff logins; leavers lose groups the same day
- No invented WAF SKU in the “we are secure” paragraph
What this is not
It is not a penetration test. Official docs do not claim XenForo is “unhackable.” They give you hashes, a four-strike limit, 2FA, a lock, HTTPS requirements, and files that should stay internal.
It is not the moderation toolkit. Spam cleaner, reports, and approvals are content tools. This article is credential and host hygiene.
It is not a privacy-policy writer. Official Help already ships Terms and Privacy pages; forcing re-acceptance is under Terms and rules. Put the legal text there, not in a notice that expires.
Takeaways
- You cannot recover a password. You can lock, reset, or mandate 2FA.
- After four failed logins, official code will CAPTCHA or lock for 15 minutes — if you chose a method.
- Require two-step verification on Moderating and Administrative. Both. Secondary groups. Registered stays primary.
- Must reset password is the lock for a guessed secret. Must change password is a rotation for a person you still trust.
- HTTPS is required for PWA/push and for any OAuth you add later.
debug/fullJsoff.src/andinternal_data/not public. nginxinternalis the documented way.- Security is the baseline. The mistakes article is what happens when you undo it.
Set the mandate before you hire the second moderator. The first stolen password is a process failure, not a surprise.

