# Upgrade oCMS

*Published: 2026-11-02*

Upgrade the application while preserving the site's database, uploads, custom themes and configuration. Treat the new binary and any startup migrations as one change. Keep a verified pre-upgrade backup and its matching old binary until the new installation has passed checks.

This walkthrough uses the standard Linux layout from the [deployment guide](/deployment): `/opt/ocms/ocms`, `/srv/ocms/my-site`, service `ocms-site`, and runtime user/group `ocms`. Adjust every path and the service name before running server commands. Sites with external data or upload paths need a backup and restore plan for those paths too.

## 1. Identify the installation and target release

Record the installed version, active custom theme, active modules, operating system and architecture. Read the [latest stable release notes](https://github.com/olegiv/ocms-go/releases/latest), including intervening breaking changes when skipping versions. The [Download page](/download#upgrade-requirements) highlights the currently reviewed release's requirements.

```
/opt/ocms/ocms -version
uname -s
uname -m
```

A stock release binary does not include your separately compiled custom Go modules. If the installation uses any, obtain a verified replacement build containing those modules before continuing. A custom HTML theme can stay in `custom/themes`, but still needs a compatibility check with the new binary.

Preserve existing `OCMS_SESSION_SECRET` and `OCMS_MFA_KEY`. Add new required settings to the existing file; do not run `ocms init` over the site or replace its configuration with release examples. Review [configuration](/configuration) and [account security](/account-security) for key and MFA requirements.

## 2. Stage one verified candidate

The example below downloads **Linux AMD64**. Choose Linux ARM64 or macOS Apple Silicon on [Download](/download) when appropriate; Linux server commands below do not apply to macOS. Run these server steps in an administrator's Bash shell, keeping the same shell for subsequent sections. The application itself continues to run as the unprivileged service user.

```
set -euo pipefail
umask 077
SITE_DIR=/srv/ocms/my-site
INSTALLED_BINARY=/opt/ocms/ocms
STAGE_DIR="/root/ocms-upgrade-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir "$STAGE_DIR"
cd "$STAGE_DIR"

curl -fLO https://github.com/olegiv/ocms-go/releases/latest/download/ocms-linux-amd64.tar.gz
curl -fLO https://github.com/olegiv/ocms-go/releases/latest/download/checksums.txt
grep -F '  ocms-linux-amd64.tar.gz' checksums.txt | sha256sum -c -
tar -xzf ocms-linux-amd64.tar.gz
./ocms -version | tee target-version.txt
sha256sum ocms > candidate.sha256
"$INSTALLED_BINARY" -version > installed-version.txt
```

The download URLs follow latest, but the files in this new staging directory are the candidate you test and later install. Compare the reported target version with its release notes. Keep this exact binary and checksum; do not download a different latest build between rehearsal and deployment. A failed checksum or unexpected version means stop and obtain a consistent archive and checksum file.

## 3. Test a restored copy first

Follow [backup and restore](/backup-and-restore) to create and restore a consistent site backup. Use a separate host or container with the same OS/architecture, no access to production data paths, and outbound traffic blocked so copied integrations and jobs cannot affect real services. Bind the rehearsal to loopback on a separate port.

1. Verify the restored site with its matching old binary first.
2. Stop the restored process and replace only its binary with the staged candidate. Apply any required configuration changes to the restored copy.
3. Start the candidate from the restored site's directory. Startup applies pending core and module migrations. Read the startup log and check `/health`.
4. Check pages, media, the custom theme, admin sign-in/MFA, and the integrations or modules the site uses. Test editing a disposable draft in this isolated copy.
5. Rehearse restoring the pre-upgrade backup with the old binary into another clean destination. Do not point the old binary at the migrated rehearsal database.

Resolve failures before scheduling the production update. A successful local startup alone does not prove a customized production site is compatible.

## 4. Stop writes and take the final backup

Schedule maintenance and keep public writes closed until verification finishes. Stop the service and verify it is stopped. Take a fresh backup now, even if a previous backup was used for rehearsal. These commands require `sqlite3`, `tar` and `sha256sum`.

```
systemctl stop ocms-site
if systemctl is-active --quiet ocms-site; then
  echo 'The site is still running; do not continue.' >&2
  exit 1
fi

test -f "$SITE_DIR/.env"
test -f "$SITE_DIR/data/ocms.db"
test -d "$SITE_DIR/uploads"
test -d "$SITE_DIR/custom"
test -x "$INSTALLED_BINARY"
BACKUP_DIR="/root/ocms-before-upgrade-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir "$BACKUP_DIR"
(
  cd "$BACKUP_DIR"
  sqlite3 "$SITE_DIR/data/ocms.db" '.backup ocms.db'
  test "$(sqlite3 ocms.db 'PRAGMA integrity_check;')" = ok
  tar -czf site-files.tar.gz -C "$SITE_DIR" .env custom uploads
  cp -p "$INSTALLED_BINARY" ./ocms
  ./ocms -version > version.txt
  sha256sum ocms.db site-files.tar.gz ocms version.txt > SHA256SUMS
  sha256sum -c SHA256SUMS
)
```

Keep the original configuration and both keys in this private backup. Include any external storage paths omitted by the standard layout. If a backup command fails, leave the candidate uninstalled and correct the backup before continuing. Keep a protected copy on another machine as described in the backup guide.

## 5. Install the tested binary and restart

After the backup is complete, apply the release's required configuration changes to the existing file. Recheck the staged checksum, install alongside the current binary, then rename it into place. This keeps the replacement on the binary's own filesystem.

```
(
  cd "$STAGE_DIR"
  sha256sum -c candidate.sha256
)
test ! -e "${INSTALLED_BINARY}.new"
install -m 0755 "$STAGE_DIR/ocms" "${INSTALLED_BINARY}.new"
mv -f "${INSTALLED_BINARY}.new" "$INSTALLED_BINARY"
systemctl start ocms-site
systemctl status ocms-site --no-pager
journalctl -u ocms-site -n 80 --no-pager
```

Once the candidate starts, assume it may have changed the database even if startup later fails. Do not repeatedly swap binaries to diagnose a migration error. Save the logs and use the recovery procedure if the update cannot be made healthy.

## 6. Verify before reopening writes

Replace the public example domain, then check both routes:

```
curl --fail --silent --show-error http://127.0.0.1:8080/health
curl --fail --silent --show-error https://cms.example.com/health
"$INSTALLED_BINARY" -version
```

- Confirm health returns HTTP 200 and the installed version matches the staged candidate.
- Check a known page, blog listing, search result, uploaded image and custom-theme layout at desktop and mobile widths.
- Sign in with the intended account and complete MFA. Check a disposable draft and the site's required modules and integrations.
- Confirm CSS and JavaScript load successfully, then verify the public site after another planned service restart.

Reopen normal writes only after these checks pass. Record the installed version, candidate checksum, backup location, configuration changes and actual results. Retain the verified backup and old binary according to your retention policy.

## 7. Recover the whole pre-upgrade installation

**Do not assume a binary downgrade reverses a database migration.** An older binary may not understand the upgraded schema or data. Unless a specific release documents and you have tested a compatible rollback, restore the matching pre-upgrade database, site files, configuration and old binary together.

Stop writes and the affected service first. A restore loses changes made after the backup; if production has already accepted new content or uploads, preserve those changes and plan their recovery before restoring. Keep the upgraded installation for investigation.

If a failed command ended the previous shell, open a new administrator Bash shell. Set `SITE_DIR` and `INSTALLED_BINARY` to the paths from section 2, and `BACKUP_DIR` to the actual final backup from section 4; do not create a new backup of the failed installation in its place. Keep `set -euo pipefail` and `umask 077` enabled. The following standard-layout example restores into a new directory, so no old WAL/SHM files are mixed with the restored database:

```
systemctl stop ocms-site
if systemctl is-active --quiet ocms-site; then
  echo 'The site is still running; do not restore.' >&2
  exit 1
fi
(
  cd "$BACKUP_DIR"
  sha256sum -c SHA256SUMS
)
RESTORE_DIR="${SITE_DIR}.restore-$(date -u +%Y%m%dT%H%M%SZ)"
FAILED_DIR="${SITE_DIR}.before-restore-$(date -u +%Y%m%dT%H%M%SZ)"
test ! -e "$FAILED_DIR"
test ! -e "${INSTALLED_BINARY}.new"
mkdir "$RESTORE_DIR"
tar -xzf "$BACKUP_DIR/site-files.tar.gz" -C "$RESTORE_DIR"
mkdir "$RESTORE_DIR/data"
cp "$BACKUP_DIR/ocms.db" "$RESTORE_DIR/data/ocms.db"
test "$(sqlite3 "$RESTORE_DIR/data/ocms.db" 'PRAGMA integrity_check;')" = ok
chown -R ocms:ocms "$RESTORE_DIR"
chmod 600 "$RESTORE_DIR/.env"
mv "$SITE_DIR" "$FAILED_DIR"
mv "$RESTORE_DIR" "$SITE_DIR"
install -m 0755 "$BACKUP_DIR/ocms" "${INSTALLED_BINARY}.new"
mv -f "${INSTALLED_BINARY}.new" "$INSTALLED_BINARY"
systemctl start ocms-site
systemctl status ocms-site --no-pager
```

Repeat the health, content, image, theme and sign-in checks with the old version before reopening writes. If restoring also fails, keep the service stopped and inspect the saved logs and configuration rather than initializing an empty replacement database. For storage outside the standard site directory, restore those locations consistently too.

The [backup walkthrough](/backup-and-restore) covers isolated restore checks, and [the CLI reference (GitHub Wiki)](https://github.com/olegiv/ocms-go/wiki/Getting-Started) describes migration commands. Routine recovery here uses the full verified backup; it does not run `migrate down` against production.

---

[Source](https://ocms.tech/upgrading)
