02.01 The Upgrade Process
How SimpleRisk upgrades — the two version stamps that have to match, the release chain the upgrade walks to make them match, what protects you when an upgrade spans several releases, and the deployment-shape-specific workflows in the rest of this chapter.
Why this matters
SimpleRisk releases roughly every two to three months. Each release ships application code changes, sometimes ships database schema changes, and accumulates against the prior release rather than replacing it. Upgrades are how an installation moves from one release to the next; the upgrade is what runs the schema migrations that keep the database in sync with the application code.
Understanding the upgrade architecture matters because it shapes the operator's mental model for everything else in this chapter. Once you know that:
- The application code carries one version stamp, and the database carries another.
- After you swap the files but before you run the database upgrade, those two stamps disagree. That's normal, and it's the state the upgrade exists to resolve.
- The upgrade walks the chain of releases, one hop at a time, until the database stamp catches up with the application stamp.
- The chain covers every release SimpleRisk has ever shipped, so an instance years behind can still walk forward to current.
- Each hop finishes by stamping the database at that release before the next one starts.
— then the deployment-specific upgrade workflows (Upgrading via Docker, Upgrading on Bare Metal, Upgrading Extras) all make sense as variations on the same underlying mechanism.
The other thing worth knowing: upgrades are not optional. SimpleRisk's release cadence includes security patches that don't backport to old versions. An installation running on a version released two years ago is exposed to whatever security issues have been fixed since. The upgrade discipline is part of the program's security posture, not an optional convenience.
The third thing: Extras upgrade separately from Core. Each Extra tracks its own version and walks its own chain. A Core upgrade doesn't automatically bring the Extras forward — they have their own workflow, documented in Upgrading Extras.
How frameworks describe this
Frameworks generally don't prescribe specific upgrade processes (that's vendor-specific) but they do require organizations to keep their software current as part of broader vulnerability management.
- NIST SP 800-53 SI-2 (Flaw Remediation) explicitly mandates that the organization identify, report, and correct information system flaws, including patches and updates. SimpleRisk upgrades fit under SI-2 directly.
- CIS Critical Security Controls v8 Control 7 (Continuous Vulnerability Management) includes patching as a core practice.
- PCI DSS v4.0 Requirement 6.3.3 mandates that all in-scope system components have all known security vulnerabilities addressed within an organization-defined timeframe (typically 30 days for critical, 90 days for high).
- ISO/IEC 27001 Annex A
A.8.8(Management of technical vulnerabilities) requires the organization to have a documented vulnerability-management process that includes timely patching.
For programs running SimpleRisk under any of these regimes, "we're still on the version from 18 months ago" isn't a defensible answer. The upgrade cadence needs to keep pace with the release cadence, with documented exception handling for cases where an upgrade has to wait.
How SimpleRisk implements this
Version stamps
Two version values matter, and both are shown to you on the upgrade page:
- The application version — the version of the code currently deployed. It changes the moment you swap the files.
- The database version — the version the schema is currently at. It changes only when you run the upgrade.
Both use the same YYYYMMDD-VVV format, so a release reads as something like 20260422-001.

The two are allowed to disagree, and for a short window they always will. Swap the Docker image or extract the new tarball and the application version jumps to the new release while the database version stays on the old one. That's exactly the state the upgrade resolves: it brings the database version up to match the application version. If you ever find the two disagreeing when you didn't expect it, that's the tell that the file swap happened and the database upgrade didn't.
The release chain
SimpleRisk knows the full ordered list of every version it has ever shipped, stretching back to 2014, and it knows how to move the schema from each one to the next. That list is the release chain, and it's what makes a multi-version jump safe.
The upgrade doesn't leap from your version to the newest one. It walks — applying one release's schema changes, stamping the database at that release, then looking again to see whether there's further to go. An instance three releases behind runs three migrations in order, in a single pass, without you doing anything different from an instance one release behind.
Two properties matter operationally:
- Each hop is stamped before the next begins. If something fails partway through a long chain, the database is left cleanly at the last release that completed rather than somewhere between two. Fix the cause and re-run; the upgrade picks up from where it stopped rather than starting over.
- Two upgrades can't run at once. Every route into the upgrade takes an instance-wide lock for the duration of its run. A second one that starts while the first is in flight is turned away with a message saying an upgrade is already running, rather than being queued behind it or — worse — interleaved with it.
That second guard matters most on hosted instances, where fleet automation calls the upgrade endpoint on a schedule and can collide with an administrator who has just clicked Upgrade in the browser. The refused caller changes nothing.
The upgrade UI and API
Two operator-facing entry points:
/admin/upgrade.php— the browser-based upgrade UI. Requires admin login. Walks the schema upgrade and reports progress. Use this for interactive upgrades.POST /api/upgrade/upgrade/simplerisk/db— the API entry point for programmatic upgrades. Authenticates via the standard API key mechanism. Use this for scripted upgrades or for orchestration in CI/CD-driven deployments.
A third entry point, POST /api/v2/admin/upgrade/db, brings the database fully up to date from wherever it currently sits and returns a structured per-release result. Called with no version parameter it walks the whole chain; called with one it applies that single release. It is the endpoint to reach for when you want a machine-readable answer rather than a progress page.
Several related API routes exist for full-stack upgrade orchestration:
GET /api/upgrade/version/app— get the current application version.POST /api/upgrade/backup/app— back up the application files (used in the standard upgrade workflow).POST /api/upgrade/backup/db— back up the database (used in the standard upgrade workflow).POST /api/upgrade/upgrade/simplerisk/app— upgrade the application files (Docker image swap, file replacement; the API endpoint orchestrates this for some deployment shapes).POST /api/upgrade/upgrade/simplerisk/db— run the database upgrade (the main programmatic entry point).- Per-extra:
POST /api/{extra}/upgrade/app(for example/api/notification/upgrade/app) replaces an Extra's files, andPOST /api/v2/{route}upgrades its database (both sets of routes are listed in Upgrading Extras). - All Extras at once:
POST /api/v2/admin/extras/upgrade-databasesupgrades the database of every installed, enabled Extra and reports each one's result. It is the call for deployments that replace the code without the one-click upgrade (see Upgrading Extras).
Everything above except the version lookup changes data, so it accepts POST only; a GET is answered 404.
These endpoints come from the Upgrade Extra, which may need activating first. The Extra is free, but it's gated on registering the instance — until the installation is registered there's nothing to activate, and none of the API routes above exist. Registration is a one-time step covered in Registration and Licensing. On installations that don't register and activate it, the browser-based /admin/upgrade.php is the upgrade path, and it's a complete one.
Which releases your instance is offered
An instance is offered releases from its update channel: Production by default, or Release Candidate if an administrator chose it. The release list, the Extras and the downloads for one upgrade all come from the same channel, and the channel is fixed when the upgrade starts, so changing the selector halfway through a run does not mix sources. Choosing a channel, and pinning it in config.php, is covered in Registration and Licensing.
The release chain only ever moves forward. If your instance is running a release its channel does not list, which happens when a release candidate is withdrawn and replaced, the upgrade starts from the next published release after yours and carries on from there. It is never moved to an earlier release. If the channel has nothing later, the Register & Upgrade page says the instance is running a release the channel has not published and offers no upgrade.
If an upgrade run cannot read the update service at all, it reports the message The update service could not be reached, so the latest release is unknown. and installs no new application release. Any database upgrade, Extra upgrade, or correction that is already pending still runs, and when there is nothing else to do the run ends as failed. Restore outbound access and run it again.
Special handling during upgrade
The upgrade flow deliberately bypasses several security checks that apply during normal operation:
- Account lockout after failed login attempts — disabled during upgrade, so an admin locked out of normal sessions can still log in to run an upgrade.
- MFA enforcement — disabled during upgrade.
- Forced password change — disabled during upgrade.
The reasoning: an upgrade is by definition a recovery scenario where the database schema may be mid-migration; requiring the full normal-operation security flow could prevent the operator from completing the upgrade. The trade-off is documented; if your security policy doesn't permit the bypass, run upgrades from a controlled environment with the same physical/network controls you'd require for any privileged operation.
The upgrade flow also runs in its own session namespace (SimpleRiskDBUpgrade rather than the main app session) so an upgrade in progress doesn't contaminate normal user sessions.
What the operator sees during a one-click upgrade
An instance running the Upgrade Extra no longer watches a single long-lived HTTP response scroll past. Clicking Upgrade starts a background job and returns immediately; the page then polls for that job's state and draws a list of named steps — check the version, refresh the Upgrade Extra, back up the application, back up the database, swap the files, migrate the schema, update the installed Extras, upgrade those Extras' databases — each carrying its own state and the messages the upgrade functions produced underneath it.
A one-click upgrade installs the newest release your server's PHP can run, not the next one in line. An instance four releases behind gets the current release's files in a single swap, and the database migration then walks every release in between, as described above. It never stops on an intermediate release, so a defect that shipped in one of those releases and was fixed in a later one can't catch it on the way through.
The database migration runs as a separate PHP command-line process, started once the new files are in place, so that it loads only the new release's code. That means the server needs the PHP command-line interface with the pdo_mysql extension. SimpleRisk's scheduled jobs need the same thing, so a working install already has it. If it can't be found, the upgrade stops before changing anything and says so.
Extras follow the core in a fixed order: the Extras' files are replaced after the core's files and database are done, then each upgraded Extra that is enabled has its own database upgraded, and last the queue workers are asked to restart so they load the new Extra code. An Extra that is installed but not enabled is left alone. If an Extra's database upgrade does not complete, the run ends as failed and names the Extra, even though the core upgrade itself succeeded; the Extra finishes upgrading the first time it is used, or you can run its database upgrade yourself (see Upgrading Extras). An Extra whose new version can't be downloaded doesn't catch up on its own. It stays on the version it had, its database is left alone, and the run ends as failed with a message naming it. Run the upgrade again to retry. The core is already current by then, so the second run only has the Extras left to do.
Two consequences are worth knowing before you run one:
- Closing the tab does not stop the upgrade. The work runs in its own process. Reopening the page re-attaches to the job in progress rather than starting a second one.
- A step that is skipped says so. An instance already on the current release swaps no files and migrates no schema, and those steps are marked Not needed rather than left blank — a blank step is indistinguishable from one that failed.
The upgrade backups this flow takes are registered like any other, so they appear under Configure > Backups alongside manually-taken ones. Where the files are written: under the Backup Path configured on that page, in a simplerisk-backup folder, when that path is an existing folder the web server can write to and is neither in a temporary directory (/tmp, /var/tmp) nor inside the SimpleRisk folder itself. Otherwise they go to a simplerisk-backup folder next to the SimpleRisk folder (for example /var/www/simplerisk-backup for an install in /var/www/simplerisk). If the one-click upgrade stops with "Failed to create backup directory", the web server account can write to neither: fix the permissions on your Backup Path, or on the folder that contains SimpleRisk.
Upgrading as far as your PHP allows
Each SimpleRisk release declares a minimum PHP version. An instance several releases behind is frequently running a PHP that the newest release will not run on. Historically that refused the upgrade outright and left the instance exactly where it was, which is the worst of both outcomes.
The Upgrade Extra carries a table of the minimum PHP each release requires and checks it before choosing what to install. An instance on PHP 8.1 is taken straight to the newest release that still supports 8.1, its database is brought up to match, and the run ends with a message naming the release that blocked it, the PHP version that release needs, and how much further raising PHP would get you. Upgrade PHP and run the upgrade again to continue.
Releases predating the table (pre-2021) carry no entry and are treated as unconstrained. Silence never blocks an upgrade, since the table exists only to stop one.
Post-upgrade housekeeping
Once the release chain finishes, the upgrade runs two normalization passes over the database:
- Storage engine — anything still on MyISAM from a much older install is moved to InnoDB, so the whole schema gets transactional consistency.
- Character set — every table is moved to full UTF-8 (
utf8mb4), which is what the multi-language UI needs to store the full Unicode range.
Both passes are safe to re-run; on an already-normalized database they do nothing. And every route into the upgrade runs them — the browser UI, one-click, both API endpoints, and the Upgrade Extra.
That last part was not always true, and it's worth knowing if you maintain instances that have been upgraded different ways over the years. Instances upgraded through the API used to finish on the older three-byte UTF-8 while the same instance upgraded through the browser finished on four-byte. Because MySQL widens columns when it converts to a wider character set, that left the two on genuinely different column types, not just different collations. Whichever route you take now lands on the same schema.
If a single table refuses to convert — a legacy sessions table with an over-wide primary key is the classic case — it's logged and skipped rather than aborting the pass. One obstinate table no longer leaves every table after it unconverted.
Telling your users
When an upgrade finishes, every user gets a notification under Notifications saying SimpleRisk was upgraded to the new version, with a link to the release notes at https://www.simplerisk.com/product-releases. The notice goes out once per upgrade run, however many releases the run crossed, so an instance that jumps four releases sends one notice, not four. The notice appears in each user's own language.
It is sent only when the database version actually moved. Opening the upgrade page on an instance that is already current sends nothing, and neither does running an upgrade twice to the same version. The release notes are published by hand, so the page can lag a release by a short time.
Database privilege requirements
Before it changes anything, the upgrade checks that SimpleRisk's MySQL user holds the privileges it will need: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, REFERENCES, and INDEX. These are the same privileges the initial install granted (Step 6 of the wizard), so an untouched instance already has them — upgrades ask for nothing extra.
If the check fails, because the privileges were narrowed after install or because a different MySQL user is in play, the upgrade stops and names the missing privileges rather than failing partway through a migration. Grant them temporarily for the upgrade, or point the upgrade at a connection that has them.
Common pitfalls
A handful of patterns recur with SimpleRisk upgrades.
-
Skipping the application-files step. The two-part upgrade is "swap the files" (new image or new tarball) plus "run the database upgrade." Skipping the file swap leaves the application code at the old version even after the database has migrated; the symptoms range from confusing UI behavior to outright errors. Always do both parts; the deployment-shape-specific articles (Upgrading via Docker, Upgrading on Bare Metal) document the specific sequence.
-
Skipping the database upgrade after the file swap. The mirror failure: new application code, old database schema. The application starts erroring as code paths try to use schema elements that don't exist yet. After replacing the application files, run the database upgrade promptly — either via
/admin/upgrade.phpor viaPOST /api/upgrade/upgrade/simplerisk/db. -
Upgrading without a backup. The single most common operator regret. The upgrade is generally reliable but the schema changes are not trivially reversible without a database backup. Take a backup before any upgrade; see Database Backup and Restore. The backup is the rollback path; without it, a failed upgrade is recoverable only by running the upgrade forward through whatever issue blocked it.
-
Letting the upgrade chain stretch too far. Skipping releases is supported (the chain handles it), but the longer the chain, the more upgrade functions run in series, and the more chance for a failure mid-chain to leave the database in an indeterminate state. For installations that have skipped many releases, consider upgrading through intermediate versions (release-by-release) rather than jumping the entire gap in one upgrade. The intermediate steps are testable independently.
-
Not reading the release notes. Each release's notes (under
release_notes/in the SimpleRisk repository) document what changed, including upgrade-specific notes (data migrations that take significant time, configuration settings that need attention, deprecations). Reading the notes for the versions you're skipping over is part of the upgrade preparation. -
Forgetting to upgrade the Extras. Core upgrades don't trigger Extra upgrades. After a Core upgrade, walk the activated Extras and verify each is at the version expected for the new Core release. The per-Extra upgrade workflow is in Upgrading Extras.
-
Expecting asset discovery to work straight after the upgrade. Network discovery from the Manage Assets page now scans only the networks a system administrator lists in
config.php, and with no list it refuses every range. After upgrading, add the list if you use discovery; see Allowing asset discovery targets. -
Running the upgrade UI from a session that's about to time out. Long upgrades (multi-version chains, large databases) can take many minutes. A session that times out mid-upgrade can leave the operator unable to monitor the completion. Use the API path for long upgrades, or extend the session timeout temporarily.
-
Running the upgrade against a database with active production load. The schema changes can lock tables or run for minutes; concurrent application traffic against the same tables can fail. For high-traffic installations, schedule the upgrade during a maintenance window with the application disabled (or in maintenance mode) for the duration of the upgrade.
-
Treating the upgrade-mode security bypass as a permanent escape hatch. The bypass exists for upgrade-time scenarios. Don't rely on it for routine admin access; if you're doing admin work outside an actual upgrade, run through the normal authentication flow (with MFA if configured).