Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

Operating a site: caches, cronjobs, backups and repairs

This guide is for the person who keeps an Exponential site running, day to day. It takes you through the routine work of keeping it healthy: looking at the caches and clearing the right one, running cronjobs, switching…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/guides/operating-a-site.md

On this page

Operating a site: caches, cronjobs, backups and repairs

This guide is for the person who keeps an Exponential site running, day to day. It takes you through the routine work of keeping it healthy: looking at the caches and clearing the right one, running cronjobs, switching on the static cache, warming the site, taking a backup, knowing which database you run on, reading the logs, and repairing a site that will not start. Follow it top to bottom. Each part takes a few minutes and ends in something you can check.

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.

Every command is run from the installation root, the directory that contains index.php. When you are logged in as root, add --allow-root-user to the PHP scripts; the scripts refuse to run as root otherwise. In the examples the flag is written out so you can copy them as they are.

Where a topic needs more than a few minutes, the part ends with links to the feature, specification and upgrade pages.

1. Look before you clear: what caches exist

Exponential has about twenty-seven caches: INI, templates, content, translations, image aliases, the HTTP cache, the SQL query cache, and, if you run the Velocity server, its response cache. One command lists them and says how each is cleared.

php bin/php/cache.php list --sizes --allow-root-user

Expected output: one block per cache, for example

  content              on  content                    expiry timestamp content-view-cache set, directory moved aside
                          var/site/cache/content 3 files, 31.5 KB
  template             on  template                   directory removed
                          var/site/cache/template/compiled 227 files, 5.4 MB

The word after the cache name is its tag; one tag clears every cache that carries it. For a one-screen summary of the caches that sit in front of PHP (static, HTTP, query, Velocity, OPcache, APCu):

php bin/php/cache.php status --allow-root-user

Expected output ends with PASS status read and has one line each for static, httpcache, querycache, velocity, precompress and php.

In the administration interface the same caches are under Setup > Cache.

2. Clear the right cache

Clear the smallest thing that fixes the problem. Each of these first, with --dry-run, shows what it would do and changes nothing:

php bin/php/cache.php clear --all --dry-run --allow-root-user

Then clear for real, choosing by what you changed:

You changedCommand
A setting (.ini file)php bin/php/ezcache.php --clear-tag=ini --allow-root-user
A template (.tpl)php bin/php/ezcache.php --clear-tag=template --allow-root-user
Content looks stalephp bin/php/ezcache.php --clear-tag=content --allow-root-user
A new template or extension was addedphp bin/php/ezcache.php --clear-id=template-override --allow-root-user and --clear-id=design_base
Everything, when unsurephp bin/php/ezcache.php --clear-all --allow-root-user

ezcache.php --list-tags and --list-ids print the valid names. --purge physically removes entries instead of only marking them expired, which saves disk space; --expiry="-2 days" with --purge removes only old entries.

The same actions exist per group in exp:cache, which also reports PASS or FAIL and has a --json output for monitoring:

php bin/php/cache.php clear --tag=ini --allow-root-user
php bin/php/cache.php imagealias clear --allow-root-user
php bin/php/cache.php opcache status --allow-root-user

Large caches are moved aside and deleted afterwards, so a clear does not make requests wait; see Cache clears that move directories aside.

The caches in front of PHP

Content is cached again after a clear as soon as the next request renders it. Three caches sit in front of the page rendering and have their own commands:

php bin/php/cache.php httpcache status --allow-root-user     # the HTTP cache: entries, size, hits and misses
php bin/php/cache.php httpcache clear --allow-root-user      # every stored page becomes stale
php bin/php/cache.php velocity status --allow-root-user      # Velocity's response cache
php bin/php/cache.php velocity clear --allow-root-user

Expected output of the status commands ends in PASS and a line such as the HTTP cache is on: 4651 entries, generation 22850. Clear the HTTP and response caches after PHP-FPM or Velocity runs the new code, otherwise a page rendered by the old code in between is stored again. If you run Velocity, one command does all steps in the right order:

./console exp:velocity deploy --dry-run --allow-root-user

Depth: Velocity response cache, HTTP cache, response cache and navigation, cache console, Velocity engines: deploying a PHP change.

3. Run cronjobs

Cronjobs do the work nobody waits for: publishing scheduled content, importing feeds, cleaning baskets, sending newsletters, building sitemaps.

See what exists and which script belongs to which part:

php runcronjobs.php --list --allow-root-user

Expected output: blocks named CronjobSettings: and CronjobPart-frequent:, CronjobPart-infrequent: and so on, each followed by its script files.

Run one part, or one script of a part:

php runcronjobs.php --siteaccess=site frequent --allow-root-user
php runcronjobs.php --siteaccess=site --script=notification.php --allow-root-user

Use the name of your own public siteaccess for --siteaccess. The same list is in the administration at Setup > Cronjobs (/setup/cronjobs), where each part has a card with its schedule, its next and last run, a Run part button and its shell command to copy; the output follows while the job runs, and the page proposes the crontab lines. Show what is installed now:

./console crontab:list

To install the schedule open the crontab for editing (./console crontab:edit) and add a line per part. A common starting point is the frequent part every few minutes and the infrequent part once an hour or a day:

*/5 * * * * cd /path/to/installation && php runcronjobs.php --siteaccess=site frequent --allow-root-user >> var/log/cron-frequent.log 2>&1
17 * * * *  cd /path/to/installation && php runcronjobs.php --siteaccess=site infrequent --allow-root-user >> var/log/cron-infrequent.log 2>&1

Replace /path/to/installation with the root directory. Every part also has a console alias, for example ./console cron:frequent; ./console list cron shows them all. If you run Velocity, its own scheduler can start these tasks without the system cron.

The notification cronjob part has its own guide: Notifications: running them and fixing problems.

The part mailpreferences (also in infrequent) keeps the e-mail consent log and confirmations within their retention time: E-mail preferences: setting them up and running them.

Depth: Cronjobs console, commands, cronjob parts and views as classes, Content jobs (large removals and copies run in batches and are resumed by cron:contentjobs).

4. Turn on the static cache

The static cache stores public pages as plain files that the web server can send without starting Exponential. First see what it would do:

php bin/php/cache.php static status --allow-root-user

Expected output lists the sites that can be generated and ends with PASS and a sentence such as the static cache is not enabled in site.ini [ContentSettings] StaticCache; 3 sites can be generated.

Generate the pages of one site, limited to 500 pages so you can see it work quickly:

php bin/php/makestaticcache.php --site=site --max-pages=500 --max-depth=6 --allow-root-user

Omit --site to generate every public siteaccess. Only siteaccesses that need no login are offered, because their pages are the same for everybody. The same is possible in the browser at Setup > Cache, section Static content cache, with a Stop button. Check the result:

php bin/php/cache.php static status --allow-root-user

The files are counted per site in var/site/static. To make the web server answer from them, set StaticCache=enabled in settings/override/site.ini.append.php block [ContentSettings], clear the INI cache (php bin/php/ezcache.php --clear-tag=ini --allow-root-user), and check that the web server rules of your installation are active. Remove the generated files with php bin/php/cache.php static clear --allow-root-user.

Depth: Static cache generator, static cache defaults.

5. Warm the site (preload)

After a clear or a deploy the first visitor pays for the render. Warm the pages yourself:

php bin/php/preload.php --apply --siteaccess=site --allow-root-user

It requests the section pages first, then follows the links of the site, with PHP's curl extension (no wget needed); one preload runs at a time, and every run is listed on Setup > Preload. To warm every published page, which suits a cron entry every few minutes:

php bin/php/warm.php --allow-root-user >> var/log/cache-warm.log 2>&1

In the browser use the Preload Sites view from the administration: choose the site, a page limit (default 250) and a link depth (default 3), and watch the pages being fetched. Depth: Preloading caches, Preload Sites view, Site cache preloader, HTTP/2 and cache warming.

6. Back up and restore

Exponential keeps state in two places: the database and the files (var/, settings/override/ and settings/siteaccess/, and your own extensions and designs). A backup is both, taken at the same time.

Maintenance mode makes the pair consistent. It answers every visitor with a 503 page while you work:

php bin/php/maintenance.php on --message="Back soon" --until=30m --allow-root-user
php bin/php/maintenance.php status --allow-root-user     # prints: Maintenance is on...

Take the files:

tar czf backup-files.tar.gz var/storage var/log settings/override settings/siteaccess

Take the database with the tool of your database system. Replace USER and DATABASE with your values. -p makes the client ask for the password; never write the password itself on the command line, where it lands in the shell history.

DatabaseBackupRestore
MySQL or MariaDBmysqldump --single-transaction -u USER -p DATABASE > backup.sqlmysql -u USER -p DATABASE < backup.sql
PostgreSQLpg_dump -U USER DATABASE > backup.sqlpsql -U USER DATABASE < backup.sql
SQLitesqlite3 var/storage/sqlite3/exponential.db ".backup backup.db"copy backup.db back with the site in maintenance mode

exponential.db is the file name exp:install uses by default; use the name of your own database file (ls var/storage/sqlite3/). For SQLite never copy the file while the site is busy; .backup is safe. The schema alone, as a portable file, is also available through php bin/php/ezsqldumpschema.php --type=mysql --user=USER DATABASE schema.sql --allow-root-user.

Then switch the site back on and clear the caches:

php bin/php/maintenance.php off --allow-root-user
php bin/php/ezcache.php --clear-all --allow-root-user

Test a restore on another server at least once; a backup you have never restored is a hope. Depth: Maintenance mode.

7. Know your database driver

Which database runs the site is one setting. Read it:

grep -rn "DatabaseImplementation" settings/override settings/siteaccess/*/site.ini.append.php settings/site.ini

Expected output: a line such as DatabaseImplementation=ezmysqli. The values are ezmysqli (MySQL, MariaDB), pgsql or ezpostgresql (PostgreSQL), sqlite3 (one file under var/storage/sqlite3/) and mongodb; Oracle comes with its own extension. The aliases are listed in settings/site.ini block [DatabaseSettings], key ImplementationAlias[].

What to remember:

  • SQLite is the quickest way to start, and its transactions queue for the single write lock (SQLiteTransactionWait, default 25 seconds, kept below Velocity's 30-second request timeout Q.webserver.requestTimeout so the driver can report "database is busy" before the request is cut off; raise both together). For many editors publishing at once use MySQL or PostgreSQL.
  • MySQL and PostgreSQL are the usual production choices.
  • Oracle and MongoDB are supported; check the pages below before choosing them.

For developers: a failed statement's error number is the database's own, in $db->errorNumber() and, with $db->setErrorHandling( eZDB::ERROR_HANDLING_EXCEPTIONS ), as getCode() of the eZDBException thrown, so code can tell a duplicate key from a missing table without reading the message:

DriverDuplicate keyMissing table
MySQL, MariaDB10621146
PostgreSQL (the SQLSTATE, a string)'23505''42P01'
SQLite (the result code)191
Oracle (the ORA number)1942
MongoDB (errorNumber() only; the driver answers false, it throws nothing)11000-
$db = eZDB::instance();
$db->setErrorHandling( eZDB::ERROR_HANDLING_EXCEPTIONS );
try
{
    $db->query( "INSERT INTO my_table ( id ) VALUES ( 1 )" );
}
catch ( eZDBException $e )
{
    if ( $e->getCode() === 1062 || $e->getCode() === '23505' )   // MySQL, PostgreSQL
        { /* the row is there already */ }
}
$db->setErrorHandling( eZDB::ERROR_HANDLING_STANDARD );

The SQLite driver throws inside a transaction; outside one it answers false with errorNumber() set. Upgrade note: PostgreSQL's errorNumber() was 1 for every error and is the SQLSTATE now; with the exception handling, MySQL on PHP 8.1 and later throws eZDBException instead of PHP's mysqli_sql_exception (the standard handling is unchanged). Thanks to Mugo Web, whose LoveStack commit 3d18f47551 carried MySQL's error codes into the exceptions first.

Numbers in the built-in search index

The built-in search engine stores each word of a text with its value as a number, so that a search can filter on numbers. A very large number in a text field (a 20-digit order number, 1e30) no longer breaks indexing on MySQL or PostgreSQL: it is stored as the largest number the column holds, and the object is indexed with all its other words. Each driver knows the range of its integer columns, $db::MAX_INT and $db::MIN_INT (signed 32-bit for MySQL, PostgreSQL and Oracle, signed 64-bit for SQLite and MongoDB); $db->clampInteger( $value ) gives a value within that range, cuts a fraction off as (int) does and gives 0 for anything that is not a number. On for every installation; nothing to do on an upgrade. With thanks to Mugo Web (LoveStack, commit 3774a54ead).

Related objects are indexed with the object that points to them: the searchable fields of an object chosen in an object relation or relation list field are found when you search for the object that holds the field. The relation fields of that related object are left out, so indexing never follows a chain of relations. Exponential has worked this way since 2018 (see relation indexing); LoveStack's commit 5c089f6250 does the same, so a site coming from LoveStack finds the same search results here.

Depth: SQLite database support, SQLite 3 driver, database drivers 16 to 30 September 2026, SQLite and Oracle driver behaviour, MongoDB support.

8. Read the logs

Where to look first:

WhatWhereHow
PHP and application errorsvar/log/error.log, var/log/debug.logtail -n 50 var/log/error.log
PHP deprecations (code that a later PHP version will refuse)var/log/deprecated.logsort var/log/deprecated.log | uniq -c | sort -rn | head
Cronjob outputvar/log/ and, for jobs started from the browser, cronjobs/output.log and cronjobs/error.log in the siteaccess var directorySetup > Cronjobs, section Output
Installervar/log/kickstart.log (passwords masked)tail -n 50 var/log/kickstart.log
Who did what (logins, content changes, permission changes)the audit trailphp bin/php/audit.php status --allow-root-user

The audit trail is a signed, tamper-evident log. Check that it is intact and read the newest events:

php bin/php/audit.php status --allow-root-user
php bin/php/audit.php tail --channel=access --lines=20 --allow-root-user
php bin/php/audit.php verify --allow-root-user

Expected output of status: Audit: enabled and one line per channel (content, access, system, ...) ending in intact. To see more in the page itself while you investigate, set DebugOutput=enabled in settings/override/site.ini.append.php block [DebugSettings], reload the page, and switch it off again; never leave it on in production. Depth: Audit trail, audit event model, debug output improvements.

PHP deprecations and log file sizes

PHP 8 reports a deprecation for every use of something a later PHP version removes, often the same one many times in one page. They have a log of their own, var/log/deprecated.log, with each distinct message written once per request; they are not kept in memory or listed in the debug output, which only counts them (the depr value of the debug bar, and the warning count). For a developer the file is the list of what to fix before the next PHP upgrade; for an administrator a page with debug output on needs less memory.

# settings/override/site.ini.append.php
[DebugSettings]
# enabled (default): deprecations go to deprecated.log; disabled: they are warnings, as before 6.0.15
DeprecatedLog=enabled
# Also write them while debug output is off (production): add the level to the list
AlwaysLog[]=deprecated

Developers write one of their own with eZDebug::writeDeprecated( 'Use newThing() instead', __METHOD__ ).

Every log file (error.log, warning.log, deprecated.log, storage.log, the cronjob logs ...) is rotated when it is larger than site.ini [FileSettings] LogFileMaxSize (default 2097152 bytes, 2 MB) into .1, .2, .3; LogFileRotateCount (default 3) is how many are kept, 0 turns the rotation off. With the defaults a log takes at most 8 MB on disk. The constants EZPUBLISH_LOG_MAX_FILE_SIZE, CUSTOM_LOG_MAX_FILE_SIZE and their *_ROTATE_FILES in config.php still win over these settings; ./console exp:config:list --only-set shows whether config.php sets them, and ./console exp:config:unset EZPUBLISH_LOG_MAX_FILE_SIZE --apply hands the size back to site.ini (see Setting up .htaccess and config.php from the console).

Upgrading: both are on after the upgrade. Log files are rotated at 2 MB instead of 200 KB (set LogFileMaxSize=204800 for the old size), and deprecations no longer appear in warning.log or the debug list (set DeprecatedLog=disabled for the old behavior). Both build on work by Mugo Web in LoveStack (commits e39dde30ff, fbbd333a85, e20570a7ad, b7832d110e), with thanks.

Remove expired sessions and old versions regularly if you do not run the matching cron parts:

php bin/php/ezsessiongc.php --allow-root-user
php bin/php/cleanupversions.php --help --allow-root-user

9. Repair a site that will not start

The most common cause is a missing vendor/ directory: the site shows a page that explains it. Repair it without a shell session, or with one command:

sudo -u WEB_SERVER_USER php bin/php/exprepair.php --create-key

The key is printed once and works once. Open any page of the site, paste the key into the field and press Enter; three steps run (install the libraries, regenerate the autoloads, clear the caches) and the page reopens when they are done. Check the state at any time, and switch the feature off when you do not want it:

php bin/php/exprepair.php --status
php bin/php/exprepair.php --disable

For a site that starts but misbehaves, the usual order is: clear the INI and template caches (part 2), check var/log/error.log (part 8), run php bin/php/checkclasses.php --allow-root-user to find a class PHP refuses to load, and regenerate the autoloads after adding or renaming a class:

php bin/php/ezpgenerateautoloads.php -e --allow-root-user
php bin/php/ezcache.php --clear-all --allow-root-user

When every page fails with a PHP parse error in config.php, the console still starts and says so ("Warning: config.php does not compile: ..."). Fix the line it names, copy back the last backup from var/backup/config/ or var/backup/setup/, or start again from the template:

./console exp:setup:status                       # names the syntax error
./console exp:setup:config --force --apply       # replaces config.php with config.php-RECOMMENDED, backup first

Depth: Repair from the browser, repair (upgrade page), Setting up .htaccess and config.php from the console.

10. A weekly checklist

  1. php bin/php/cache.php status --allow-root-user ends in PASS.
  2. php bin/php/audit.php status --allow-root-user shows every chain intact.
  3. tail -n 50 var/log/error.log shows nothing new that you do not understand.
  4. ./console crontab:list still shows your cron entries.
  5. A backup from the last seven days exists and has been restored at least once on another machine.

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.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x