Joomla Security Hardening, Multi-factor Authentication and Passkeys
|
This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production. This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages. |
This page collects everything an administrator needs to make a Joomla site hard to take over and quick to recover. It was written against Joomla 6.2.x on PHP 8.3+ and checked against the official documentation on 2026-10-30. The running example is the Tallpine Hiking Club site: a handful of editors, a Trail Warden group that sees member-only trail conditions, and a headless "trail map" front end that reads the Web Services API with a token.
Joomla’s security model has layers, and each one is covered below in the order an attacker meets it:
-
Identity: strong passwords, multi-factor authentication (MFA), passkeys and the rule of least privilege.
-
Transport and browser: HTTPS, HSTS and the HTTP Headers plugin (Content-Security-Policy, Referrer-Policy and friends).
-
Abuse resistance: the Proof-of-Work captcha and the MFA and password-reset throttles.
-
Server: file permissions,
configuration.php,.htaccessand restricting the administrator area. -
API: the API token and Basic Auth plugins.
-
Supply chain and patching: the Security Centre, the Vulnerable Extensions List, Automated Core Updates and a monthly routine.
-
Recovery: backups and a written incident-response procedure.
Fresh-install defaults worth knowing
Joomla 6.2 installs with sensible defaults, but they differ from what many older tutorials and books describe. The default plugin states below come from the installation SQL of the 6.2 branch and the plugin manifests.
| Area | Default on a fresh 6.2 install | What to do |
|---|---|---|
Multi-factor authentication |
The Email, Verification Code (TOTP), Web Authentication and YubiKey method plugins are enabled. The Fixed Code method is disabled. |
Keep Fixed Code disabled. Require MFA for every group that can reach the administrator area. |
Passkeys |
The |
Leave it on, and encourage administrators to register a passkey in addition to MFA. |
API authentication |
|
Keep Basic Auth off unless a legacy client forces it. Use tokens. |
HTTP Headers plugin |
Enabled. |
Turn on HSTS once HTTPS works everywhere. Introduce a CSP in report-only mode first. |
Captcha |
The |
Select it as the default captcha if you want spam protection on forms. |
Automated Core Updates |
On for new installs (opt-out in the installer), off for upgraded sites. |
Decide explicitly. See Updating Joomla and Automated Core Updates. |
Password rules |
Minimum length 12. Minimum digits, symbols, upper-case and lower-case letters are all 0. |
Raise the character-class minimums to 1 and, for administrators, rely on a password manager. |
Older books on Joomla describe an earlier generation of these settings. Joomla! 4 Masterclass (2023) targets Joomla 4,
which reached end of life in October 2025, and Developing Extensions for Joomla! 5 (2023) targets 5.0. Both predate the
Proof-of-Work captcha (6.1), the security-severity flag on extension updates (6.2) and Automated Core Updates (5.4 and
later), and both pre-date the move of the end-user documentation to the Joomla! User Guide at guide.joomla.org. The old
wiki at docs.joomla.org marks many of its security pages as obsolete. Pages that mention the Google reCAPTCHA plugin are
out of date as well: the core reCAPTCHA plugin was removed in Joomla 5.0, and the 6.x captcha is the Proof-of-Work plugin
(or a third-party captcha extension).
Multi-factor authentication
Multi-factor authentication has been part of Joomla core since 4.2, where it replaced the earlier two-factor authentication (TFA) plugins. The feature is derived from Akeeba LoginGuard, and the official User Guide says its text was derived from Akeeba’s documentation with permission. An account that has MFA enabled must prove a second factor after the password was accepted.
How the login flow works
After the username and password are checked, a user who has at least one MFA method (or who must set one up) is sent to a captive page. Until a correct second step is provided the user cannot reach any other page of the site. Modules are disabled while the captive page renders, so no module positions appear, but plugins still run. When the second step succeeds the session is flagged as fully authorised and the user is redirected to the URL they originally requested.
There are several practical consequences:
-
The captive page applies to the front end and to the administrator. The template used on the site is chosen by the
captive_templateoption, which defaults to the site’s default template style. -
Any extension that logs a user in outside the normal form must either honour the captive flow or be treated as an MFA bypass. See "Silent logins" below.
-
Throttling is applied to the second step: the options
mfatrycount(default 10 attempts) andmfatrytime(default 1 hour) in the Users component configuration limit repeated failures. The#__user_mfatable storestriesandlast_tryper method for this purpose.
The method plugins
Each MFA method is a plugin in the multifactorauth group, so a method can be disabled site-wide by disabling its plugin
in System > Plugins.
| Method (user-facing name) | Plugin | Notes |
|---|---|---|
Verification Code |
|
Time-based one-time passwords (TOTP): six-digit codes from an authenticator app, a password manager or, in some cases, the browser. The user scans a QR code or types the shared secret once. This is the default recommendation for most staff. |
Authentication Code by Email |
|
Sends a six-digit, time-limited code to the account’s email address. The plugin options are |
Web Authentication |
|
A hardware security key, a platform authenticator (Windows Hello, Touch ID, Face ID, Android biometrics) or a phone, using the W3C WebAuthn standard. A user may register several authenticators. |
YubiKey |
|
Uses a YubiKey one-time-password token. Each user needs their own key. The Yubico validation service is contacted from the server. For new deployments the WebAuthn method covers the same keys with a phishing-resistant protocol. |
Fixed Code |
|
For testing and illustration only. It is disabled by default and must not be enabled on a production site, because the "second factor" is a constant string. |
Additionally, backup codes are generated by com_users itself, not by a method plugin. When the user creates their first
method, Joomla offers to create a set of backup codes. Each code can be used once in place of a second factor and the user
can regenerate the set with the Regenerate Backup Codes button. Backup codes should be printed or stored in a password
manager, never in the same place as the password.
The data lives in the #__user_mfa table (the #__ prefix is replaced by your table prefix). Its columns are id,
user_id, title, method, default, options, created_on, last_used, tries and last_try. The method-specific
secret is in options. Treat a database dump as sensitive for this reason.
Setting MFA up as a user
-
Log in, open User Menu > Edit Account in the administrator (or Edit Your Profile on the site).
-
Open the Multi-factor Authentication tab. The tab is visible only to the account owner. Even a Super User cannot see or edit another person’s methods.
-
Press the + Add … button for the method, scan the QR code or follow the prompts, enter the first code to prove that it works and save. Creating a method turns the feature on for the account.
-
Create backup codes when offered, and store them offline.
-
Add a second method or a second authenticator, so that losing a phone does not lock the account.
An account can hold several methods at once. The official documentation orders the options from most to least secure: WebAuthn as the primary login with MFA as the secondary step, WebAuthn as the only login, username and password with MFA, and finally username and password alone.
Forcing MFA per user group
Under Users > Options > Multi-factor Authentication the administrator controls who must or must not use MFA. The option names below are the keys stored in the Users component parameters.
| Option | Default | Meaning |
|---|---|---|
|
none |
Users in these groups are required to set up MFA. When they log in without a method they are redirected to the setup page and cannot use the rest of the site until they finish. |
|
none |
Users in these groups never see MFA, for example a service account that logs in non-interactively. Use it sparingly, because a group that is both forced and excluded is a configuration mistake that is easy to make with nested groups. |
|
empty |
Module positions that may be shown on the captive and setup pages for each client. |
|
Yes |
Shows the page title on the front-end captive page. |
|
site default |
Template style used to render the captive page on the site. |
|
No, |
Silent logins and MFA, explained next. |
|
No, empty |
Redirect users who have no MFA method to a given URL after login (for example an internal help page). |
|
10 and 1 (hour) |
Throttle on failed second-step attempts. |
For the Tallpine site the policy is: Super Users, Administrators, Editors and the custom Trail Warden group are all forced. Registered members are not forced, but the profile page invites them to add a method. The safest order is to force MFA for Super Users first, confirm that two people can still get in, and only then widen the forced groups.
Silent logins and MFA
Some login flows are "silent": the user is authenticated without typing a password, for example the remember-me cookie
(Authentication - Cookie) or a passkey login. The mfaonsilent switch controls whether those logins still trigger the
MFA captive page, and silentresponses lists the authentication responses that are exempt when mfaonsilent is No. The
default list is cookie, passwordless. This is deliberate: a passkey is already a strong second factor in itself, and a
remember-me cookie is only issued after a previous full login. If your policy says "MFA on every login", set
mfaonsilent to Yes, understanding that remember-me then asks for the second factor on each return visit.
Recovery
Losing every method is the most common MFA support request. In order of preference:
-
Use a backup code.
-
Use another registered method or authenticator.
-
A Super User opens Users > Manage. The list has a Multi-factor Authentication filter and an MFA column that shows which accounts have methods, and the component can turn off all methods of an account (the "Turn Off" action). Ask the user to register a new method straight away.
-
As a last resort, delete the user’s rows from the
#__user_mfatable in the database, with a backup taken first.
Every verification, creation and removal is written to the action log, see privacy and action logs. Because the Joomla documentation notes that the site does not lock accounts, rely on the throttle options and on a firewall or fail2ban for the web server.
Common MFA errors
-
Clock drift: TOTP codes are rejected when the server clock is wrong by more than a few seconds. Enable NTP on the server and in the Docker host.
-
Email codes never arrive: check the mail configuration and the 120 second validity. A slow mailbox makes a code expire.
-
Locked out after changing domain: WebAuthn credentials are bound to the exact domain. Moving staging to production or switching
www.on and off invalidates every registered authenticator. -
An extension or custom login module that bypasses the captive page. Test every login entry point after installing a login-related extension.
-
Fixed Code left enabled after a demo.
Passwordless login with passkeys (WebAuthn)
WebAuthn lets a user sign in with an authenticator, called a passkey, instead of a password. The private key never leaves the authenticator and the site stores only a public key. The username is still typed, because WebAuthn has no identity management and the browser needs the name to find the matching credentials.
Requirements
-
HTTPS with a valid, trusted certificate. WebAuthn only works over HTTPS. A free Let’s Encrypt certificate is enough. If
$live_siteinconfiguration.phpbegins withhttp://, the login button is not shown. See web server, reverse proxy and HTTPS for a proxy that forwards the scheme correctly. -
PHP: the OpenSSL extension, GMP or BCMath (either), and ideally the Sodium library, which enables elliptic-curve cryptography for FIDO2 authenticators.
-
A stable domain. The credential is bound to the domain on which it was registered. Changing the domain (or serving the site on several) requires registering every authenticator again.
-
Browsers: Chromium-based browsers and Firefox since 2019, Safari from iOS and iPadOS 13 and macOS Catalina. Windows Hello needs a TPM enabled in firmware.
The plugins involved
Two different plugins carry the word WebAuthn, and they do different jobs.
| Plugin | Role |
|---|---|
|
Adds the Web Authentication button to the login form and handles passwordless login. Enabled by default, no parameters. |
|
Uses the same kind of authenticator as a second step after a password login. |
An authenticator registered for passwordless login is stored in the #__webauthn_credentials table. The credentials
are encrypted with a key derived from the site secret, as defence in depth against an attacker who can write to that
table but cannot read the configuration file. An attacker who can read configuration.php already controls the site.
Registering and using a passkey
-
Log in once with the password. In the administrator choose User Menu > Edit Account > W3C Web Authentication (WebAuthn) Login and press Add New Authenticator.
-
Follow the browser prompt (touch the key, use the fingerprint, face or device PIN) within about a minute.
-
Register at least two authenticators: a hardware key kept in a drawer and a platform or phone passkey.
-
To log in, type the username, leave the password empty and press the Web Authentication button on the login form.
Users can only manage their own authenticators, not even a Super User can add or remove those of somebody else.
Username and password login keeps working, so keep strong MFA enabled on the account. If the passkey succeeds, the MFA
step is skipped by design (see silentresponses above), because a passkey is already a possession plus a local biometric
or PIN.
Pitfalls
-
No button on the login form: the site is not on valid HTTPS,
$live_siteuseshttp://, or the reverse proxy hides the scheme from PHP. -
"Authenticator not registered": PHP notices or warnings corrupt the JSON response. Set Error Reporting to None in Global Configuration (or look at the response in the browser developer tools).
-
Third-party login modules only show the button when they call the extra login buttons API (
Joomla\CMS\Helper\AuthenticationHelper::getLoginButtons, fed by theonUserLoginButtonsevent). -
Lockout after losing all authenticators or changing domain. The password remains the fallback, which is exactly why that fallback must be protected by MFA.
Older books show the pre-4.2 "two-factor authentication" tab and a separate WebAuthn plugin under Users > Options.
In 4.2 and later these are the multifactorauth plugin group and the passwordless login plugin described here.
Passwords and brute-force controls
Under Users > Options > Password Options the following keys exist (defaults in brackets): minimum_length (12),
minimum_integers, minimum_symbols, minimum_uppercase and minimum_lowercase (all 0), reset_count (10) and
reset_time (1 hour), which limit how many password-reset requests are accepted in the period. The User Guide recommends
setting each character-class minimum to 1. Before Joomla 5.2 some symbols were accepted in a password but were not counted as
symbols during validation. The guide’s older advice of "8 to 14 characters" and monthly password changes is not what
current guidance recommends: prefer long passphrases from a password manager, change them when a compromise is
suspected, and protect the account with MFA.
The HTTP Headers plugin
System - HTTP Headers (plg_system_httpheaders) is a core plugin, enabled by default, that sets response headers from
the backend, so you do not have to edit web server configuration. It has the tabs Basic Settings, Strict-Transport-Security
and Content Security Policy. The official User Guide article was written for Joomla 4 and states that it also applies
to Joomla 5. The defaults below are taken from the plugin manifest on the 6.2 branch.
Basic settings
| Setting (parameter) | Default | Effect |
|---|---|---|
X-Frame Options ( |
Yes |
Sends |
Referrer-Policy ( |
|
Eight choices: |
Cross-Origin-Opener-Policy ( |
|
Choices |
Additional headers ( |
none |
A repeatable list of header name, value and client (site, administrator or both). The header names offered are |
A typical addition for the Tallpine site is Permissions-Policy with the value geolocation=(self), camera=(), microphone=(),
because the trail map uses geolocation and nothing needs the camera or microphone.
Strict-Transport-Security (HSTS)
HSTS tells browsers to use HTTPS only. It is off by default (hsts = 0). When enabled it exposes hsts_maxage (default
31536000 seconds, one year; the minimum accepted is 300), hsts_subdomains and hsts_preload, both off by default.
A safe roll-out avoids locking visitors out of a subdomain that lacks a certificate:
-
Make HTTPS work on the whole site and redirect HTTP to HTTPS at the web server or proxy.
-
Enable HSTS with a short
max-age(for example 300), and test. -
Raise it step by step to one week, one month and finally a year (some guides suggest two years).
-
Add
includeSubDomainsonly when every subdomain, now and later, has a valid certificate. -
Enable
preloadand submit the domain to the browser preload list only when you intend to keep HTTPS permanently: removal from the list is slow.
HSTS is preferable to a bare 301 redirect because, once the browser knows the policy (or the domain is preloaded), the first request is never sent in clear text.
Content-Security-Policy (CSP)
CSP is off by default (contentsecuritypolicy = 0). When turned on, the plugin defaults to report-only
(contentsecuritypolicy_report_only = Yes), which logs violations in the browser console without blocking anything. The
other options are:
-
contentsecuritypolicy_client: apply the policy to the site (default), the administrator or both. -
nonce_enabled: Joomla generates a random nonce for thescriptandstyletags it adds through its API. Use the placeholder{nonce}in a directive, for examplescript-src 'self' {nonce}. The official article notes that core scripts and styles do not carry nonces everywhere. -
script_hashes_enabledandstyle_hashes_enabled: Joomla collects the inline scripts and styles registered through its API and generates SHA hashes. Use{script-hashes}and{style-hashes}in directives. The hashes change whenever the inline code changes. -
strict_dynamic_enabled: scripts trusted by a nonce or hash may load further scripts (this overrides'self'and'unsafe-inline'for those children). -
frame_ancestors_self_enabled(default Yes): addsframe-ancestors 'self'. -
contentsecuritypolicy_values: a repeatable list of directive, value and client. The User Guide describes 21 of the directives, among themdefault-src,script-src,style-src,img-src,connect-src,font-src,object-src,media-src,frame-src,child-src,form-action,frame-ancestors,base-uri,worker-src,manifest-src,report-uriandreport-to.report-uriis deprecated in CSP level 3 in favour ofreport-to, but browser support forreport-tois still uneven.
A starting policy for Tallpine, assuming map tiles from a tile server and no third-party scripts, is entered as separate rows of the directive list. The block below is the resulting header, for reference.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-RANDOM'; style-src 'self' 'nonce-RANDOM'; img-src 'self' data: https://tiles.tallpine.example; connect-src 'self' https://tiles.tallpine.example; frame-ancestors 'self'; base-uri 'self'; form-action 'self'
Strict-Transport-Security: max-age=31536000
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Cross-Origin-Opener-Policy: same-origin
Permissions-Policy: geolocation=(self), camera=(), microphone=()
Source: Joomla User Guide: HTTP Headers
Run the site in report-only mode for at least a week while editors use TinyMCE, the Media Manager and the Smart Search
module, then switch Report-Only off. Typical breakages are inline event handlers, editor iframes, third-party analytics
and fonts. Test the result with an external security-headers scanner. Enabling CSP can change front-end behaviour,
especially with inline code, so keep it separate from other changes in the release.
The Proof-of-Work captcha
Joomla 5.0 removed the reCAPTCHA plugin, which left sites with no core captcha for a release cycle. Joomla 6.1
(released 2026-04-14) brought spam protection back with Captcha - Proof of Work (plg_captcha_powcaptcha). It needs no
account and no external API, which makes it privacy-friendly, and it asks the visitor’s browser to solve a small
computational puzzle in the background so the visitor sees nothing. A bot that submits a form without doing the work is
rejected.
To use it, open System > Global Configuration > Site > Default Captcha and select it, then make sure forms that support a captcha (contact, registration, password reset, and any extension that uses the captcha field) are set to use the default captcha. The plugin parameters are:
| Parameter | Default | Meaning |
|---|---|---|
|
moderate |
|
|
250000 |
Between 10000 and 1000000. Shown only when difficulty is |
|
|
When the puzzle starts: |
|
300 |
How long a solution is valid, in seconds: 60, 300, 600 or 3600. |
Proof of work raises the price of automated spam, it does not identify humans. A determined attacker with real compute can
still pass. Combine it with the contact form’s email rate limits, the registration moderation settings and, for public
forms, a honeypot or moderation queue. Third-party captcha extensions that use the same on…Setup events (for example
hCaptcha or Turnstile integrations) remain an option.
Server hardening
File permissions and ownership
After installation the recommended permissions are 644 for files and 755 for directories. The Web Installer needs some
directories to be writable, and the official guide says to reset them to safer values afterwards. Never leave 777 (or 757)
on a directory or on configuration.php.
# run from the Joomla root as the account that owns the site (not as root)
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
# the web server must write only here
chmod 775 cache administrator/cache tmp administrator/logs
Points that cause trouble:
-
Ownership matters more than modes. On a host where PHP runs as the file owner (suExec, PHP-FPM pools per account), 644 and 755 are enough. When PHP runs as
www-dataand files are owned by a deploy user, grant write access only through group ownership of the writable folders. -
The files PHP must write:
cache,administrator/cache,tmp, the log folder ($log_path),images(for the Media Manager) and, if you install extensions from the web interface, the extension folders. Writing toimagesis also an upload risk: do not allow PHP execution there (see below). -
640 or 750 can make the web server unable to read files when it falls under "other".
-
In containers use a read-only root filesystem with volumes for
images,tmp,cacheand logs. See local development with Docker and the site pages on Docker security and Kubernetes admission control and policy.
Protecting configuration.php
configuration.php holds the database credentials and the site $secret. It is a PHP file, so a request for it returns an
empty page, but anything that lets an attacker read files (a path traversal, a backup left in the web root, a
misconfigured server that serves PHP as text) discloses it. Hardening steps:
-
Make it read-only for everyone but the owner after configuration is finished, for example
chmod 444 configuration.php(or 440 with a matching group). If a Global Configuration save is needed, temporarily make it writable and revert. -
Never commit it to Git. Keep a
configuration.phptemplate with placeholders in the repository and generate the real one at deploy time. -
Prefer to place it outside the web root. The optional
publicfolder layout lets the document root contain only the front controllers and assets (see installation and the public folder and configuration.php and .htaccess). -
Do not leave
configuration.php.bak,configuration.php~,.origor editor swap files next to it. Block those names in the server configuration (below). -
Keep
$debugat 0 and$error_reportingatnoneordefaulton production, so stack traces do not leak paths. -
Set
$force_sslto 2 (whole site) only when HTTPS is certain, and leave$live_siteempty so Joomla derives it from the request.
<?php
// configuration.php (extract) for Tallpine production. Every value shown here is a placeholder.
class JConfig
{
public $offline = false;
public $sitename = 'Tallpine Hiking Club';
public $debug = false;
public $error_reporting = 'none';
public $dbtype = 'mysqli';
public $host = 'db';
public $user = 'tallpine';
public $password = 'CHANGE_ME_DB_PASSWORD';
public $db = 'tallpine';
public $dbprefix = 'tp_';
// Used to derive encryption keys (for example for stored WebAuthn credentials). Generated by the installer.
public $secret = 'CHANGE_ME_GENERATED_BY_INSTALLER';
public $live_site = '';
public $force_ssl = 2;
public $session_handler = 'database';
public $lifetime = 30;
public $log_path = '/var/www/html/administrator/logs';
public $tmp_path = '/var/www/html/tmp';
}
A generated configuration.php is created by the installer, which fills $secret with a random value. Deployment tooling
should treat that file like a secret: in Docker or Kubernetes, mount it from a secret store or render it from environment
variables at container start instead of baking it into an image. The $secret value must be kept stable, because
rotating it invalidates sessions and encrypted data such as stored WebAuthn credentials. The database table prefix
(dbprefix) is a convenience for sharing a database, not a security feature.
Hardening with .htaccess
Joomla ships htaccess.txt in the root. To use it on Apache, rename it to .htaccess (if the host already supplies one,
merge the two). The shipped file already contains:
-
Options -IndexesandIndexIgnore *, which disable directory listings; -
Header always set X-Content-Type-Options "nosniff"; -
a rule that sets
Content-Security-Policy "script-src 'none'"for SVG files, so scripts embedded in an uploaded SVG do not run; -
rewrite rules that reject requests whose query string contains patterns such as
base64_encode(, a script tag, aGLOBALSassignment or a_REQUESTmodification (they answer 403; comment them out if they block legitimate URLs); -
the SEF rewrite to
index.php, routing of/api/toapi/index.phpand anHTTP_AUTHORIZATIONforwarding rule for PHP over FastCGI; -
optional pre-compressed asset serving, which must be disabled if the server already compresses (otherwise browsers show
ERR_CONTENT_DECODING_FAILED).
The shipped file has no HTTPS redirect and no canonical www redirect. The official guide says to add custom redirects in
the marked section. Keep your own edits in .htaccess, never in htaccess.txt, because an update can overwrite the text
file and the file differs between versions, so compare it after every upgrade.
# .htaccess additions for Tallpine (Apache 2.4), placed in the custom section
# 1. Redirect HTTP to HTTPS
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# 2. Never serve backups, dumps or editor leftovers
<FilesMatch "(^#.*#|\.(bak|old|orig|sql|dist|swp|log)|~)$">
Require all denied
</FilesMatch>
# 3. Never execute PHP in upload folders
<IfModule mod_rewrite.c>
RewriteRule ^images/.*\.(php|phtml|phar)$ - [F,L]
</IfModule>
On nginx there is no .htaccess. The equivalent protections go in the server block.
# nginx server block fragment for the same rules
location ~* ^/(images|media|tmp|cache)/.*\.(php|phtml|phar)$ { return 403; }
location ~* \.(bak|old|orig|sql|dist|swp|log)$ { return 403; }
location ~ ^/(configuration\.php|htaccess\.txt|README\.txt|LICENSE\.txt)$ { return 404; }
Restricting access to the administrator area
The most effective step after MFA is to limit who can even reach /administrator. Choose one or more of these:
-
By IP address, with an
.htaccessfile in theadministratorfolder (or the equivalent nginxallowanddenyrules). This suits clubs whose editors share a VPN or a fixed address. -
By HTTP basic authentication in front of
/administratoras a second password, kept separate from the Joomla login. -
By network: serve the administrator only on an internal hostname or through the reverse proxy, or require a corporate identity provider at the proxy.
-
By permissions: only groups with the
core.login.adminpermission (Administrator Login) can log in to the backend. Grant it to the smallest set of groups in Global Configuration. A related permission,core.login.api, controls access to the Web Services API. See users, groups, access levels and ACL.
# administrator/.htaccess (Apache 2.4): only the club office and the VPN may reach the backend
<RequireAny>
Require ip 203.0.113.10
Require ip 198.51.100.0/24
</RequireAny>
Do not restrict /api this way unless the headless front end has a fixed address; use token authentication instead
(next section). Also give each person a personal account, never share a Super User, avoid the username admin, keep the
number of Super Users to two or three, and give everyone else only the role they need: Managers manage content but cannot
install extensions, Administrators manage users, menus and extensions but not Global Configuration, and only Super Users
change Global Configuration and other Super Users.
API authentication defaults
The Web Services API (under /api) authenticates through plugins of the api-authentication group. On a fresh 6.2 install
API Authentication - Token is enabled and API Authentication - Basic Auth is disabled. Two details are easy to
get wrong:
-
The token is sent in an
X-Joomla-Tokenheader together withAccept: application/vnd.api+json. A user can view or reset their token on the profile page, and the matchingUser - Joomla API Tokenplugin (plg_user_token) provides that tab. The account needs thecore.login.apiandcore.login.sitepermissions (Super Users have them). Store tokens in a password manager or secrets vault. -
Basic Auth sends the username and password with every request. Leave it disabled unless a legacy client needs it, and if you must enable it, require HTTPS and give the integration its own low-privilege account. Older tutorials enable Basic Auth to "get started"; with tokens that is no longer necessary.
# The headless trail-map build calls the API with a token read from the environment
curl -sS https://tallpine.example/api/index.php/v1/content/articles \
-H "Accept: application/vnd.api+json" \
-H "X-Joomla-Token: ${TALLPINE_API_TOKEN}"
Source: Joomla Manual: Web Services
Create a dedicated API user in a group with read-only permissions for the content the front end needs, never a Super User token. See the Web Services API and headless Joomla for the full set-up.
Security Centre, Vulnerable Extensions and the monthly update routine
The Joomla Security Centre
The Security Centre at developer.joomla.org/security-centre.html is the project’s official security hub. It explains how
to report a vulnerability to the Joomla Security Strike Team (JSST), which handles the CMS, the Joomla Framework and
*.joomla.org sites, and it lists the Security Announcements feed, available as an RSS feed. Each announcement names
the affected and fixed versions, a CVE number where one exists and the reporter. Recent entries include fixes shipped in
6.1.4 and 5.4.9 on 2026-09-25 and in 6.1.3 and 5.4.8 in August 2026. Subscribe an administrator mailbox or a team channel to
the feed so that nobody depends on remembering to look.
Vulnerable Extensions List
The Vulnerable Extensions List (VEL), linked from the Security Centre and from the Extensions Directory, lists extensions known to contain vulnerabilities. For each extension on your site:
-
Check it against the VEL before installing and again during each monthly review.
-
Remove or replace listed extensions with no patch. Unpublishing a module or deleting a menu item does not protect the site, because the files stay on the server and can be requested directly.
-
Uninstall through the Installer, compare the files with the extension’s manifest, and delete leftovers. Database tables may be kept if you plan to reuse the data.
-
Run the JED Checker on an extension zip that is not on the VEL, as a coarse compliance check.
Since Joomla 6.2 extension developers can mark an update as a security update with a severity from None to Critical. Flagged updates are highlighted under System > Update > Extensions and on the dashboard quick icon, so triage security updates first. See installing, updating and update sites and security in extensions.
Core updates
Joomla releases patch and security fixes for the current minor and, for a limited time, the previous major’s last minor. At the check date 6.2 is current, 5.4.x receives security fixes until 2027-10-12, and 4.x and 3.x are end of life. Core updates are verified with The Update Framework (TUF) since 5.1. Automated Core Updates (5.4 and 6.0 onward) lets joomla.org trigger the update on your site for minor, patch and security releases. They require a publicly reachable site, the Default update channel and Stable minimum stability, and are on for new installs and off for upgraded sites. Major upgrades such as 5.4 to 6.x are always manual. If the server is behind a firewall or HTTP basic authentication, automated updates cannot be triggered, and the staging-first routine below is your only update path.
A monthly routine for the Tallpine site
-
Subscribe to the Security Announcements feed and note any release that fixes a vulnerability. Treat those as out-of-cycle updates, not as part of the monthly slot.
-
Back up files and database, download the archive and test-restore it on staging (see backups and recovery). Akeeba Backup and similar tools are third-party.
-
Update staging first: core, then extensions (Extensions > Manage > Update, with security-flagged updates first), then templates. Clear the cache.
-
Read the post-installation messages on staging and production. They often describe security changes that an upgrade cannot apply automatically.
-
Check the VEL against the list in System > Information.
-
Review users: remove stale accounts, check Super Users and the forced-MFA groups, and review the action log for unusual logins (see privacy and action logs).
-
Review the plugin state: MFA methods enabled, Fixed Code and Basic Auth disabled, HTTP Headers enabled.
-
Update PHP within the supported branch used by the host, after checking extension compatibility. Joomla 6.2 needs PHP 8.3 or newer in the versions listed in requirements and hosting.
-
Promote to production during a quiet period and run a smoke test (login with MFA, passkey login, a contact form with the captcha, an API call with a token).
# Check available core and extension updates and list the accounts (full list on the CLI page)
php cli/joomla.php core:check-updates
php cli/joomla.php update:extensions:check
php cli/joomla.php user:list
Source: Joomla User Guide: Using the CLI
The site:down and site:up commands are also handy for containment during an incident. See
the command-line interface for the full list.
Incident response
Plan before an incident, because the first hour decides how much has to be rebuilt. Typical indicators are an unknown
Super User or extension, modified PHP files, defaced pages, spam mail from the server, new files in images, unexpected
redirects (including only for search-engine visitors), and a hosting-provider abuse notice.
-
Contain. Switch the site offline in Global Configuration (or run
site:down), or block traffic at the firewall or proxy. Take the site off the network if the host is also used for other projects. -
Preserve evidence. Save web server, PHP and Joomla logs (the
$log_pathfolder), the action log and a snapshot of the files before cleaning anything. -
Find how they got in. Check the VEL for each extension, compare core files against a clean download of the same version, search for recently changed PHP files with
find . -name '*.php' -mtime -7, inspect theimages,tmpandcachefolders for scripts, and list Super User accounts and the most recent logins from the action log. -
Clean or rebuild. The safest recovery is a restore of a backup made before the intrusion into a freshly provisioned server, followed by the patches. If you clean in place, you must replace every core file and review every extension, because one overlooked back door brings the intruder back.
-
Rotate secrets. Change the database password, the
$secret, all user passwords that have a path to the backend, and every API token. Regenerate MFA backup codes and re-register passkeys for administrators. Rotate SMTP credentials and any keys stored in extensions. After changing the secret, sessions and stored encrypted data are invalidated. -
Patch and verify. Apply all updates, remove the vulnerable component, re-run the permissions commands and re-check the HTTP headers.
-
Review. Write down the timeline, the cause and one change that would have detected it earlier (alerting on new Super Users, file-change monitoring, an off-site backup). Report new core vulnerabilities to the Joomla Security Strike Team through the Security Centre, not in public forums.
Pulling it together: a hardening checklist
| Control | Where | Since |
|---|---|---|
Forced MFA for Super Users, Administrators, Editors, Trail Wardens |
Users > Options > Multi-factor Authentication |
4.2 |
Fixed Code method disabled; at least two methods or authenticators per administrator; backup codes stored offline |
System > Plugins; profile |
4.2 |
Passkey registered on each administrator account |
Edit Account > WebAuthn Login |
4.2 |
Minimum password length 12 and character-class minimums of 1 |
Users > Options > Password Options |
4.x, symbol handling fixed 5.2 |
HSTS, Referrer-Policy, COOP, X-Frame-Options, CSP in report-only then enforced |
System > Plugins > System - HTTP Headers |
4.0, per-directive options expanded since |
Proof-of-Work captcha selected as the default captcha |
Global Configuration > Site |
6.1 |
644 files, 755 folders, |
Server |
all |
Administrator reachable only from known addresses or VPN |
|
all |
API token authentication on, Basic Auth off, a read-only API user |
System > Plugins; Users |
4.0, defaults confirmed in 6.2 |
Security Announcements feed followed; VEL checked monthly; security-flagged extension updates first |
Security Centre; System > Update |
flag in 6.2 |
Automated Core Updates decided on purpose; tested backups; written incident procedure |
Joomla Update; backup tool |
5.4 and 6.0 |