11. Upgrading
This chapter brings an existing installation to the current Exponential 6.0 line: from an earlier 6.0.x, from 5.4 or
5.90, or from the 4.x and older 5.x lines. It explains how to find the version you run, the one rule that keeps an
upgrade safe (back up, then never run the installer over an existing site), the database update files in
update/database/ for each engine and how to apply them, the data repair scripts in update/common/scripts/, the
console commands that check the result, and the release notes to read for each period you pass. It summarises and
extends the Upgrading guide, which remains the step-by-step procedure.
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: 10. After installing · Next: 12. Troubleshooting
11.1 Before you start
Never run an installer over an existing site. The setup wizard, the kickstarter and exp:install create a new
database; with DatabaseAction=remove (the default of exp:install) they empty the one they are pointed at. An
upgrade replaces the code and updates the existing database in place. exp:install refuses to run where
settings/override/site.ini.append.php already names a database ("This directory already holds an installation"),
and the kickstarter refuses to run its CreateSites step without --force ("The CreateSites step will modify the
database and site settings."), whatever the directory holds. Do not add --force to get past either on a live site.
Who runs the commands. The commands in this chapter are written as the site's own user runs them. Run as root,
the scripts refuse to start unless --allow-root-user is added, and files they create in var/ belong to root
afterwards, which the web server cannot overwrite (chapter 12).
Prefer sudo -u <site user> php ....
Back up first, every time. The database step is the one that cannot be undone by putting files back.
mysqldump -u USER -p DATABASE > backup-before-upgrade.sql # MySQL or MariaDB
pg_dump -U USER DATABASE > backup-before-upgrade.sql # PostgreSQL
sqlite3 var/storage/sqlite3/exponential.db ".backup backup-before-upgrade.db" # SQLite, online backup
tar czf backup-var-and-settings.tgz var settings/override settings/siteaccess extension designFor SQLite use the file name your installation has ([DatabaseSettings] Database in
settings/override/site.ini.append.php); .backup is SQLite's online backup and is safe while the site runs, a plain
cp of a busy database file is not. Restore the backup on a scratch machine once: it is the only proof that it
works.
Take the site offline for the database step.
php bin/php/maintenance.php on --message="Upgrade in progress" --until=30m
php bin/php/maintenance.php status
php bin/php/maintenance.php offWhile maintenance mode is on, every page request is answered with the maintenance page (HTTP 503, never cached);
images, styles and scripts are still served. --allow-ip= and --allow-admin let you in to check. See
Maintenance mode.
11.2 Which version do you run?
The code and the database each carry a version, and after a correct upgrade they agree.
| Where | How | Example |
|---|---|---|
| The code | php bin/php/console --version (reads lib/version.php) | console (Exponential) 6.0.15stable |
| The database | the rows ezpublish-version and ezpublish-release of table ezsite_data | 6.0.14, 1 |
| The admin | Setup > System information |
mysql -u USER -p DATABASE -e "SELECT name, value FROM ezsite_data WHERE name LIKE 'ezpublish-%';"
psql -U USER DATABASE -c "SELECT name, value FROM ezsite_data WHERE name LIKE 'ezpublish-%';"
sqlite3 var/storage/sqlite3/exponential.db "SELECT name, value FROM ezsite_data WHERE name LIKE 'ezpublish-%';"The row names keep their historic spelling on purpose; they are identifiers, and every update file writes to them.
On a site that is current, the query prints (MySQL's layout):
+--------------------+--------------+
| name | value |
+--------------------+--------------+
| ezpublish-release | 1 |
| ezpublish-version | 6.0.15stable |
+--------------------+--------------+The 6.0.0 to 6.0.15 file writes 6.0.15stable; the earlier files write a number such as 6.0.0 (the 5.3 to 5.4
file writes 5.4.0alpha1, as it did upstream).
A version row that does not match the code means an update file is still missing, and the code may already use a
column the database does not have yet: find the step from the table below and apply what is missing before the site
is used.
| You run | Path | Section |
|---|---|---|
| 3.10 up to 5.3 | the old chain to 5.4, then to 6.0.0, then to today | 11.4, 11.5, 11.6 |
| 5.4.0, or 5.90 (2017.08 and later) | to 6.0.0, then to today | 11.5, 11.6 |
| 6.0.0 to 6.0.14, or an earlier checkout of the 6.0.15 line | to today | 11.6 |
This chapter is the update chain itself. For the practical side of moving a whole site, the migration chapters go further: 14. from the 4.x line, 15. from the 5.x legacy stack, 16. from eZ Platform and Ibexa, and the lookup tables in 17. Migration reference.
11.3 The update files
update/database/<engine>/<version>/ holds one SQL file per step. Each file takes a database from one version
to the next, and the chain must be walked in order, every file once.
By engine
| Engine | Directory | Steps available |
|---|---|---|
| MySQL or MariaDB | update/database/mysql/ | 3.10 → 4.0 → 4.1 → ... → 5.4, then 6.0/dbupdate-5.4.0-6.0.0.sql, then 6.0/dbupdate-6.0.0-6.0.15.sql |
| PostgreSQL | update/database/postgresql/ | the same chain; the 5.4 step is 6.0/dbupdate-5.4-to-6.0.sql, then 6.0/dbupdate-6.0.0-6.0.15.sql |
| SQLite | update/database/sqlite/ | only 6.0/dbupdate-6.0.0-6.0.15.sql: SQLite support starts with 6.0.1 |
| Oracle | extension/ezoracle/update/database/ (in the ezoracle extension): ezpublish/ for the kernel chain, ezoracle/ for the driver's own tables | the kernel chain up to 5.3 |
| MongoDB | none | the MongoDB driver arrived in 6.0.14; the audit tables of 6.0.15 are created by createaudittables.php (below); the trash fields trashed_by and trashed_via need no update (trash) |
The 4.0 to 5.4 chain (MySQL file names; PostgreSQL has the same names)
| From | To | File |
|---|---|---|
| 3.10 | 4.0 | 4.0/dbupdate-3.10.0-to-4.0.0.sql |
| 4.0 | 4.1 | 4.1/dbupdate-4.0.0-to-4.1.0.sql |
| 4.1 | 4.2 | 4.2/dbupdate-4.1.0-to-4.2.0.sql |
| 4.2 | 4.3 | 4.3/dbupdate-4.2.0-to-4.3.0.sql (+ dbupdate-cluster-4.2.0-to-4.3.0.sql on MySQL) |
| 4.3 | 4.4 | 4.4/dbupdate-4.3.0-to-4.4.0.sql |
| 4.4 | 4.5 | 4.5/dbupdate-4.4.0-to-4.5.0.sql |
| 4.5 | 4.6 | 4.6/dbupdate-4.5.0-to-4.6.0.sql |
| 4.6 | 4.7 | 4.7/dbupdate-4.6.0-to-4.7.0.sql (+ dbupdate-cluster-4.6.0-to-4.7.0.sql on MySQL) |
| 4.7 | 5.0 | 5.0/dbupdate-4.7.0-to-5.0.0.sql |
| 5.0 | 5.1 | 5.1/dbupdate-5.0.0-to-5.1.0.sql |
| 5.1 | 5.2 | 5.2/dbupdate-5.1.0-to-5.2.0.sql (+ dbupdate-cluster-5.1.0-to-5.2.0.sql on MySQL) |
| 5.2 | 5.3 | 5.3/dbupdate-5.2.0-to-5.3.0.sql |
| 5.3 | 5.4 | 5.4/dbupdate-5.3.0-to-5.4.0.sql (+ dbupdate-cluster-5.3.0-to-5.4.0.sql on MySQL) |
Rules
- In order, each once. If a statement fails because a column or table already exists, the file was applied before: stop and compare with the backup.
- Cluster files. If the installation stores files in the database cluster (DFS or the database file handler),
also apply the
dbupdate-cluster-...file of the same step (MySQL: 4.3, 4.7, 5.2, 5.4). unstable/directories hold the files of pre-releases (alpha, beta, rc). Do not apply them to a site that ran a final release.6.12/,7.2/and7.3/are not steps of this chain; never apply them as files. Their schema changes are in the 6.0 files: see below.- A file that fails half way. Restore the backup and apply the file again from the start; do not repair by hand and re-run.
- Check the chain itself.
php bin/php/console exp:checkdbfiles --no-verify-branchesverifies the update files against the upgrade path, every directory from 4.0 to 7.3 on every engine, and reports missing (!) or stray (?) files. Without--no-verify-branchesit also tries to compare 4.3 to 5.4 with the retired SVN repository of the old vendor, which fails with oneCline.
Apply a file with the client of the database:
mysql -u USER -p DATABASE < update/database/mysql/4.1/dbupdate-4.0.0-to-4.1.0.sql
psql -U USER -d DATABASE -f update/database/postgresql/4.1/dbupdate-4.0.0-to-4.1.0.sql
sqlite3 var/storage/sqlite3/exponential.db < update/database/sqlite/6.0/dbupdate-6.0.0-6.0.15.sqlThe 6.12, 7.2 and 7.3 directories
These three directories come from the upstream legacy kernel of 2016 to 2018, the code Exponential 6.0 is built on.
In those years its update files were numbered after the version of the Symfony kernel the legacy kernel ran beside,
not after a legacy release, and each file records that number in ezsite_data. Exponential kept them unchanged
apart from the SET default_storage_engine line. They are not steps between 5.4 and 6.0.
| File | Engines | From | What it does |
|---|---|---|---|
6.12/dbupdate-5.4.0-to-6.12.0.sql | MySQL, PostgreSQL | February 2016, password security (EZP-24744) | records 6.12.0; widens ezuser.password_hash to 255 |
7.2/dbupdate-6.13.0-to-7.2.0.sql | PostgreSQL | June 2018, sequence names (EZP-28706) | records 7.2.0; renames every sequence from <table>_s to <table>_<column>_seq and points each column default at the new name |
7.3/dbupdate-7.2.0-to-7.3.0.sql | MySQL, PostgreSQL | October 2018, trash date (EZP-28881) | records 7.3.0; adds ezcontentobject_trash.trashed |
Do not apply them as files to a database on the Exponential path. Each would replace the version row with a
number that is not an Exponential version, and 7.2/ starts from 6.13.0, a version no Exponential database has.
Their schema changes are in the 6.0 files. The 6.0 kernel was built after all three changes and uses their schema; since October 2026 the 6.0 files give a database that comes from 5.4 (or 5.90) all of them, so nothing needs to be run by hand:
ezuser.password_hashwidened to 255 (11.5, step 2).ezcontentobject_trash.trashed, without which moving content to the trash fails: MySQL and PostgreSQL, same definition as the7.3/file, added only when the column is missing.- PostgreSQL only, the sequence names: the 6.0 kernel reads the last inserted id from
<table>_<column>_seq, so on a database that still has the<table>_ssequences of 5.4 every insert that needs its new id fails. The same 88 sequences as the7.2/file are renamed, each only when the old name exists and the new one is free, and each column default is pointed at the new name when it does not name it yet.
All three are in the 5.4 to 6.0.0 file and, for a site that reached 6.0 with an older copy of it, again in the 6.0.0
to 6.0.15 file. On a database that already has a change they leave it as it is, so they are harmless on a database
installed by Exponential 6.0 or one that ran those upstream releases (its version row reads 6.12.0, 6.13.0,
7.2.0 or 7.3.0); such a database continues with 11.6. SQLite needs none of them:
its schema has always had the column and it has no sequences.
Per-database notes
| Engine | Note |
|---|---|
| MySQL or MariaDB | 6.0/dbupdate-5.4.0-6.0.0.sql, like 6.12/, 7.3/ and the old chain's files, starts with SET default_storage_engine=InnoDB;, which MySQL (from 5.5.3) and MariaDB accept: apply the files whole. Copies from before October 2026 started with SET storage_engine=InnoDB;, a spelling MySQL removed in 5.7.5 and MariaDB in 12.0; take the current files. 6.0/dbupdate-6.0.0-6.0.15.sql has no such line, because each of its tables names its engine. |
| MySQL or MariaDB | Tables must be UTF-8; php bin/php/ezconvertdbcharset.php converts an old database. php bin/php/ezconvertmysqltabletype.php --list lists the table types and --newtype=InnoDB converts them. |
| PostgreSQL | The 5.4 step is 6.0/dbupdate-5.4-to-6.0.sql: the version rows, the wider password_hash, the trashed column and the sequence renames (above). Its guarded steps are DO blocks, which need PostgreSQL 9.0 or newer. The digest function of pgcrypto must exist in the database. |
| SQLite | Each ALTER TABLE adds one column, because SQLite takes only one per statement, and SQLite has no ADD COLUMN IF NOT EXISTS: on a second run, every ALTER TABLE for a column that is already there fails ("duplicate column name"). Run the file once, after SQLite's online backup. |
| Oracle | The ezoracle extension carries its own update files up to 5.3; apply them with your Oracle client. The 6.0.15 audit tables come from createaudittables.php. |
| MongoDB | No SQL files. createaudittables.php creates the audit index collections through the driver's schema handler. The trash documents get trashed_by and trashed_via when written, and an older one reads as their defaults, so movetrashrecords.php runs as on the other engines. |
11.4 From 3.10 to 5.3: the old chain
- Put the new code in place (as in 11.6, step 1).
- Apply the update files of the chain in 11.3, from your version up to 5.4, each once.
- Run the data repair scripts of every version you pass (below), from the installation root.
- Read the release notes of each version you pass: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 5.0, 5.1, 5.2, 5.3, 5.4.
- Read the notes of the 5.90 line, which prepared the move to PHP 7 and a longer password rule: 5.90 overview, PHP 7, password length, relation indexing.
- Continue with 11.5.
The data repair scripts
update/common/scripts/<version>/ holds PHP scripts that repair or convert data for a step. Run the ones of the
versions you pass, from the installation root, after that version's SQL file, for example:
php update/common/scripts/4.1/updateimagesystem.php --help
php update/common/scripts/4.1/updateimagesystem.phpRead the first lines of a script, or its --help, before you run it. The scripts and what they say they do:
| Version | Script | Purpose |
|---|---|---|
| 4.0 | updatebinaryfile.php, updatetipafriendpolicy.php, updatevatcountries.php | binary file attributes, the tip-a-friend policy, VAT countries |
| 4.1 | addlockstategroup.php, correctxmlalign.php, fixclassremoteid.php, fixezurlobjectlinks.php, fixnoderemoteid.php, fixobjectremoteid.php, initurlaliasmlid.php, updateimagesystem.php | object states, XML alignment, remote ids, URL links, multi-language URL aliases, the image system |
| 4.2 | fixorphanimages.php | orphaned image files |
| 4.3 | updatenodeassignment.php | node assignments |
| 4.4, 4.5 | updatesectionidentifier.php | section identifiers |
| 4.6 | removetrashedimages.php, updateordernumber.php | images of trashed content, web shop order numbers |
| 5.0 | deduplicatecontentstategrouplanguage.php, disablesuspicioususers.php, restorexmlrelations.php | duplicate state group translations, disabling accounts whose login contains < or >, XML relations |
| 5.1 | fiximagesoutsidevardir.php | references to images outside VarDir |
| 5.2 | cleanupdfscache.php | cache records in the DFS storage table |
| 5.3 | recreateimagesreferences.php, updatenodeassignmentparentremoteids.php | missing image references, parent_remote_id of node assignments |
| 5.4 | cleanuntranslatablerelations.php, cleanupfieldvaluerelations.php, fixremovedezurlobjectlinks.php, fixtrashedimagereferences.php | stale relations, links and image references |
| 6.0 | createaudittables.php, movetrashrecords.php | the audit index tables of 6.0.15 (see 11.6), who trashed what from trashed.json into the trash rows |
| any | cleanup.php, updatecontentobjectname.php, updatenbxmlcontents.php | general clean-up, object names, non-breaking space encoding in XML content |
Coming from a 5.x site on the Symfony stack
A 5.x site that ran the legacy kernel inside a Symfony project kept it in ezpublish_legacy/; the installer plugin
still installs it there when the project's composer.json sets extra.symfony-app-dir. The database and the legacy
settings, extensions and designs are those of the legacy installation, and the path above applies to them. To keep
running Exponential 6 inside a Symfony platform, see Legacy bridge and the
legacy bridge bundle specification.
11.5 From 5.4 (or 5.90) to 6.0.0
New code. Either a fresh checkout or
composer create-projectof the new version, into which you copy yoursettings/override/,settings/siteaccess/, your ownextension/directories and your owndesign/directories; or, when the site is a Composer project, change the constraint ofse7enxweb/exponentialin yourcomposer.jsonand runcomposer update se7enxweb/exponential. Never let a tool replace your own settings or extensions.Record the version and bring the schema to what the 6.0 kernel uses. The file records 6.0.0 and makes three schema changes a 5.4 database needs (11.3): it widens
ezuser.password_hashfrom thevarchar(50)of every 5.x schema to 255, the width of the kernel schema; it addsezcontentobject_trash.trashed; and on PostgreSQL it renames the sequences to<table>_<column>_seq:mysql -u USER -p DATABASE < update/database/mysql/6.0/dbupdate-5.4.0-6.0.0.sql psql -U USER -d DATABASE -f update/database/postgresql/6.0/dbupdate-5.4-to-6.0.sqlApply the whole file: none of it is optional. With the default
[UserSettings] HashType=php_defaultandUpdateHash=trueevery user's hash is rewritten at the first sign-in to a 60-character bcrypt hash, which a 50-character column refuses or cuts, and the user cannot sign in again; withouttrashedmoving content to the trash fails; with the old sequence names every insert that needs its new id fails on PostgreSQL. Each change leaves a database that already has it as it is, so the file can run again. Copies of the file from before October 2026 only recorded the version; the 6.0.0 to 6.0.15 file of 11.6 makes the same changes, for a site that used one. Oracle (ezoracle) has no such file: runALTER TABLE ezuser MODIFY ( password_hash VARCHAR2(255) );with your Oracle client, and addtrashed(ALTER TABLE ezcontentobject_trash ADD ( trashed INTEGER DEFAULT 0 NOT NULL );) if the table lacks it. The MySQL file runs on every MySQL from 5.5.3 and on MariaDB (see the per-database notes above). Details: Changelog 6.0.0.PHP. The current line runs on PHP 8.0 to 8.5 (
composer.json:^8.0), Velocity included from its release 0.0.4.54. Releases 6.0.8 to 6.0.14 required 8.1. A site on PHP 7.4 can run the current line again from 6.0.15, without support (6.0.8 to 6.0.14 need PHP 8.1). Check your own extensions for PHP 8 problems: classes that extendeZPersistentObjectoreZDataTypeare the usual cases. See PHP 8 support and PHP 8.0 support.Continue with 11.6.
6.0.8 to 6.0.14 asked for 8.1 in composer.json; from 6.0.15 it accepts 8.0 again, so a server whose operating
system ships PHP 8.0 can take the current line under Apache, nginx or Velocity (Velocity from its release 0.0.4.54)
(PHP 8.0 support). Composer enforces the range: on an older PHP, composer install
stops with "Your requirements could not be resolved to an installable set of packages" and names the PHP version,
before anything is changed.
11.6 From any 6.0.x to today
Step 1: put the new code in place
As in 11.5 step 1: a checkout of the tag or branch (git fetch --tags, git checkout v6.0.<n> or main) followed
by composer install, or composer update se7enxweb/exponential in a Composer project. Then regenerate the class
maps and clear the caches, in this order:
php bin/php/ezpgenerateautoloads.php -e
php bin/php/ezcache.php --clear-allWhy the autoloads first: a new release can add or rename classes, and a class missing from the arrays is "not found" even though its file is there. Why the caches next: INI, template and override caches hold what the old code compiled.
The generator stops with exit status 1 on an option it does not know, so a mistyped option in an upgrade script is
noticed. When you regenerate the kernel array (-k), the directories in .autoloadignore are left out; the shipped
file excludes vendor, var and the .claude worktree folder. An installation that keeps other working copies of
the code inside its root (for example Git worktree folders) must add them there or pass --exclude='<folder>';
otherwise kernel classes are mapped into those copies.
Then reload the PHP that serves the site: the PHP-FPM service of the site (systemctl reload <service>; on Plesk
plesk-php<XY>-fpm, not php-fpm) and, under Velocity, the server:
php bin/php/console exp:velocity deploy --dry-run
php bin/php/console exp:velocity deployexp:velocity deploy runs every step in the right order: the extension autoloads, the INI cache, the template,
override, translation and design caches, the engine archive when needed, the PHP-FPM reload
([DeploySettings] PhpFpmService), the Velocity restart, then the content, HTTP and template-block caches and
Velocity's response cache. It prints PASS, FAIL or SKIP with the time of each and stops at the first failure. Add
--kernel when the upgrade added or renamed kernel classes (the release notes say so). The order matters: a cache
of rendered pages cleared before PHP runs the new code fills up again with pages of the old code. See
chapter 8 and
Velocity engines.
Compare your files with the new templates. A release can change .htaccess_root, .htaccess_root_static and
config.php-RECOMMENDED; your .htaccess and config.php stay as they are:
php bin/php/console exp:setup:status # is .htaccess still a copy of a template?
php bin/php/console exp:setup:htaccess # the diff between your .htaccess and the new template
php bin/php/console exp:setup:htaccess --force --apply # take the new rules (backup first), when the diff holds nothing of yours
php bin/php/console exp:config:list # settings new in config.php-RECOMMENDED show up as (not set)Set the new settings you want with exp:config:set <NAME> <value> --apply; the settings you had keep their values.
See Setting up .htaccess and config.php from the console.
Step 2: apply the database update
The file for the line is update/database/<engine>/6.0/dbupdate-6.0.0-6.0.15.sql, for MySQL, PostgreSQL and SQLite.
It records the version (6.0.15stable); on MySQL and PostgreSQL it makes the three changes of the 5.4 to 6.0.0 file
(the wider ezuser.password_hash, ezcontentobject_trash.trashed and, on PostgreSQL, the <table>_<column>_seq
sequence names) for a site that came from 5.x with a copy of that file from before October 2026, and leaves a
database that already has them as it is (SQLite needs none of them: 11.3); and it
adds the tables and columns of the line: the PDF export footer
(ezpdf_export.show_footer, footer_text), OPML and podcast exports (ezrss_export.opml_head, podcast_head,
table ezrss_export_opml_item), the audit index (expaudit_cursor, expaudit_event, expaudit_file), bookmark
folders (expbookmark_folder, ezcontentbrowsebookmark.folder_id, priority), the e-mail preferences
(expmail_category, expmail_consent_log, expmail_pending, expmail_preference, expmail_suppression) and who
moved an object to the trash (ezcontentobject_trash.trashed_by, trashed_via; until they exist the kernel keeps
writing <VarDir>/trash/trashed.json, afterwards run php update/common/scripts/6.0/movetrashrecords.php, see
the trash view).
Apply it once, whole, in order. If the database already has a column, the statement fails and names it; skip only that statement.
A site that came from 5.x and already applied an older copy of this file may still lack those three changes
(SHOW COLUMNS FROM ezuser LIKE 'password_hash' and SHOW COLUMNS FROM ezcontentobject_trash LIKE 'trashed'; on
PostgreSQL \d ezuser, \d ezcontentobject_trash and \ds in psql). Do not apply the file again: apply the current
5.4 to 6.0.0 file of your engine, which makes them and can run again, and then set the version row, which that file
sets to 6.0.0, back: UPDATE ezsite_data SET value='6.0.15stable' WHERE name='ezpublish-version';.
mysql -u USER -p DATABASE < update/database/mysql/6.0/dbupdate-6.0.0-6.0.15.sqlThen create the audit index tables and index the audit files written so far (the audit trail is on by default from 6.0.15):
php update/common/scripts/6.0/createaudittables.php --dry-run
php update/common/scripts/6.0/createaudittables.phpThe script works on every engine (MySQL/MariaDB, PostgreSQL, SQLite, Oracle and MongoDB, each through its schema
handler) and leaves existing tables alone, so it can be run again. --dry-run shows which tables are missing;
--no-index creates the tables only. If it stops with "The audit index classes are not in the autoload array", run
php bin/php/ezpgenerateautoloads.php -k first. See Audit.
Check the version row afterwards (the query in 11.2); expected 6.0.15stable.
Step 3: read the checklist of each period you pass
Read every release note between your version and today; each opens with what to check.
| From | Read |
|---|---|
| 6.0.0 | 6.0.1: SQLite, replacing kernel modules from an extension |
| 6.0.1 | 6.0.2: links in the online editor |
| 6.0.2 | 6.0.3: datatype extensions joined the distribution |
| 6.0.3 | 6.0.4: the responsive administration design |
| 6.0.4 | 6.0.5: a failed 6.0.4 install, datatypes of extensions |
| 6.0.5 | 6.0.6: admin3 on phones and tablets |
| 6.0.6 | 6.0.7: the last release for PHP 7.4 until 6.0.15 |
| 6.0.7 | 6.0.8: needs PHP 8.1; and PHP 8 support |
| 6.0.8 | 6.0.9: PostgreSQL, REST access to content |
| 6.0.9 | 6.0.10: the product name in templates, translations and overrides; The product is called Exponential |
| 6.0.10 | 6.0.11: PHP 8.5, notification mail, page view counting |
| 6.0.11 | 6.0.12: role-based template operators, several sites in one installation |
| 6.0.12 | 6.0.13: security fixes, sort column checks; Security hardening 6.0.13, Hardening |
| 6.0.13 | 6.0.14: MongoDB, the upgrade file check |
| 6.0.14 | 6.0.15, then in order: July and August 2026, 16 to 30 September 2026 with Security defaults, 1 and 2 October 2026, e-mail preferences and the mail gate |
The changes people meet most often when they reach 6.0.15:
- Accounts without an
ezuser_settingrow cannot sign in (16 August 2026): a user with no settings row used to count as enabled and now counts as disabled. Accounts made in the admin always have the row; accounts made by SQL, packages or migrations may not. The queries that find them are in the August 2026 security specification. - Overrides follow
Priority(12 July 2026): override blocks ofoverride.iniare sorted byPriority, lowest first, and a block without it comes last. Review anyoverride.iniwhere only some blocks have aPriority. - Security headers,
HttpOnly/SameSitesession cookies and a stricter upload check are on by default (September and October 2026).[Session] CookieSecure=automarks the session cookieSecurewhenever the request came over HTTPS. If another host frames your admin, set the header deliberately. - Legacy extensions were brought to one standard between 22 September and 2 October 2026: Behaviour changes of the legacy extensions.
- Static cache defaults changed: Static cache defaults.
- Static publishing is new (November 2026): off until you switch it on; the upgrade step
6.0.15-static-publish-tablesmakes the tablesexp_static_runandexp_static_page. Static publishing. - Velocity, if you run it: Velocity engine upgrade notes and on-disk layout.
For the story by month, see the chronicle; for every single change, the ledger of the root repository.
Step 4: check your own extensions
php bin/php/ezpgenerateautoloads.php -e
php bin/php/console exp:checkclassesexp:checkclasses loads every declared class and reports those PHP refuses to load, which is how a PHP 8
incompatibility in an extension shows. Extensions carry their own version in ezinfo.php and extension.xml, and the
admin's Setup > Extensions list shows it (Extension list). The
order in which extensions load changed in 2026: Extension loading order.
Composer-installed extensions are updated with their package: composer update se7enxweb/<package>, after reading
the package's notes in the extensions' release notes.
Some classic extensions have their own upgrade scripts in bin/php/:
| Script | Upgrades |
|---|---|
php bin/php/ezwebinupgrade.php --to-version=<version> | an existing ezwebin installation to the current package version |
php bin/php/ezflowupgrade.php --to-version=<version> | the installed ezflow packages (--package, --url) |
Run them with --help first.
11.7 Verify the upgrade
php bin/php/ezcache.php --clear-all # ends without an error
php bin/php/console exp:checkdbfiles --no-verify-branches # prints nothing: every update file is in place
php bin/php/console exp:checkclasses # no class PHP refuses to load
php bin/php/console exp:checkmanifest --all # the installed files against share/filelist.md5; exit status 0 when all match
php bin/php/console exp:expfetch # the new version, PHP, engine and database at a glance; no error hintexp:expfetch (the system card) shows the version you upgraded to, the PHP
branch and its support date, the engine and the database in one screen, and ends with hints for what needs
attention (OPcache, a missing PHP extension, a stale help index, cron). Keep ./console exp:info --json --no-logo
from before and after the upgrade and compare them to see every extension version that changed.
exp:checkdbfiles reports only problems, one line each: ! for a file the upgrade path names but the disk lacks,
? for a file on disk the path does not know. No output (apart from the console's own running ... line) means the
update files are complete. Without --no-verify-branches it also tries to reach the retired SVN repository of the
old vendor and prints a C line for that; ignore it or add the option.
Then in the browser: the front page loads; you can log in to the admin; Setup > System information shows the new
version; a test item can be published and appears on the site. Setup > Upgrade check with Check file
consistency compares the installed files with the file manifest share/filelist.md5 and lists the ones that differ
(File consistency check). Files you changed on purpose are expected
there.
Other tools that help after an upgrade:
| Command | When |
|---|---|
php bin/php/ezsqldiff.php --type=mysql --user=USER --password=PASS share/db_schema.dba <database> | compare your database with the reference schema, which goes first. The output is the SQL that would turn the second argument (your database) into the first (the reference): a CREATE TABLE or ADD line names something an update step should have made, a DROP TABLE line a table only your database has (your own extensions' tables appear that way). Read it, do not run it. Exit code 0 when the schemas match, 1 when they differ (--type is mysql or postgresql). Chapters 14 to 16 use it the same way |
php bin/php/updateniceurls.php | rebuild the URL aliases (--update-nodes) |
php bin/php/updatesearchindex.php | reindex all content in the search engine |
php bin/php/cleanupversions.php --apply | remove archived versions beyond the VersionManagement limits |
Last, clear the content cache and, under Velocity, its response cache, after the PHP-FPM reload or Velocity restart, so that a page rendered by the old code is not served again:
php bin/php/ezcache.php --clear-tag=content
php bin/php/console exp:velocity cache clear --apply11.8 If something goes wrong
| Symptom | What to do |
|---|---|
| A white page or HTTP 500 after the code change | Read var/log/error.log; run step 1 of 11.6 again; vendor/ must match the new composer.json. |
| "The site is missing the software libraries it needs" (HTTP 503) | vendor/ is missing: composer install; see Repairing an installation. |
Class ... not found | php bin/php/ezpgenerateautoloads.php -e, then clear the caches. |
| An SQL file failed half way | Restore the backup and apply the file again from the start. |
SET storage_engine=InnoDB; fails | An update file from before October 2026: the current files say SET default_storage_engine=InnoDB;. Take the current file, or skip that one line. |
| Someone cannot sign in | The ezuser_setting change of August 2026 (11.6, step 3). |
| A change does not show under Velocity | exp:velocity restart (or deploy), then exp:velocity cache clear. |
More in chapter 12.
A published release never changes; a fix always comes as the next version. To see what a version holds, open its changelog: 6.0.0 to 6.0.15.
Demo mode (6.0.15). Nothing changes on an upgraded site unless the environment variable EXP_DEMO_MODE=1 is set, which is only for public demos. After upgrading a production server, check that no process of a real site has it (grep -r EXP_DEMO_MODE over the PHP-FPM pools, virtual hosts, .htaccess files, service units and crontabs). Demo mode.
Icon themes of extensions (6.0.15). The shipped rewrite rules now serve extension/<name>/icons/ (SVG, PNG and GIF images whose name has exactly one extension) as files. An upgrade never rewrites your own .htaccess or server configuration: if an extension of the site ships icons (icon.ini [ExtensionSettings] IconExtensions[]), copy the rule into it, before the front controller rule. Apache .htaccess: RewriteRule ^extension/[^/]+/icons/[A-Za-z0-9_/-]+\.(svg|png|gif)$ - [L]; a virtual host takes ^/extension/..., nginx location ~ ^/extension/[^/]+/icons/[A-Za-z0-9_/-]+\.(svg|png|gif)$ { try_files $uri =404; }. Velocity and the generated FrankenPHP configuration have it after exp:velocity deploy. Every server: Web server rules.
References
In this repository:
- Upgrading: the step-by-step guide this chapter extends.
- Setting up .htaccess and config.php from the console: compare both files with the new templates.
- Release notes: changelogs of 6.0 (6.0.0 to 6.0.14), the extensions' release notes, all changelogs.
- Behaviour changes: July and August 2026, 16 to 30 September 2026, 1 and 2 October 2026, legacy extensions, e-mail preferences, static cache defaults, Velocity engine upgrade notes.
- PHP: PHP 8 support, PHP 8.0 support, 5.90 and PHP 7.
- Security: Hardening of 6.0.13, Security hardening 6.0.13, Security hardening August 2026, Security defaults.
- Audit, Maintenance mode, File consistency check, Repairing an installation, Velocity engines.
- Legacy bridge.
- Operating a site: backups, caches and repairs.
- History: the 2026 chronicle and the ledger.
- Code:
update/database/,update/common/scripts/,bin/php/checkdbfiles.php,bin/php/checkclasses.php,bin/php/ezsqldiff.php,lib/version.php.
External:
- PHP migration guides: 8.0, 8.1, 8.2, 8.3, 8.4, 8.5.
- Database backups: mysqldump, pg_dump, SQLite backup command, PostgreSQL pgcrypto.
- The storage engine variable: MariaDB server system variables
(
storage_engine: deprecated in 5.5, removed in 12.0;default_storage_engine). - Composer: update.
- GitHub: se7enxweb/exponential releases.
Contents · Previous: 10. After installing · Next: 12. Troubleshooting
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.