Upgrade procedure
Updated 2026-06-22
Last Updated: 2026-06-22 · Applies to: OpenWatch 0.2.0-rc series (Go single-binary)
This guide covers upgrading an OpenWatch deployment to a newer version. OpenWatch
ships as a single Go binary (/usr/bin/openwatch) that serves both the REST API
and the embedded web UI over HTTPS on port 8443, managed by the openwatch.service
systemd unit and backed by PostgreSQL. There is no container runtime, no separate
web tier, and no Redis/Celery to coordinate, so an upgrade is: install the new
package, apply migrations, restart the service.
There are two paths. The automatic path (below) is one command: the package scriptlet backs up and migrates the database and restarts the service. The controlled (manual) path gives you a step at a time and is the right choice for production change windows. Both are documented here.
For first-time install and configuration, see
INSTALLATION.md. For the database backup and restore
commands referenced below, see BACKUP_RECOVERY.md. For
migration mechanics, see DATABASE_MIGRATIONS.md.
Version note: the current release line is a pre-release (the
0.2.0-rcseries). Treat upgrades between pre-release builds as potentially breaking and always back up first.
Quick upgrade (automatic, recommended)
On a single-instance install an upgrade is one command. The package post-install scriptlet applies any pending database migrations automatically — taking a backup restore point first — and restarts the service.
# RHEL / CentOS / Rocky / Alma / Fedora
sudo dnf update -y 'openwatch*' 'kensa-rules*'
# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade openwatch kensa-rulesWhat happens automatically (on upgrade)
The scriptlet runs only on upgrade, never on a fresh install, and does:
- Checks the database is reachable. If it isn't, migrations are skipped
with a warning (the upgrade doesn't fail) — run
openwatch migratemanually once the DB is back, thensystemctl restart openwatch. - Stops the service — so the new binary never runs against an old schema.
- Backs up the database with
pg_dumpto/var/lib/openwatch/backups/(your restore point; the password is passed via the environment, never on the command line). - Applies pending migrations. Each runs in a transaction, so a failure rolls back atomically — data is never left half-migrated.
- On success → starts the service on the new version.
On failure → leaves the service stopped, prints the restore path, and
exits non-zero so
dnf/aptflag that the upgrade needs attention.
Preview what would change before upgrading:
sudo openwatch migrate --status
# -> "up to date — no migrations pending" OR "PENDING: N migration(s) ..."If an automatic migration fails
The service is left stopped and your data is intact (the failed migration rolled back). Recover with:
# 1. read the error in the dnf/apt output or: journalctl -u openwatch
# 2. fix the cause, then re-apply:
sudo openwatch migrate
sudo systemctl start openwatch
# on Debian, also clear the half-configured state:
sudo dpkg --configure -aTo restore the pre-upgrade dump instead, see Rollback.
Backups: location, retention, opt-out
- Dumps live in
/var/lib/openwatch/backups/. - A systemd timer (
openwatch-backup-cleanup.timer, daily) prunes dumps older thanBACKUP_RETENTION_DAYS(default 30) but always keeps the most recent one, so you never lose your last restore point. - Tune in
/etc/openwatch/upgrade.conf:AUTO_BACKUP=yes|no(setnoonly if you run your own verified pre-upgrade backups),BACKUP_DIR,BACKUP_RETENTION_DAYS.
Controlled (manual) upgrade
The remaining sections are the manual, step-at-a-time path — for production
change windows, multi-step validation, or when AUTO_BACKUP=no.
Before you upgrade
Run through this checklist on the running host:
- Read the release notes for the target version.
- Confirm the service is healthy:
curl -k https://localhost:8443/api/v1/health(expect{"status":"healthy","db_connected":true,"version":"<version>"}). - Record the current version:
openwatch --version. - Record the current migration version (printed at the end of
openwatch migrate, or querygoose_db_version— see Record the migration version). - Take a full PostgreSQL backup (see
BACKUP_RECOVERY.md). - Back up
/etc/openwatch/(config,secrets.env, andtls/). - Confirm free disk space with
df -h /var/lib/openwatch /var. - Schedule a maintenance window and notify users.
Record the migration version
Migrations are tracked in the goose_db_version table. Capture the current
version so you know what the database looked like before the upgrade:
sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
psql "$OPENWATCH_DATABASE_DSN" \
-c "SELECT max(version_id) FROM goose_db_version;"How migrations work
openwatch migrate applies every pending up-migration in
internal/db/migrations/ using goose, then prints the resulting version and the
list of migration files. The command is idempotent: it applies only migrations
not yet recorded in goose_db_version, so it is safe to re-run.
There is no down-migration or downgrade subcommand. Migrations are forward-only.
To revert a schema change you restore the pre-upgrade database backup (see
Rollback). Plan upgrades accordingly: the database backup is your
rollback path, not a reverse migration.
Standard upgrade
These steps assume the OpenWatch user is openwatch and the database DSN is in
/etc/openwatch/secrets.env as OPENWATCH_DATABASE_DSN, matching the install
guide.
Step 1 — Back up the database
Take a fresh dump immediately before the upgrade (commands in
BACKUP_RECOVERY.md). Do not skip this: it is the only
rollback path for schema changes.
Step 2 — Stop the service
sudo systemctl stop openwatchStopping the service quiesces the API, the embedded worker loops, and the PostgreSQL-native job queue before the schema changes.
Step 3 — Install the new package
On RHEL-family hosts (RPM):
sudo dnf upgrade ./openwatch-<new-version>.<arch>.rpmOn Debian/Ubuntu hosts (DEB):
sudo apt install ./openwatch_<new-version>_<arch>.debBoth packages replace /usr/bin/openwatch, refresh the systemd unit, and run
systemctl daemon-reload in their post-install scripts. The config files under
/etc/openwatch/ are marked as config files and are not overwritten on upgrade;
review the new package's default openwatch.toml against yours for new keys.
Confirm the binary version:
openwatch --versionStep 4 — Validate the resolved config
Catch missing or renamed config keys before starting the server:
sudo -u openwatch openwatch check-config --config /etc/openwatch/openwatch.tomlThis prints the resolved configuration with secrets redacted and exits non-zero
if validation fails. Config layering, highest precedence first: CLI flags > env
vars (OPENWATCH_<SECTION>_<KEY>) > the TOML file > built-in defaults.
Step 5 — Apply migrations
sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
openwatch migrate --config /etc/openwatch/openwatch.tomlThe command prints the current version, the count of migration files, and each
filename, then migrations applied. If it fails, the service is still stopped —
fix the cause or restore the backup (see Rollback) before starting.
Step 6 — Start the service
sudo systemctl start openwatch
sudo systemctl status openwatchStep 7 — Verify the upgrade
# Health and reported version.
curl -k https://localhost:8443/api/v1/health
curl -k https://localhost:8443/api/v1/version
# Watch the structured logs for the startup line and any errors.
sudo journalctl -u openwatch -n 100 --no-pager
# Confirm the database is reachable from the host.
sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
psql "$OPENWATCH_DATABASE_DSN" -c "SELECT 1;"The version field in both /api/v1/health and /api/v1/version should report
the new version. Sign in at https://<host>:8443/ and confirm the UI loads.
Rollback
Because migrations are forward-only, rolling back a release that changed the schema means restoring the pre-upgrade database backup and reinstalling the previous package.
Code-only rollback (no migration ran)
If the upgrade failed before Step 5, or the target version applied no new migrations, reinstall the previous package and restart:
sudo systemctl stop openwatch
# RHEL family:
sudo dnf install ./openwatch-<old-version>.<arch>.rpm
# Debian/Ubuntu:
sudo apt install ./openwatch_<old-version>_<arch>.deb
sudo systemctl start openwatch
curl -k https://localhost:8443/api/v1/healthFull rollback (migrations ran)
If Step 5 applied new migrations, restore the pre-upgrade database backup, then reinstall the previous binary:
# 1. Stop the service.
sudo systemctl stop openwatch
# 2. Restore the pre-upgrade database dump
# (exact pg_restore/psql commands: BACKUP_RECOVERY.md).
# 3. Reinstall the previous package (see Code-only rollback above).
# 4. Start and verify.
sudo systemctl start openwatch
curl -k https://localhost:8443/api/v1/healthKeep the pre-upgrade dump until you have validated the upgrade in production (at least several days).
Updating Kensa compliance rules
Kensa is the SSH-based compliance engine, integrated as a Go dependency
(internal/kensa/); its native YAML rules are compiled into the openwatch
binary. Rules therefore travel with
the binary — installing a new OpenWatch package is what updates the bundled
rule set. There is no separate rule-pull or out-of-band rule-sync step. For the
Kensa/OpenWatch responsibility boundary, see
docs/KENSA_OPENWATCH_BOUNDARY.md.
Upgrading PostgreSQL
PostgreSQL is provisioned and operated independently of the OpenWatch package
(see INSTALLATION.md). A PostgreSQL major-version upgrade
(e.g. 15 to 16) is never performed by the OpenWatch package scriptlet — it is
a data-directory migration (pg_upgrade or dump/restore) that needs both server
versions and must be operator-supervised; doing it silently from a package
upgrade would risk the whole database. Plan it separately, with its own backup:
follow your distribution's procedure, stop openwatch.service first so no
connections are open, then start it again afterward and run the
verification checks. (Minor PostgreSQL and
dependency updates are handled by dnf/apt via package dependencies — nothing
extra to do.)
Troubleshooting
Service fails to start after upgrade
sudo systemctl status openwatch
sudo journalctl -u openwatch -n 200 --no-pagerCommon causes:
- Invalid or incomplete config — run
sudo -u openwatch openwatch check-config --config /etc/openwatch/openwatch.toml. - Missing database secret — confirm
/etc/openwatch/secrets.envdefinesOPENWATCH_DATABASE_DSN. - Missing signing/encryption key material — the server refuses to start without
[identity].jwt_private_keyand[identity].credential_key_file; the log line names the missing key. - Schema not migrated — run Step 5.
migrate fails
Re-run the command and read the error. The most common cause is the database
being unreachable or the DSN being wrong; verify with
psql "$OPENWATCH_DATABASE_DSN" -c "SELECT 1;". Because migrations are
idempotent, a partial run can be retried after the underlying issue is fixed. If
the schema is in an unexpected state, restore the pre-upgrade backup.
Health endpoint returns 503
A 503 from /api/v1/health means the service started but a dependency is
unhealthy — typically the database. Check db_connected in the response body
and confirm PostgreSQL is running and reachable.
Post-upgrade checklist
-
/api/v1/healthreturnshealthywithdb_connected:true. -
/api/v1/versionreports the new version. -
journalctl -u openwatchshows a clean startup and no recurring errors. - An administrator can sign in at
https://<host>:8443/. - A compliance scan completes end to end.
- The upgrade is recorded in your change log.
- The pre-upgrade backup is retained through the validation period.