10. After installing: the operations handbook
The installer leaves you with a site that answers its first request. This chapter turns it into a site you can run: securing the administrator account, getting the site addresses right, scheduling the cronjobs that publish, notify and clean up, sending real mail, knowing which cache to clear and when, keeping the class autoloads and the search index current, reading and rotating the logs, taking backups you have actually restored, watching the site, tuning it for speed, managing extensions and their order, and adding languages. Each part explains what the mechanism does, gives the commands with the settings they read, and ends with a way to check the result. Work through it once in order after the install; afterwards it serves as a reference.
Warning
.htaccess slows down every request. When .htaccess files are enabled (AllowOverride anything but None), let alone
used, Apache looks for them and reads and evaluates them on every request, in every directory of the path, and
page loading times go up drastically (measured). The only real fix is to switch the feature off for
the virtual host: AllowOverride None in the HTTPS virtual host, and the rules in the virtual host's own
configuration as mod_rewrite rules. That is far faster in production speed tests and well worth the time it takes
to optimize a production configuration. Switch over right away: copy the rules of the
example HTTPS virtual host into your virtual host (with the static cache, also
staticcache_offload.conf), set AllowOverride None, run apachectl configtest, reload Apache and
test the site. The same rules for other servers: nginx, LiteSpeed,
Caddy, IIS, Velocity (built in); where virtual host rules go in a control
panel: Plesk, cPanel, DirectAdmin, ISPConfig.
On LiteSpeed or OpenLiteSpeed, the server's own page cache can also answer anonymous visitors and crawlers without PHP: Page caching with LiteSpeed and OpenLiteSpeed.
Contents · Previous: 9. Databases · Next: 11. Upgrading
Contents
- Conventions in this chapter
- 10.1 First login and the administrator password
- 10.2 Siteaccesses and site addresses
- 10.3 Cronjobs
- 10.4 Mail: the file transport and real sending
- 10.5 Notifications and newsletters
- 10.6 Caches
- 10.7 Class autoloads
- 10.8 Search indexing and image handling
- 10.9 Backups and restore
- 10.10 Logs and log rotation
- 10.11 Monitoring and health checks
- 10.12 Performance tuning
- 10.13 Extension management
- 10.14 Multi-language sites
- 10.15 A checklist for the first week
- 10.16 When something does not work
- References
Conventions in this chapter
- Every command runs from the installation root, the directory that holds
index.php. php bin/php/consoleis the command console; many installations link it as./console.php bin/php/console listshows every command,php bin/php/console list cronone namespace.- Scripts refuse to run as
rootunless given--allow-root-user. Run routine work, and above all cron, as the site's own user; then the flag is not needed and files are created with the right owner. The examples carry the flag so they can be copied as they are.bin/php/ezpgenerateautoloads.phphas no root check of its own; it accepts the flag so the same habit works there too (10.7). - Files created by
rootbelong toroot. A cache or log file thatrootcreated and the web server's user cannot overwrite is the most common cause of errors after maintenance work (chapter 12). When you must work asroot, run the command as the site user instead:sudo -u <site user> php .... - Settings are never edited in
settings/*.ini. Overrides go tosettings/override/<file>.ini.append.php(the whole installation) orsettings/siteaccess/<name>/<file>.ini.append.php(one siteaccess).php bin/php/console exp:inireads and writes them in every scope (exp:ini); after a hand edit runphp bin/php/ezcache.php --clear-tag=ini --allow-root-user. siteandadminstand for your public and administration siteaccess names (the defaults ofexp:install).
10.1 First login and the administrator password
Find the address and the password. The installer's summary printed the admin login address (with the default URL access,
/admin/user/loginbelow the site address) and the user,admin. A generated password was shown once and written tovar/log/initial-admin-password, readable by its owner only.Sign in and change the password at once: in the administration, Change password in the user menu, or directly
/admin/user/password. The page lists the requirements beside the field: at least[UserSettings] MinPasswordLengthcharacters (shipped10,settings/site.ini), not a well-known or common password (WeakPasswordCheck, since 6.0.15) and the[PasswordSettings]rules you switched on. Choose a long, random password. The user gets an e-mail that the password was changed (ChangeNotificationMail), which is also the first test of your mail settings (10.4).Remove the password file once you have signed in with the new password:
rm var/log/initial-admin-passwordGive the administrator a real e-mail address (edit the administrator's user object in the administration), so the password reset by mail reaches someone.
A lost password is reset on the command line. As the operating system's root user no other login is needed:
php bin/php/resetuserpassword.php -u admin -g --allow-root-user-u names the user (default admin); -g generates a password (-l <length>, default 16) and prints it;
-p <password> sets one; -a <login> with -ap <password> authorises the reset with another administrator's login
instead, for when you are not the operating system's root. A password given with -p is visible in the process
list and the shell history; prefer -g and change the password after logging in. See
Reset a user password and
Changing your password. Every way to change it, the default per install
method, and how to retire the admin login: Change the administrator password,
The default administrator password.
10.2 Siteaccesses and site addresses
A siteaccess is one way into the installation: a name, a set of settings in settings/siteaccess/<name>/, and a
rule for which requests it answers. The installer creates at least the public siteaccess and the administration.
The rules live in site.ini [SiteAccessSettings]:
| Key | Shipped | Meaning |
|---|---|---|
AvailableSiteAccessList[] | set by the installer | Every siteaccess the installation knows. |
MatchOrder | uri;host;port | The ways a request is matched, tried in order: uri, host, host_uri, port, servervar. |
URIMatchType, URIMatchElement | element, 1 | With uri: the first path element names the siteaccess (/admin/...). |
HostMatchType, HostMatchMapItems[] | map | With host: a host name maps to a siteaccess, HostMatchMapItems[]=www.example.com;site. |
RelatedSiteAccessList[] | Siteaccesses that share one database. | |
ForceVirtualHost | false | true when the web server rewrites every address to index.php, which the shipped rules and Velocity do. Every installation (setup wizard, kickstarter, exp:install, any site package) writes true to settings/override/site.ini.append.php when the server rewrites, so no generated link carries /index.php. Set it back to false only on a server without rewrite rules. |
RemoveSiteAccessIfDefaultAccess | disabled | enabled leaves the default siteaccess's name out of generated links. |
RequireUserLogin | true in site.ini | Overridden per siteaccess; a public site sets it to false. |
In site.ini [SiteSettings] of each siteaccess:
| Key | Meaning |
|---|---|
SiteURL | The site's address without the scheme (www.example.com, or www.example.com/admin for URL access). Used in mails, feeds, sitemaps and by exp:benchmark. |
DefaultAccess | The siteaccess a request gets when no rule matched. |
SiteList[] | The public siteaccesses, used by the administration for previews and links. |
The installer chose one of three layouts (exp:install --access=url|host|port):
- URL (
/admin): one host name for everything; simplest behind any web server. - Host (
admin.example.com): a host name per siteaccess; best for production and for HTTPS certificates per host. - Port (
:8081): one port per siteaccess; useful for local work.
To change from URL to host access after the install, for example:
# settings/override/site.ini.append.php
[SiteAccessSettings]
MatchOrder=host;uri
HostMatchType=map
HostMatchMapItems[]
HostMatchMapItems[]=www.example.com;site
HostMatchMapItems[]=admin.example.com;adminand in settings/siteaccess/site/site.ini.append.php and settings/siteaccess/admin/site.ini.append.php set
[SiteSettings] SiteURL=www.example.com and admin.example.com. Clear the INI cache, add the host names to the web
server (chapter 8) and to DNS, and check which siteaccess an address reaches. The
administration asks for a login and the public site does not, which makes the difference easy to see:
php bin/php/ezcache.php --clear-tag=ini --allow-root-user
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://admin.example.com/
curl -s -o /dev/null -w '%{http_code}\n' https://www.example.com/Expect a redirect to .../user/login (or the login page itself) from the admin host and 200 from the public host.
If both answer the same, the host map does not match: the host name the browser sends must be exactly the one in
HostMatchMapItems[], without a port and in lower case.
Behind a proxy or load balancer. The scheme (http or https) and the host name the visitor asked for are taken
from forwarded headers (X-Forwarded-Proto, X-Forwarded-Host, ...) only when the request comes from an address in
site.ini [HTTPHeaderSettings] TrustedProxies[] (shipped: 127.0.0.1 and ::1, a proxy on the same machine); the
visitor's address is read from X-Forwarded-For only when, in addition, ClientIpByCustomHTTPHeader=X-Forwarded-For
is set. Anyone can send these headers, which is why they are believed only from the proxies you list. A proxy on
another machine must be added, or the site treats HTTPS requests as plain HTTP and records the proxy's address as
every visitor's:
# settings/override/site.ini.append.php
[HTTPHeaderSettings]
TrustedProxies[]
TrustedProxies[]=127.0.0.1
TrustedProxies[]=::1
TrustedProxies[]=10.0.0.5
ClientIpByCustomHTTPHeader=X-Forwarded-ForThe empty TrustedProxies[] line replaces the shipped list, so repeat the loopback entries when you still need them.
See chapter 8 and Trusted proxies.
Settings per site inside an extension, for hosting several sites from one installation, are explained in Per-site settings inside extensions.
10.3 Cronjobs
Cronjobs do the work no visitor waits for: they publish and hide content on schedule, send notifications, resume content jobs, run the audit trail's maintenance, import feeds, index content and clean up. Without them a site works, but scheduled content never appears, notifications never go out and caches and drafts pile up on disk.
How runcronjobs.php works
runcronjobs.php runs parts: named lists of scripts in settings/cronjob.ini. Extensions add their own parts
and directories through [CronjobSettings] ExtensionDirectories[]. Run without a part name, it runs the scripts of
[CronjobSettings] Scripts[], the default part.
php runcronjobs.php --list --allow-root-user # every part and its scripts
php runcronjobs.php -s site frequent --allow-root-user # one part
php runcronjobs.php -s site --script=notification.php --allow-root-user # one script
php runcronjobs.php -s site --allow-root-user # the default part-s (--siteaccess) chooses the siteaccess whose settings the scripts read; use the public siteaccess unless a
part says otherwise (the Oracle parts want the administration). -q suppresses output except errors. In --list
each script appears once per directory searched; that repetition is normal. Every part also has a console alias,
php bin/php/console cron:<part> (for example cron:frequent), and the administration runs any part from
Setup > Cronjobs, where the output follows while it runs and the page proposes crontab lines. Parts listed in
[AdminSettings] ForbiddenParts[] (shipped: cluster_maintenance, unlock) cannot be started from there.
MaxScriptExecutionTime (shipped 43200 seconds) bounds a script's run.
The parts and their scripts
The kernel's parts (settings/cronjob.ini):
| Part | Script | What it does |
|---|---|---|
| default | unpublish.php | Carries out scheduled unpublish actions. |
| default | rssimport.php | Fetches the configured RSS imports into the content tree. |
| default | indexcontent.php | Indexes content whose indexing was delayed ([SearchSettings] DelayedIndexing). |
| default | hide.php | Carries out scheduled hide and unhide actions. |
| default | internal_drafts_cleanup.php | Removes internal drafts older than the configured age. |
frequent | notification.php | Processes pending notification events and sends the messages and digests. |
frequent | workflow.php | Runs pending workflow events and advances stalled workflows. |
frequent | contentjobs.php | Resumes content jobs whose worker died and starts queued ones (large removes and copies). |
frequent | audit.php | The audit trail: sink spools, alert rules, rotation by day, archives, retention, checkpoints. |
frequent | mailbounces.php | Reads the bounce mailbox: hard bounces and complaints go on the suppression list. |
infrequent | basket_cleanup.php | Removes abandoned shop baskets. |
infrequent | linkcheck.php | Checks the internal and external links in published content. |
infrequent | mailpreferences.php | E-mail preference retention: consent log, expired confirmations, old suppression entries. |
cache_cleanup | cachecleanup.php, httpcache_cleanup.php | Removes view cache and cache-block files that can never be served again, and expired entries of the HTTP cache ([CacheCleanupSettings] MaxAge, shipped two days). |
cleanuprss | cleanuprss.php | Trims RSS-imported content to the newest items of each feed; does nothing until content.ini [RSSImportCleanupSettings] names its classes. |
cluster_maintenance | clusterpurge.php | Purges expired files of a database cluster backend. |
unlock | unlock.php | Releases content objects stuck in a locked editing state. |
Single parts exist for contentjobs, audit, notification, mailpreferences and mailbounces, so each can run
on its own schedule. cronjobs/ holds more scripts that no shipped part lists, to be run with --script= or added
to a part of your own: session_gc.php (expired sessions, see below), old_drafts_cleanup.php, trashpurge.php
(empties the trash, or with content.ini [TrashSettings] KeepItemsForDays only what is older),
staticcache_cleanup.php, subtreeexpirycleanup.php and updateviewcount.php.
Active extensions add more parts; php bin/php/console list cron shows them with a description, for example the
newsletter parts (10.5), the sitemap parts of bcgooglesitemaps and
xrowmetadata, and the Oracle parts (chapter 9).
A crontab
Install it in the crontab of the site user (crontab -e -u <site user>, or php bin/php/console crontab:edit;
crontab:list shows the current one):
# m h dom mon dow command
*/5 * * * * cd /path/to/installation && php runcronjobs.php -q -s site frequent >> var/log/cron-frequent.log 2>&1
*/15 * * * * cd /path/to/installation && php runcronjobs.php -q -s site >> var/log/cron-default.log 2>&1
17 * * * * cd /path/to/installation && php runcronjobs.php -q -s site infrequent >> var/log/cron-infrequent.log 2>&1
40 3 * * * cd /path/to/installation && php runcronjobs.php -q -s site cache_cleanup >> var/log/cron-cleanup.log 2>&1
50 3 * * * cd /path/to/installation && php runcronjobs.php -q -s site --script=session_gc.php >> var/log/cron-cleanup.log 2>&1- Use the same PHP binary the web server uses (
/opt/.../phpon hosts with several PHP versions);php -vin the cron environment may differ from your shell's. frequentevery few minutes is what notifications and content jobs expect;infrequenthourly or daily.session_gc.phpremoves sessions older thansite.ini [Session] SessionTimeout(shipped31104000s, 360 days), where the operating system's PHP session cleanup does not run. The same by hand:php bin/php/ezsessiongc.php --allow-root-user.- Rotate the
cron-*.logfiles with the system's logrotate (10.10).
Check that the jobs run: the files grow, Setup > Cronjobs shows the last output, and the notification status command reports the last run (10.5).
Velocity has a scheduler of its own that runs handler functions at times or intervals while the server runs. It does not replace the system cron for these parts out of the box: it needs a handler you write, and it has no catch-up for times missed while the server was down.
10.4 Mail: the file transport and real sending
Exponential sends mail for password changes, registrations, forms, notifications and newsletters. The kernel's
transport is set in site.ini [MailSettings]:
| Key | Shipped | Meaning |
|---|---|---|
Transport | sendmail | sendmail, SMTP or file (TransportAlias[] maps them to eZSendmailTransport, eZSMTPTransport, eZFileTransport). |
TransportServer, TransportPort | empty, 25 | The SMTP server. Secure SMTP is usually 465 (ssl) or 587 (tls). |
TransportConnectionType | empty | empty (no encryption), ssl or tls. |
TransportUser, TransportPassword | empty | SMTP login. |
SenderHost | localhost | The name given in the SMTP EHLO. |
AdminEmail | a placeholder | The administrator's address; the sender when nothing else is set. Change it. |
EmailSender, EmailReplyTo | empty | From and Reply-To unless a template sets them. |
UserAgent | empty | The User-Agent header of every mail (sendmail, SMTP and file), e.g. the name of the portal; ASCII only. Empty: Exponential, Version <version> (sendmail, file) and Apache Zeta Components (SMTP). eZMail::setUserAgent() still sets it for one mail, over SMTP too. |
ContentType, OutputCharset | text/plain, utf-8 | |
SendmailOptions[] | empty | Extra options for sendmail, one per line, e.g. -f with the envelope sender. |
SendmailEnvelopeSender | enabled | disabled leaves out the -f <sender> the sendmail transport adds, for msmtp with --read-envelope-from; a mail without a sender of its own then gets a From header with EmailSender, else AdminEmail. |
DebugSending, DebugReceiverEmail | disabled | enabled sends every mail to one test address instead of its recipients. |
ExcludeHeaders[] | empty | Headers removed before sending over SMTP (add bcc if your server does not hide them). |
The file transport writes every mail as a file instead of sending it: Transport=file puts them in
var/log/mail/ (or the directory of [MailSettings] FileTransportDirectory), one <time>-<random>.mail file per
message. Use it on a test or staging site, to see exactly what would go out without anyone receiving it:
ls -lt var/log/mail/ | headReal sending. For production, use SMTP to your organisation's mail server or a relay service:
# settings/override/site.ini.append.php
[MailSettings]
Transport=SMTP
TransportServer=smtp.example.com
TransportPort=587
TransportConnectionType=tls
TransportUser=<smtp user>
TransportPassword=<smtp password>
SenderHost=www.example.com
AdminEmail=[email protected]
EmailSender=[email protected]Mail that should reach real inboxes needs a sender address of a domain you control, with SPF (and preferably DKIM and DMARC) records that allow the sending server, for every address family the server sends from: if the server sends over IPv6 and the SPF record lists only IPv4 addresses, large providers reject the mail. Send a test by changing a test user's password, or by a form with an e-mail receiver, and read the mail server's log.
Some extensions have their own transport settings. The newsletter extension's are described in
10.5; the e-mail preference system (categories, consent, suppression list, the
bounce mailbox) in E-mail preferences: setting them up and running them.
php bin/php/console exp:mailstatus shows the preference system's state.
10.5 Notifications and newsletters
Notifications
Users subscribe to subtrees; publishing creates events; the notification.php script (in frequent, and as
the part notification) turns events into mails and digests. The settings are in settings/notification.ini.
php runcronjobs.php -s site notification --allow-root-user # run it once by hand
php bin/php/console exp:notificationstatus # pending events, last runs, problems
php bin/php/console exp:notificationrun --dry-run # who would get what, nothing sentThe status page in the administration and the status command flag the usual faults: events waiting with no run recorded (cron is not set up), a failed run, an overdue digest, the file transport still active, a missing sender. The complete procedure, with each fault and its fix, is Notifications: running them and fixing problems.
Newsletters (cjw_newsletter)
When the newsletter extension cjw_newsletter is active, sending is a two-step job run by cron:
| Part | Scripts | What it does |
|---|---|---|
cjw_newsletter | cjw_newsletter_mailqueue_create.php, cjw_newsletter_mailqueue_process.php | Checks pending subscribers, builds the mail queue of the editions due, and sends it. |
cjw_newsletter_mailqueue_create | cjw_newsletter_mailqueue_create.php | Only builds the queue. |
cjw_newsletter_mailqueue_process | cjw_newsletter_mailqueue_process.php | Only sends what waits in the queue. |
cjw_newsletter_mailbox | cjw_newsletter_mailbox.php | Collects the mails of the bounce accounts and parses them (not part of cjw_newsletter). |
*/10 * * * * cd /path/to/installation && php runcronjobs.php -q -s site cjw_newsletter >> var/log/cron-newsletter.log 2>&1
25 * * * * cd /path/to/installation && php runcronjobs.php -q -s site cjw_newsletter_mailbox >> var/log/cron-newsletter.log 2>&1The extension has its own transports, in cjw_newsletter.ini [NewsletterMailSettings] (override in
settings/override/cjw_newsletter.ini.append.php):
| Key | Shipped | Used for |
|---|---|---|
TransportMethodCronjob | file | the newsletter editions the cronjob sends |
TransportMethodPreview | sendmail | test newsletters from the editor |
TransportMethodDirectly | file | subscription confirmations and info mails |
FileTransportMailDir | var/log/mail | where the file transport writes, one .eml file per mail |
SmtpTransportServer, SmtpTransportPort, SmtpTransportUser, SmtpTransportPassword, SmtpTransportConnectionType | the SMTP connection when a method is smtp |
So a fresh installation simulates newsletter sending: the editions and confirmations land as files in
var/log/mail/, which lets you test subscription, double opt-in, sending and unsubscribing without a mail server
and without real recipients. To send for real, set the methods to smtp (with the SmtpTransport* keys) or
sendmail. The directory must be writable by the web server user and by the cron user. Deliverability, throttling
and bounce handling are documented with the extension and in
the cjw_newsletter specification.
10.6 Caches
Exponential caches at many levels, and most "my change does not show" questions are cache questions. The rule is: clear the smallest cache that explains the problem, and clear caches in front of PHP only after PHP runs the new code.
The caches by id and tag
bin/php/ezcache.php clears by id (one cache) or tag (every cache that carries it). The list below is
what --list-ids --verbose prints on a default installation, with the tags from kernel/classes/ezcache.php and the
extensions' [Cache_*] blocks:
| Id | What it holds | Tags | Clear it when |
|---|---|---|---|
content | the content view cache (rendered pages' main area) | content | content looks stale; done automatically on publish |
exphttpcache | the role-aware HTTP cache (when httpcache.ini enables it) | content, template | after a template change, when it is on |
querycache | SQL query results (when querycache.ini enables it) | content, ini | data was changed outside Exponential |
global_ini | the global INI cache | ini | a setting changed |
ini | the INI cache | ini | a setting changed |
codepage | character set mappings | codepage | rarely |
classid | class identifier lookups | content | a class identifier changed |
sortkey | sort key lookups | content | |
urlalias | URL alias wildcard cache | content | URL aliases misbehave |
chartrans | character transformation tables (URL aliases) | i18n | transformation rules changed |
imagealias | generated image variations | image | an alias definition in image.ini changed |
template | compiled templates | template | a .tpl changed while compilation caching is on |
template-block | {cache-block} output | template, content | menus or blocks look stale; a stylesheet changed (the packed link is cached in one) |
template-override | the override rules of override.ini | template | a new template file or override rule (else: a 200 page with an empty content area) |
texttoimage | text-to-image output | template | |
rss_cache | generated feeds | content | |
user_info_cache | user and role information | user | roles or policies changed and a user does not see it |
content_tree_menu | the admin tree menu in the browser | content | |
state_limitations | object state limitations | content | |
content_language | the language list | content | a language was added |
design_base | the list of design directories | template | a design or extension was added |
active_extensions | the list of active extensions | ini | ActiveExtensions changed |
translation | compiled .ts translations | i18n | a translation file changed |
sslzones | SSL zone settings | ini | |
rest, rest-routes | REST application and route caches | content, rest / rest | REST configuration changed |
ezjscore-packer | packed JavaScript and CSS files | content, template | a script or stylesheet of the packer changed |
The tags are therefore content, template, ini, codepage, i18n, image, user and rest.
php bin/php/ezcache.php --list-ids --verbose --allow-root-user
php bin/php/ezcache.php --clear-tag=ini --allow-root-user
php bin/php/ezcache.php --clear-id=template-override,design_base --allow-root-user
php bin/php/ezcache.php --clear-all --allow-root-user
# remove the files for real (saves disk space), only those older than two days:
php bin/php/ezcache.php --clear-id=content --purge --expiry="-2 days" --allow-root-userWithout --purge a clear only moves an expiry timestamp (or moves a directory aside), so it is instant; the
cache_cleanup cron part removes what can never be served again. -s <siteaccess> clears the cache of another
siteaccess's var directory.
Which to clear
| You changed | Clear |
|---|---|
an .ini setting | --clear-tag=ini (not --clear-id=ini, which misses the global INI cache) |
a .tpl file | --clear-tag=template: compiled templates are not compared with their source unless [TemplateSettings] DevelopmentMode=enabled, which live sites should leave off |
a new .tpl file, an override rule, an extension or design | --clear-id=template-override,design_base |
| a stylesheet or script | --clear-id=ezjscore-packer,template-block, then the Velocity cache |
| content looks stale | --clear-tag=content |
| roles and policies | --clear-tag=user |
| an image alias definition | --clear-id=imagealias |
| a translation | --clear-tag=i18n |
| unsure | --clear-all (costs a slower first view of every page afterwards) |
The caches in front of PHP
php bin/php/cache.php (console: exp:cache) does every Setup > Cache action from the command line, ends with
PASS or FAIL, accepts --dry-run and --json, and also reaches the caches ezcache.php does not:
php bin/php/cache.php list --sizes --allow-root-user # every cache, its tag, size and how it is cleared
php bin/php/cache.php status --allow-root-user # static, HTTP, query, Velocity, precompress, PHP caches
php bin/php/cache.php velocity clear --allow-root-user # Velocity's response cache
php bin/php/cache.php httpcache status --allow-root-user
php bin/php/cache.php opcache status --allow-root-userVelocity's response cache answers anonymous repeat requests in the server's parent process, without PHP. After a template or stylesheet change clear it (no restart needed):
php bin/php/console exp:velocity cache clear --apply --allow-root-user
php bin/php/console exp:velocity cache stats --allow-root-userThe order after a code change
A page rendered by the old code between a cache clear and a PHP reload is cached again and looks as if the change had not worked. The order is: INI and template caches, then reload PHP-FPM and restart Velocity, and only then the content, HTTP, template-block and response caches. Under Velocity one command does it, with PASS or FAIL per step and a stop at the first failure:
php bin/php/console exp:velocity deploy --dry-run --allow-root-user # what it would do
php bin/php/console exp:velocity deploy --allow-root-user # do it
php bin/php/console exp:velocity deploy --kernel --allow-root-user # a kernel class was added or renamed--packer also clears the packed scripts and styles; --no-fpm, --no-velocity and --no-autoload skip steps.
The PHP-FPM service it reloads is velocity.ini [DeploySettings] PhpFpmService (auto finds it). See
Velocity engines: deploying a PHP change.
10.7 Class autoloads
Exponential finds classes through generated autoload arrays in autoload/ (the kernel) and var/autoload/ (the
extensions). A class that is not in an array is not found, even if its file is in place: "Class ... not found"
right after adding an extension or a class is the classic symptom.
php bin/php/ezpgenerateautoloads.php -e # extensions: after adding, renaming or removing a class
php bin/php/ezpgenerateautoloads.php -k # the kernel: only when kernel/ or lib/ changed
php bin/php/ezpgenerateautoloads.php -o # kernel overrides
php bin/php/ezpgenerateautoloads.php -e -n # dry run: report, write nothing
php bin/php/ezpgenerateautoloads.php -e -p # with progress outputThe script has no root check; it accepts --allow-root-user (without effect) so that the flag can be given as with
every other script. An option it does not know stops it with exit status 1 and nothing written:
$ php bin/php/ezpgenerateautoloads.php -e --bogus
The referenced parameter '--bogus' is not registered.Older releases printed their usage and exited with 0 on an unknown option, --allow-root-user included, so a
deployment script never noticed that nothing had been generated. If a deployment script of yours ran the generator
and checked nothing, check it once by hand now. Run it as the site user: the arrays it writes in var/autoload/ must
stay writable for the next run.
Which directories it walks is limited by .autoloadignore in the installation root, one directory per line,
anchored at the root, # for comments. The shipped file excludes vendor (Composer resolves those classes itself),
ai, var and .claude (working copies, which would otherwise map every kernel class to a copy). Add a directory
there when it holds PHP files that are not application classes; --exclude does the same for one run.
After regenerating, clear the INI cache and, under Velocity, restart the workers: they keep the arrays they loaded
when they started. exp:velocity deploy (with --kernel for the kernel array) does all three.
php bin/php/console exp:checkclasses loads every declared class and reports those PHP refuses to load.
10.8 Search indexing and image handling
Search
The built-in engine is eZSearchEngine (site.ini [SearchSettings] SearchEngine); it keeps its index in the
database. Content is indexed when it is published, unless DelayedIndexing is enabled (or classbased with
DelayedIndexingClassList[]): then publishing only queues it and the default cron part's indexcontent.php
indexes it later, which makes publishing faster for large objects.
The queue is the table ezpending_actions: index_object reindexes the object, index_moved_node the object and
every object below its main node (for a search engine that keeps paths, such as ezfind's Solr). Each row carries the
time it was queued (created); the cronjob takes the oldest first and removes a row once it is indexed. What an
operation queues with delayed indexing:
| Operation | eZSearchEngine (built in) | An engine with addNodeAssignment() (ezfind) |
|---|---|---|
| Publish a new object or a new version (with or without notification, any number of locations) | one index_object | one index_object |
| Publish a version that moves a location (a parent changed in the edit form) | one index_object | one index_moved_node |
| Move (content/move) | nothing: the index holds no paths | index_moved_node and index_object |
| Add a location, swap | nothing | index_object |
| Hide, reveal | nothing | ezfind indexes at once and queues its own subtree action |
| Move to the trash, delete | removes the object's queued index_object and index_moved_node rows | the same |
Rebuild the whole index after an import, a database move or when search results are wrong:
php bin/php/updatesearchindex.php --clean --allow-root-user # --clean removes the old index firstSearch statistics (LogSearchStats) are disabled by default, because they store every phrase visitors type, which
can be personal data. For a Solr server, php bin/php/console exp:solr controls it once installed and configured.
Images
Uploaded images live in var/<site var dir>/storage/images; their variations (aliases) are generated on first
use. The rules are in settings/image.ini:
[AliasSettings] AliasList[]names the aliases (reference,small,tiny,medium,large,rssand those of your design), each with a block of filters.[ImageConverterSettings] ImageConverters[]lists the converters in order: GD first (PHP's own extension, no external program per image), ImageMagick as the fallback for formats GD cannot read (PSD, TIFF, PDF, WebP) and filters only it has.[ImageMagick] ExecutablePathandExecutable(convert) find the program; check withconvert -version.[OutputSettings] AllowedOutputFormat[](JPEG, PNG, WebP, GIF) lists the formats pages may show.[ImageConverterSettings] LockTimeout(shipped60): seconds a process waits while another generates the same alias; after that the waiting process ends the other one and generates the alias itself.
After changing an alias definition, remove the generated variations so they are made again:
php bin/php/cache.php imagealias clear --allow-root-user10.9 Backups and restore
A backup is the database and the files, taken at the same moment. Files that matter:
| Path | Why |
|---|---|
var/storage and var/<site>/storage | uploaded images and files |
settings/override, settings/siteaccess | the installation's settings (they contain passwords: keep the backup private) |
| your own extensions and designs | if they are not in a repository |
var/storage/sqlite3/ | the database, on SQLite (taken separately, see below) |
var/log | optional, for later investigation |
Caches (var/cache, var/<site>/cache) need no backup: they are regenerated.
Procedure
Put the site in maintenance mode, so no write happens between the database and the file backup. Visitors get a 503 page;
--allow-adminkeeps the administration reachable,--allow-iplets given addresses through:php bin/php/maintenance.php on --message="Back soon" --until=30m --allow-root-user php bin/php/maintenance.php status --allow-root-userBack up the database with the tool of its engine:
Engine Backup Restore SQLite sqlite3 var/storage/sqlite3/exponential.db ".backup '/backup/db.sqlite'"sqlite3 var/storage/sqlite3/exponential.db ".restore '/backup/db.sqlite'"MySQL, MariaDB mysqldump --single-transaction -u USER -p DATABASE > /backup/db.sqlmysql -u USER -p DATABASE < /backup/db.sqlPostgreSQL pg_dump -U USER -Fc DATABASE > /backup/db.dumppg_restore -U USER -d DATABASE --clean /backup/db.dumpMongoDB mongodump --uri="mongodb://USER@HOST/DATABASE" --out=/backup/mongomongorestore --uri="..." --drop /backup/mongoOracle php bin/php/console ext:ezoracle:datapump --export=db.dmp(orexpdp)ext:ezoracle:datapump --import=db.dmp-pmakes the client ask for the password; never write it on the command line. For SQLite read the details in chapter 9: the online backup is safe while the site runs, a plain copy of a busy file is not.Back up the files:
tar czf /backup/files-$(date +%F).tar.gz var/storage settings/override settings/siteaccess(add
var/<site>/storagewhen the site's var directory is separate, and your own extensions).Switch maintenance off:
php bin/php/maintenance.php off --allow-root-user.
Store backups away from the server, and keep several generations. On SQLite and MySQL with InnoDB the database backup is consistent without maintenance mode; maintenance mode makes the database and the files agree.
Restore
Maintenance on. Under Velocity also stop the server (
php bin/php/console exp:velocity stop --allow-root-user), so no worker holds the old database.Restore the database (table above) and unpack the files over the installation; fix the owner and mode (chapter 8).
Clear every cache and regenerate the autoloads:
php bin/php/ezcache.php --clear-all --allow-root-user php bin/php/ezpgenerateautoloads.php -eStart Velocity, clear its response cache, switch maintenance off, and open the front page and the administration.
Test a restore on another machine at least once and after every major upgrade: a backup that was never restored is a hope, not a backup. See Maintenance mode and Operating a site, part 6.
Maintenance windows
For an upgrade, an import or a restore, take the public site offline from Setup > Maintenance (message,
expected duration, who still gets in, a preview and a confirmation) or with php bin/php/maintenance.php on|off|status.
Visitors get a 503 maintenance page, the administration under /admin stays reachable, and deleting
var/maintenance.json ends it whatever else is wrong. The full guide: Maintenance mode.
10.10 Logs and log rotation
File (under var/log/ unless noted) | Written by | Read it for |
|---|---|---|
error.log | the kernel, always (site.ini [DebugSettings] AlwaysLog[]=error) | failures: the first place to look |
warning.log, notice.log, debug.log, strict.log | the kernel, when enabled | investigations |
setup.log, kickstart.log, exp-install-<date>.ini | the installers: one record per installation run (setup.log), the full output of each exp:kickstarter run (kickstart.log, earlier runs in .1 to .9), the configuration exp:install used; passwords masked in all three | install problems |
mail/ | the file transports | the mails that would have gone out |
oracle-slow.log | ezoracle, with SlowQueryThreshold | slow Oracle statements |
cron-*.log | your crontab redirections | cron output |
<site var dir>/log/cronjobs/output.log, error.log | jobs started from Setup > Cronjobs (cronjob.ini [AdminSettings] LogFile, ErrorFile) | browser-started jobs |
| Velocity's logs | the server (velocity.ini [LogSettings]) | the application server |
| the audit trail | audit.php; read with php bin/php/audit.php | who did what |
tail -n 50 var/log/error.log
php bin/php/audit.php status --allow-root-user
php bin/php/audit.php tail --channel=access --lines=20 --allow-root-userBuilt-in rotation. The kernel rotates its own logs: when a file passes 200 KB it becomes .1, the older ones
move up, and three are kept (error.log.1 to error.log.3). Change the limits in config.php, the
installation's PHP configuration file (config.php-RECOMMENDED lists them):
define( 'EZPUBLISH_LOG_MAX_FILE_SIZE', 10485760 ); // 10 MB per file
define( 'EZPUBLISH_LOG_ROTATE_FILES', 10 ); // keep ten; 0 switches built-in rotation off
define( 'CUSTOM_LOG_MAX_FILE_SIZE', 10485760 ); // logs written through eZLog
define( 'CUSTOM_LOG_ROTATE_FILES', 10 );Without an editor, the console writes the same lines (a backup of config.php first, a php -l check before it
lands); ./console exp:setup:config --apply creates config.php from the template when there is none yet:
./console exp:config:set EZPUBLISH_LOG_MAX_FILE_SIZE 10485760 --apply
./console exp:config:set EZPUBLISH_LOG_ROTATE_FILES 10 --apply
./console exp:config:list --only-setSee Setting up .htaccess and config.php from the console.
System rotation. Files the kernel does not write itself (your cron-*.log files) need the system's logrotate.
If you switch the built-in rotation off, logrotate takes over the kernel's logs as well:
# /etc/logrotate.d/exponential
/path/to/installation/var/log/cron-*.log {
weekly
rotate 8
compress
missingok
notifempty
copytruncate
su <site user> <site group>
}copytruncate because the writers keep the file open. Never leave [DebugSettings] DebugOutput=enabled on in
production: it shows internals to visitors and writes a lot.
10.11 Monitoring and health checks
There is no single health URL; combine a check from outside with the status commands inside. Every command below
ends with a recognisable status, and most have --json for a monitoring system.
| Check | Command | Healthy |
|---|---|---|
| Everything at a glance | ./console exp:expfetch --allow-root-user (the system card; --json for a monitor) | no error in the Hints block |
| The site answers | curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://www.example.com/ | 200 and a time you know |
| The administration answers | curl -s -o /dev/null -w '%{http_code}\n' https://www.example.com/admin/user/login | 200 |
| The caches | php bin/php/cache.php status --json --allow-root-user | ends in PASS |
| Velocity | php bin/php/console exp:velocity status --json --allow-root-user | running, workers as configured |
| Notifications | php bin/php/console exp:notificationstatus | no events waiting without a recent run |
| The audit trail | php bin/php/audit.php verify --allow-root-user | exit status 0 (every channel's chain intact; 1 means broken) |
| New errors | tail -n 100 var/log/error.log | nothing you cannot explain |
| Cron | php bin/php/console crontab:list and the cron-*.log dates | entries present, logs recent |
| Disk | df -h /path/to/installation | room for caches, logs and a backup |
| The database | engine specific; SQLite: chapter 9; Oracle: ext:ezoracle:health | |
| Performance | php bin/php/console exp:benchmark --baseline=var/benchmark/base.json | exit status 0 (no regression) |
A small script in the site user's crontab that runs these and mails on a non-zero exit status is enough for a single server. In the administration, Setup > System information shows versions, PHP and the server engine.
10.12 Performance tuning
Measure before and after every change. exp:benchmark requests the front page, the first menu pages, the search
page (when visitors may search) and the admin login page, always in that order, 100 times each after 5 warm-up
requests, 4 at a time, and prints median, p90, p99, minimum, maximum and standard deviation; kernel mode times the
parts of a page in-process (boot, render cold and warm, INI load, content fetch, database round trip, cache write
and read), and micro mode times pure-PHP hot paths without HTTP or a database:
php bin/php/console exp:benchmark --save=var/benchmark/base.json --allow-root-user
# ... make a change ...
php bin/php/console exp:benchmark --baseline=var/benchmark/base.json --threshold=15 --allow-root-user
php bin/php/console exp:benchmark --runs=200 --warmup=10 --format=json --allow-root-user > var/benchmark/run.json
php bin/php/console exp:benchmark kernel --repeat=30 --allow-root-user
php bin/php/console exp:benchmark micro --allow-root-user # no web server, no database
php bin/php/console exp:benchmark --compare=https://www.example.com,https://www.example.com:8443 --allow-root-user
php bin/php/console exp:benchmark --cold --requests=50 --allow-root-user # past the response cachesEvery run records its environment (PHP, OPcache and JIT, Xdebug, CPUs, load, the commit, the date), and a
comparison with a baseline made under another set-up says so. For numbers that compare: the same machine, nothing
else running, OPcache as in production, Xdebug off, and judge two servers only by runs made back to back. The
repository's CI runs the micro mode against tests/benchmark/baseline.json as a warning-only check.
It is polite by default: against another host than this machine more than 1000 requests per URL or 16 at a time
needs --force. The guide Benchmarking Exponential teaches every use with real
output: reading median, p90 and p99, comparing runs fairly, the noise checklist, workflows for a code change, Apache
against Velocity and a deploy, and the CI check; Benchmark: exp:benchmark is the
reference.
The levers, in order of effect
- Serve with Velocity and its response cache (chapter 8). A cached anonymous page is
answered without PHP; persistent workers (
ForkPerRequest=disabled) keep the application, the database connection and the compiled files warm. - Keep the content view cache and template-block cache on (
[ContentSettings] ViewCaching=enabled,[TemplateSettings] TemplateCache=enabled, both shipped). Warm the site after a clear:php bin/php/warm.php --allow-root-userrequests every published page. - OPcache (next part).
- APCu for Velocity's response cache memory tier and the SQL query cache in
sharedmode:velocity.ini [PHPSettings] IniOptions[]=apc.shm_size=256Mis shipped; too small and the two evict each other. - The SQL query cache (
querycache.ini [QueryCacheSettings] Mode=requestorshared), measured on your site (SQL query cache). - The database: chapter 9 (SQLite
ANALYZE, Oracle persistent connections and DRCP).
Leave the static cache off. [ContentSettings] StaticCache=disabled is the shipped value, and the installer
writes it so. With the static cache on, every publish regenerated every affected page on every public siteaccess over
HTTP inside the editor's request, so a publish that changed nothing took seconds instead of a fraction of one.
Velocity's response cache gives the same benefit to visitors without that cost.
OPcache
Under PHP-FPM, enable OPcache in the pool's php.ini (OPcache configuration):
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.interned_strings_buffer=32
; keep timestamp checks on: Exponential rewrites PHP cache files (INI caches,
; compiled templates, expiry.php) under the same name
opcache.validate_timestamps=1
opcache.revalidate_freq=2Never set opcache.validate_timestamps=0 on an Exponential site: a cache file rewritten after a clear would be served
in its old form until PHP restarts.
Under Velocity, the server passes its own PHP options (settings/velocity.ini [PHPSettings] IniOptions[]), and two
of them differ from PHP-FPM for a reason worth understanding. OPcache compares a file's time against the request's
start time, and a Velocity worker is one long request that started when the worker did:
opcache.revalidate_freq=0: with the default of 2 a worker never checked a file again, so a rewritten cache file was served as it was when the worker first compiled it.opcache.file_update_protection=0: OPcache does not keep a script changed less than this many seconds before the request's start. For a worker, every file written after it started (INI caches, override caches, compiled templates, all regenerated after a deploy or clear) counted as "too new" for the worker's whole life and was compiled again on every include: the cache served a few percent of includes and a render took about four times the CPU it takes under PHP-FPM. The protection is not needed because the server reads includes whole and drops the cached copy of a file changed in the last two seconds itself.
The shipped velocity.ini sets both, with opcache.enable_cli=1 (the command-line PHP that runs the server may have
OPcache off system-wide), memory_consumption=256, max_accelerated_files=20000 and interned_strings_buffer=32.
Check with php bin/php/cache.php opcache status --allow-root-user; read
Velocity and the opcode cache for how to profile it. Any PHP
setting that compares against "the request time" behaves as frozen in a Velocity worker; suspect that first when
something works under PHP-FPM and not under Velocity.
10.13 Extension management
Extensions live in extension/<name>/ (more roots with site.ini [ExtensionSettings] AdditionalExtensionDirectories[]). Most arrive through Composer (composer.json); some are copied in.
Activating. An extension does nothing until it is active:
# settings/override/site.ini.append.php
[ExtensionSettings]
ActiveExtensions[]=myextensionActiveAccessExtensions[] in a siteaccess's site.ini.append.php activates one for that siteaccess only (loaded
after the siteaccess is chosen). The administration does the same under Setup > Extensions. After activating:
php bin/php/ezpgenerateautoloads.php -e
php bin/php/ezcache.php --clear-tag=ini --allow-root-user
php bin/php/ezcache.php --clear-id=template-override,design_base,active_extensions --allow-root-userand restart Velocity (or run exp:velocity deploy). If the extension brings database tables, install them as its
documentation says (often share/db_schema.dba with bin/php/ezsqlinsertschema.php).
Order. When two extensions provide the same setting, template or design file, the earlier one in
ActiveExtensions[] wins: each extension's settings directory is prepended in turn, so a later extension ends up
with lower priority. settings/override/ always wins over every extension. A siteaccess's settings win over the
defaults and over the extensions in its ActiveAccessExtensions[], but not over those in ActiveExtensions[], which
are read after it (Configuring, section 2). [ExtensionSettings] ExtensionOrdering=enabled (shipped) also sorts extensions by the dependencies they
declare in extension.xml, which cannot fix every case (it does not reorder across ActiveExtensions and
ActiveAccessExtensions). So:
- a site or theme extension that should override another goes before it;
- an extension that only adds settings to one that ships a full file (for example connection settings for
ezoracle) goes before that extension; - change the order in Setup > Extensions, card Loading order, which saves only
ActiveExtensionsand keeps a backup copy (Extension loading order).
Check the effective value of a setting with php bin/php/console exp:ini get <file>/<Block>/<Key> --allow-root-user
after every change of order. The installed versions, licenses and websites are listed on the about page
(/ezinfo/about), which counts the extensions active in the current siteaccess and those on disk (for example "63 active in this
siteaccess, 85 on disk"), the same figures as the dashboard. Building and releasing extensions of your own:
Extensions.
The job queue and Exponential Intelligence. expqueue and expai are on disk after composer install and
inactive until you switch them on:
ActiveExtensions[]=expqueueinsettings/override/site.ini.append.php(the queue must be active wherever jobs are queued and run), then./console ext:expqueue:install-schema.ActiveAccessExtensions[]=expaiin thesite.ini.append.phpof each admin siteaccess (orActiveExtensions[]=expaifor all), thenphp extension/expai/bin/php/expaischema.php --install -s <admin siteaccess>.- The provider key in
settings/override/expai.ini.append.php(mode 0640, never committed):[AISettings] Enabled=trueandApiKey=..., or a local model (local models). - Autoloads, caches and
exp:velocity deploy --packeras above; the policyexpai/writefor editors. - Cron, both with the admin siteaccess when expai is active there only:
* * * * * ... php runcronjobs.php -q -s <admin siteaccess> expqueueand*/5 * * * * ... php runcronjobs.php -q -s <admin siteaccess> expai.
Details: Exponential Intelligence, the job queue.
10.14 Multi-language sites
Content languages and interface languages are separate:
- Content languages are the languages objects are translated into (the guide). Add one in the administration under
Setup > Languages (the
content/translationsview); afterwards editors can translate objects into it. - The interface language of a siteaccess is
site.ini [RegionalSettings] Locale(shippedeng-GB); an administrator can choose their own in the user preferences.
Per siteaccess, [RegionalSettings] decides what a visitor sees:
# settings/siteaccess/site_de/site.ini.append.php
[RegionalSettings]
Locale=ger-DE
ContentObjectLocale=ger-DE
# the languages shown, in order of preference; the first that exists is used
SiteLanguageList[]
SiteLanguageList[]=ger-DE
SiteLanguageList[]=eng-GB
# disabled: objects in none of these languages are not shown
ShowUntranslatedObjects=disabled
TextTranslation=enabledA typical multi-language site has one public siteaccess per language (site_en, site_de), matched by URL element or
host name (10.2), and lists them all in RelatedSiteAccessList[] because they
share one database. Do not change ContentObjectLocale of an existing site lightly: objects that do not exist in the
new default language disappear from listings. At install time, give every language in --languages= (or
Languages[] in kickstart.ini): a site package whose content uses a locale that is not installed shows pages
without images or sub-items.
Interface translations: php bin/php/ezchecktranslation.php ger-DE --allow-root-user reports how complete one is,
and after a change to a .ts file clear --clear-tag=i18n. See
Translations and languages.
10.15 A checklist for the first week
- The administrator password is changed,
var/log/initial-admin-passwordis gone, the admin's e-mail is real. AdminEmailand the mail transport are set; a password-change mail arrived in a real inbox.- The crontab runs
frequent, the default part,infrequentandcache_cleanup; the logs grow. php bin/php/cache.php status --allow-root-userends inPASS.- A backup was taken and restored on another machine.
var/log/error.logholds nothing you cannot explain.- A benchmark baseline is saved in
var/benchmark/. - Logs rotate (built-in limits in
config.php, see./console exp:config:list --only-set; logrotate for cron logs). - Behind a proxy on another machine: its address is in
TrustedProxies[], and HTTPS pages link tohttps://. ./console exp:expfetch --allow-root-usershows the expected versions and engine, and its Hints block has noerror(The system card).
10.16 When something does not work
The faults that show up most often in the first weeks, with the part of this chapter that explains them. The full list is in chapter 12.
| Symptom | Likely cause | See |
|---|---|---|
| Scheduled content never appears, notifications never arrive | the crontab is missing or runs as another user or PHP | 10.3 |
| No mail arrives, and nothing is in the mail server's log | Transport=file: the mail is in var/log/mail/ | 10.4 |
| Mail is rejected by large providers | the sender's domain has no SPF record for this server, or the server sends over IPv6 and SPF lists only IPv4 | 10.4 |
| A setting change has no effect | the INI cache, or Velocity workers that still hold the old settings | 10.6 |
| "Class ... not found" after adding an extension | the extension autoload array was not regenerated | 10.7 |
| A new template file gives a page with an empty content area | the template override cache | 10.6 |
Links on an HTTPS site point to http:// | the proxy in front is not in TrustedProxies[] | 10.2 |
| A change works for a few reloads, then the old page returns | a cache in front of PHP was cleared before PHP ran the new code | 10.6, "The order after a code change" |
| Errors about files that cannot be written after maintenance work | a script ran as root and left root-owned files in var/ | chapter 12 |
On a public demo (environment variable EXP_DEMO_MODE=1) the steps above about changing the administrator password do not apply: the published password is meant to stay, and in demo mode it cannot be changed or reset. Instead check that the Demo mode banner shows in the administration and that var/log/security.log has demo/mode lines for the web server and cron (Demo mode, Demo mode problems).
References
In this repository:
- Operating a site: caches, cronjobs, static cache, preload, backups, logs, repairs.
- Getting started, Deploying, Security and audit, Extensions, Upgrading, the learning path.
- Notifications: running them and fixing problems, Notifications specification, E-mail preferences for administrators, cjw_newsletter specification.
- Cronjobs console, Commands, cronjob parts and views as classes, Content jobs, Velocity scheduler.
- Cache console, Cache clears that move directories aside, HTTP cache, SQL query cache, Velocity response cache, Static cache defaults, Preload Sites view.
- Velocity engines and exp:velocity deploy, Velocity persistent-worker server, Velocity engine settings, Velocity and the opcode cache.
- Benchmark, Maintenance mode, Reset a user password, Changing your password, Audit trail.
- Trusted proxies, exp:ini, Setting up .htaccess and config.php from the console, Extension loading order, Additional extension directories, Per-site settings inside extensions, Translations and languages.
- Repairing an installation, Glossary.
- The settings themselves:
settings/site.ini,settings/cronjob.ini,settings/image.ini,settings/velocity.ini,settings/querycache.ini,.autoloadignore,config.php-RECOMMENDED.
External:
- PHP: OPcache configuration (including
opcache.file_update_protectionandopcache.revalidate_freq), APCu, session garbage collection. - crontab(5), logrotate(8).
- Mail: SPF (RFC 7208), DKIM (RFC 6376), DMARC (RFC 9989, which replaced RFC 7489).
- Databases: the references of chapter 9.
Contents · Previous: 9. Databases · Next: 11. Upgrading
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.