This guide takes an administrator from a fresh Linux host to a running, logged-in OpenWatch: install the package, point it at PostgreSQL, create the first admin user, start the service, and sign in to the web UI.
Installing the package gives you a single systemd-managed service that serves
both the REST API and the web UI over HTTPS on port 8443. One binary contains
everything — the API, the embedded React UI, and the Kensa compliance engine; no
separate web tier, no container runtime, no external cache.
After the steps below you have:
- The OpenWatch UI and API at
https://<host>:8443/, behind session login. - An
adminaccount you create during install. - A PostgreSQL database holding hosts, scans, transactions, and audit events.
- Kensa ready to run SSH-based compliance checks against the hosts you add.
| Step | What | Command |
|---|---|---|
| 1 | Install PostgreSQL | dnf install postgresql-server / apt install postgresql |
| 2 | Provision the database | createdb + role (see below) |
| 3 | Install the packages | dnf install ./openwatch-*.rpm ./kensa-rules-*.rpm / apt install ./openwatch_*.deb ./kensa-rules_*.deb |
| 4 | Configure the database secret | edit /etc/openwatch/secrets.env |
| 5 | Run migrations | openwatch migrate |
| 6 | Create the first admin | openwatch create-admin --username admin --email … |
| 7 | Start the service | systemctl enable --now openwatch |
| 8 | Sign in | open https://<host>:8443/ |
On a host that already runs PostgreSQL, this takes about five minutes.
- OS:
- RPM: CentOS Stream 9, RHEL 9, Rocky Linux 9, AlmaLinux 9, Oracle Linux 9
- DEB: Ubuntu 24.04 LTS, Debian 12 (or a compatible
systemdderivative)
- Architecture:
x86_64/amd64oraarch64/arm64(packages ship for both). - CPU/RAM: 1 vCPU / 512 MB for the service itself; size up for large fleets.
- Disk: 500 MB for the binary plus database growth sized to your retention.
- PostgreSQL: 14 or newer. The package depends on the PostgreSQL client/server but does not create a database — you do that in Step 2.
- Network:
- TCP/8443 inbound for the API and UI.
- TCP/22 outbound from this host to every managed host (Kensa scans over SSH).
- A browser to reach the UI, and
sudo/root for the install steps. The service itself runs as the unprivilegedopenwatchuser the package creates.
Download the
.rpm/.deb, theSHA256SUMS,SHA256SUMS.asc, andKEYSfrom the GitHub release. To verify authenticity before installing:gpg --import KEYS && gpg --verify SHA256SUMS.asc SHA256SUMS, thensha256sum -c SHA256SUMS. RPMs are also signed in-header — importKEYSwithrpm --import KEYSand check withrpm -K openwatch-*.rpm.
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresqlCreate the role and database:
sudo -u postgres psql <<'SQL'
CREATE ROLE openwatch WITH LOGIN PASSWORD 'replace-with-a-strong-password';
CREATE DATABASE openwatch OWNER openwatch;
SQLAllow password auth from localhost. Edit /var/lib/pgsql/data/pg_hba.conf and
ensure these lines exist near the top of the host rules, then reload:
host openwatch openwatch 127.0.0.1/32 scram-sha-256
host openwatch openwatch ::1/128 scram-sha-256
sudo systemctl reload postgresql
PGPASSWORD='replace-with-a-strong-password' \
psql -h 127.0.0.1 -U openwatch -d openwatch -c '\conninfo'sudo dnf install -y ./openwatch-*.x86_64.rpm ./kensa-rules-*.noarch.rpmInstall both files in one transaction. openwatch declares a hard
dependency on kensa-rules — the rule corpus the scan engine loads from
/usr/share/kensa/rules. Installing openwatch alone fails the dependency
check (by design: a corpus-less node cannot scan). kensa-rules is noarch
and versioned on the Kensa content line (e.g. 0.5.0), independent of the
platform version, so the rules can update without re-releasing OpenWatch.
Use the filenames you downloaded (aarch64 for the arm64 openwatch RPM; the
kensa-rules package is the same noarch file for every arch). Installing the
packages:
- Creates the
openwatchsystem user and group (idempotent). - Installs the binary at
/usr/bin/openwatch, config under/etc/openwatch/(openwatch.tomlplus a self-signed TLS cert/key), thesystemdunit, and the/var/lib/openwatchand/var/log/openwatchdata directories. Thekensa-rulespackage installs the rule corpus to/usr/share/kensa/rules. - Generates the identity keys the server requires in production —
/etc/openwatch/keys/jwt_private.pem(RSA-2048 JWT signing key) and/etc/openwatch/keys/credential.key(AES-256 credential DEK). This is generate-if-absent: a reinstall or upgrade never overwrites existing keys (regenerating them would invalidate sessions and make stored SSH/MFA secrets undecryptable). The server does not auto-generate these — it exits if they are missing — so the package lays them down at install time. - Reloads
systemd. It does not start the service — you do that in Step 7, after the database and admin user exist.
Confirm the install:
rpm -q openwatch
openwatch --versionThe service reads its database connection string from
/etc/openwatch/secrets.env so the password stays out of the world-readable
config. The systemd unit loads this file automatically.
sudo tee /etc/openwatch/secrets.env >/dev/null <<'EOF'
OPENWATCH_DATABASE_DSN=postgres://openwatch:replace-with-a-strong-password@127.0.0.1:5432/openwatch?sslmode=disable
EOF
sudo chown root:openwatch /etc/openwatch/secrets.env
sudo chmod 0640 /etc/openwatch/secrets.envUse
sslmode=require(or stronger) for any PostgreSQL that is not on the loopback interface.
This creates the schema (hosts, scans, transactions, audit events, the job
queue, and more). Run it as the openwatch user with the same DSN the service
uses:
sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
openwatch migrateThe command applies every pending migration and reports the version it reached. Re-running it when the schema is current is a safe no-op.
This is the account you sign in with. The admin password policy requires at least 15 characters; pick a single line with no spaces.
sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
openwatch create-admin --username admin --email admin@example.com
# Type the admin password at the prompt and press Enter.create-admin reads the password from stdin when --password is omitted, which
keeps it out of your shell history. For automation, pipe it instead:
printf '%s' "$ADMIN_PASSWORD" | sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
openwatch create-admin --username admin --email admin@example.comOn success it prints created admin user admin (admin@example.com) with id=… and
assigns the built-in admin role.
sudo systemctl enable --now openwatch
sudo systemctl status openwatchConfirm the API is healthy, then open the UI:
curl -k https://localhost:8443/api/v1/health
# {"status":"healthy","db_connected":true,"version":"<your installed version>"}In a browser, go to https://<host>:8443/. The browser warns about the
self-signed cert — accept it (or install a CA cert first; see
Replace the demo TLS cert) — then sign in with the
admin username and password from Step 6.
The -k flag and the browser warning both come from the bundled self-signed
cert. Replace it before any non-loopback use.
The flow is identical to the RPM path; only Steps 1–3 differ.
sudo apt update
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresqlsudo -u postgres psql <<'SQL'
CREATE ROLE openwatch WITH LOGIN PASSWORD 'replace-with-a-strong-password';
CREATE DATABASE openwatch OWNER openwatch;
SQLUbuntu's default pg_hba.conf already allows scram-sha-256 for
host all all 127.0.0.1/32, so no edit is needed unless you customized it.
Verify:
PGPASSWORD='replace-with-a-strong-password' \
psql -h 127.0.0.1 -U openwatch -d openwatch -c '\conninfo'sudo apt install -y ./openwatch_*_amd64.deb ./kensa-rules_*_all.debInstall both files together — openwatch Depends on kensa-rules (the
scan engine's rule corpus at /usr/share/kensa/rules), so installing the
openwatch .deb alone fails the dependency check by design. The kensa-rules
package is Architecture: all (one file for every arch). Use the openwatch
filename you downloaded (arm64 for aarch64). If apt reports missing
dependencies, add -f. The packages create the openwatch user, install the
same files as the RPM plus the corpus, and reload systemd without starting
the service.
dpkg -l openwatch
openwatch --versionFollow Steps 4 through 8 from the RPM section above — configure
/etc/openwatch/secrets.env, run openwatch migrate, run
openwatch create-admin, systemctl enable --now openwatch, and sign in at
https://<host>:8443/. The commands are the same.
Once you are signed in:
- Add a host. Provide the hostname/IP and an SSH credential (key or password). OpenWatch checks reachability and discovers the OS.
- Confirm the credential. The host's liveness and intelligence panels populate once the credential works.
- Run a Kensa scan and read the compliance posture, then drift and exceptions over time.
- Add more administrators or scoped roles from Settings as needed.
For the day-to-day workflows, see the operator guides under
docs/guides/ (hosts and remediation, scanning and compliance,
user roles). For the API, see api/openapi.yaml.
sudo systemctl start openwatch # start
sudo systemctl stop openwatch # stop
sudo systemctl restart openwatch # restart
sudo systemctl status openwatch # current state
sudo systemctl enable openwatch # start at boot
sudo systemctl disable openwatch # don't start at bootThe service logs JSON to journald:
sudo journalctl -u openwatch -f # tail live
sudo journalctl -u openwatch --since '5 min ago' # recent
sudo journalctl -u openwatch -o cat | jq . # pretty-print JSONsudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) \
openwatch check-configThe package ships a self-signed cert. Replace it with one from your CA for any non-loopback use:
sudo cp /path/to/your-cert.pem /etc/openwatch/tls/cert.pem
sudo cp /path/to/your-key.pem /etc/openwatch/tls/key.pem
sudo chown root:openwatch /etc/openwatch/tls/cert.pem
sudo chown openwatch:openwatch /etc/openwatch/tls/key.pem
sudo chmod 0644 /etc/openwatch/tls/cert.pem
sudo chmod 0600 /etc/openwatch/tls/key.pem
sudo systemctl restart openwatchThe server reads the cert on every TLS handshake, so swapping the files takes effect for new connections without a restart; restart anyway to cover existing keep-alive connections.
Config values resolve in this order, highest precedence first:
- CLI flags (
--listen,--log-level) - Environment variables (
OPENWATCH_<SECTION>_<KEY>) - The TOML file (
/etc/openwatch/openwatch.toml) - Built-in defaults
Recognized environment variables:
| Variable | Effect |
|---|---|
OPENWATCH_SERVER_LISTEN |
Override [server].listen (default :8443) |
OPENWATCH_SERVER_TLS_CERT |
Override [server].tls_cert |
OPENWATCH_SERVER_TLS_KEY |
Override [server].tls_key |
OPENWATCH_DATABASE_DSN |
Override [database].dsn |
OPENWATCH_DATABASE_MAX_CONNECTIONS |
Override [database].max_connections |
OPENWATCH_LOGGING_LEVEL |
debug / info / warn / error |
OPENWATCH_LOGGING_FORMAT |
json / text |
sudo systemctl status openwatch
sudo journalctl -u openwatch --since '1 min ago' -p err| Symptom | Cause | Fix |
|---|---|---|
config: env override: OPENWATCH_DATABASE_DSN: … |
Malformed DSN in secrets.env |
Use postgres://user:pass@host:port/db?sslmode=… |
db: ping: … password authentication failed |
Wrong DSN password, or pg_hba.conf rejects scram |
Recheck Step 2; reload PostgreSQL after edits |
db: ping: … connection refused |
PostgreSQL not running | sudo systemctl status postgresql |
server: … no such file: cert.pem |
TLS cert path or perms wrong | Ensure /etc/openwatch/tls/cert.pem is readable by openwatch |
connection refused means PostgreSQL isn't running; password authentication failed means the DSN or pg_hba.conf is wrong (recheck Step 2).
- Confirm you created the admin: re-run
openwatch create-admin(it reports if the username already exists). - The password must be at least 15 characters and was read as a single line — re-create the admin if you're unsure what was stored.
- Make sure you're using
https://(nothttp://) and accepted the cert.
curl -k https://localhost:8443/api/v1/health
# {"error":{"code":"server.unavailable",…}}The database ping inside /health failed. Check journalctl -u openwatch for
the underlying error.
Upgrading is one command. Download the newer openwatch package (and the newer
kensa-rules package if the rule corpus moved) and install it the same way you
did originally:
# RHEL family
sudo dnf install -y ./openwatch-<new>.x86_64.rpm ./kensa-rules-<new>.noarch.rpm
# Debian / Ubuntu
sudo apt install -y ./openwatch_<new>_amd64.deb ./kensa-rules_<new>_all.debOn an upgrade (and only on an upgrade — never on a fresh install) the package post-install step runs the upgrade helper, which:
- Checks the database is reachable. If it is not, it leaves the service alone,
prints how to finish later (
openwatch migrate && systemctl restart openwatch), and does not fail the package transaction. - Stops the service so the old binary never runs against a half-migrated schema.
- Takes a full
pg_dumprestore point into/var/lib/openwatch/backups/before touching the schema. If the backup fails, it aborts without migrating (fail-closed) — your data is untouched. - Applies any pending migrations, then starts the service again.
If a migration fails, the helper leaves the service stopped and exits non-zero so the package manager surfaces the problem, and it prints the restore path. Your data is intact (each migration runs in its own transaction and rolls back on error). After fixing the cause:
openwatch migrate # re-apply; reads the same DSN from secrets.env
sudo systemctl start openwatchTo preview what an upgrade would apply without changing anything:
sudo -u openwatch openwatch migrate --statusTunables live in /etc/openwatch/upgrade.conf (a noreplace config file):
AUTO_BACKUP=yes|no toggles the pre-migration dump, and
BACKUP_RETENTION_DAYS controls pruning. A systemd timer
(openwatch-backup-cleanup.timer) prunes old dumps daily but always keeps the
most recent one regardless of age.
Scope: this automates the OpenWatch application schema only. A PostgreSQL engine major-version upgrade (for example PostgreSQL 15 -> 16) is a separate, operator-supervised
pg_upgradeand is never triggered from a package scriptlet. Seespecs/release/upgrade.spec.yamlfor the full contract.
sudo systemctl stop openwatch
sudo dnf remove -y openwatchConfig under /etc/openwatch/ is preserved (%config(noreplace)). Remove it
manually if you won't reinstall:
sudo rm -rf /etc/openwatch /var/lib/openwatch /var/log/openwatch
sudo userdel openwatch && sudo groupdel openwatchsudo systemctl stop openwatch
sudo apt remove openwatch # leaves /etc/openwatch in place
sudo apt purge openwatch # also removes the packaged configapt purge removes the packaged openwatch.toml but leaves secrets.env and
the TLS material; remove those manually if needed.
Removing the package does not touch PostgreSQL. To reclaim that space:
sudo -u postgres psql <<'SQL'
DROP DATABASE openwatch;
DROP ROLE openwatch;
SQL- Operator guides:
docs/guides/— hosts and remediation, scanning and compliance, user roles. - API contract:
api/openapi.yaml— every endpoint with its required permission, license gate, and audit events. - Behavioral specs:
specs/. - Release process:
docs/runbooks/RELEASING.md.
UI + API https://<host>:8443/ (API under /api/v1/…)
TLS cert /etc/openwatch/tls/{cert,key}.pem (self-signed by default)
Config /etc/openwatch/openwatch.toml
DB secret /etc/openwatch/secrets.env (OPENWATCH_DATABASE_DSN)
Service unit /etc/systemd/system/openwatch.service
Binary /usr/bin/openwatch
Data / logs /var/lib/openwatch /var/log/openwatch (journald is primary)
User/group openwatch:openwatch
Migrate sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) openwatch migrate
Create admin sudo -u openwatch env $(cat /etc/openwatch/secrets.env | xargs) openwatch create-admin --username admin --email you@example.com
Logs journalctl -u openwatch -f
Restart sudo systemctl restart openwatch