Documentation
Release notes
Only changes that affect running Torwarden are listed here. Website copy, build tooling and internal tidying are not releases and are left out.
Read the notes before upgrading. Schema migrations are one-way, so take a backup first — how to back up. Re-running the installer upgrades in place and keeps your configuration.
0.4.23 – 0.4.24 — 6 September 2026
Security fixes. Upgrade, and review who still holds API tokens. Two of these concern authorisation and one concerns alerts you may never have received.
Fixed: disabling or demoting someone left their API token working
A token was checked only against its own expiry and revocation. It was never matched against the account that owns it, and it carried a copy of the role it was given when it was made — so disabling an account left its tokens authenticating, and demoting an administrator left a credential that was still an administrator. Offboarding somebody removed their sign-in and not their access.
A token is now bounded by its owner on every request: it stops working when the account is disabled, and never grants more than that person currently has. Tokens created through the break-glass console have no owner and are unaffected. Sessions were already correct.
Worth checking. Settings → Users lists tokens and their owners. Any token belonging to somebody who has since left or been demoted was still live until this release.
Fixed: any signed-in user could revoke anybody's API token
The revoke route accepted any authenticated role and the handler took the token id on trust, without checking who owned it. A viewer — the most restricted role there is — could switch off an administrator's automation by guessing an id. The interface never offered the control; a direct request did not need it to. Revoking now requires owning the token or administering the instance, and the check sits in the store where the route cannot bypass it.
Fixed: warnings raised during quiet hours were discarded, not delayed
Quiet hours recorded a warning as “held until 07:00” and dropped it. Nothing queued it, and the alert was marked as notified regardless — so with no repeat interval it was never sent at all, and when the condition cleared the operator received a recovery for a problem they had never been told about. A held alert is no longer recorded as sent, so the next evaluation after the window delivers it. Critical alerts were never held and are unchanged.
Fixed: an interrupted upgrade could leave the server stopped, unrecoverably
The installer stopped the service and replaced the binary before fetching the systemd unit. A network failure at that point left monitoring switched off — and re-running answered “already at this version; nothing to do”, because by then it was, so the obvious remedy did nothing. Everything that can fail over the network now happens before anything is stopped, and a re-run that finds the service enabled but not running starts it.
Also
- The server installer compared the systemd unit with
cmp, which is absent from a minimal Rocky or UBI image; a missing command exits 127, which read as “they differ”, so every upgrade on such a host claimed the unit had been edited by hand and declined to install it.
0.4.22 — 5 September 2026
Found by installing the product the way a customer does: a clean machine, the published binaries, and only what the website says.
Fixed: a standing rule conflict flooded the log
When two rules match the same metric on the same host with equal specificity, the server says so — and said so on every evaluation, for every host. Two overlapping rules across five hundred hosts on a thirty-second tick is the better part of a million identical lines a day, which informs nobody and buries what does. It is reported once now, again if it changes, and again if it returns after being resolved.
Fixed: upgrading an agent restarted it every time on a minimal image
The upgrade path compares the installed service definition with the published one to decide
whether it needs replacing, using cmp — which lives in diffutils
and is absent from a minimal Rocky or UBI image. A missing command exits 127, which the test read
as “they differ”, so on exactly the hosts this product is aimed at every re-run
rewrote the unit, restarted the agent and announced a fix that had not been needed. It compares
checksums now, with the tool that already verifies the download.
Also
torwarden-agent checkno longer tells you to start a service the installer started a moment ago; it says the agent is running.- The server installation page gives the command to download the installer. It opened with
sudo sh install-server.shand never said where that came from — the homepage had it, the documentation did not. - An unrecognised rule scope submitted directly to the form is no longer answered with the
database's own
CHECK constraint failedtext and error code.
0.4.11 – 0.4.21 — 4 September 2026
A day of work on the agents, released in stages. Everything below is in 0.4.21; the versions each part first shipped in are listed at the end, since every one of them remains downloadable.
Windows hosts enrol with a token, like Linux ones always have
Fleet enrolment shipped for Linux in 0.4.0 and never reached Windows: the PowerShell installer
demanded a per-host API key, so an estate with Windows Servers had to add every machine in the
dashboard by hand. It now takes -EnrolmentToken, or reads
TORWARDEN_ENROLMENT_TOKEN from the environment, and the host registers itself —
including keeping its history when a hostname is enrolled a second time.
Re-running it with no server and no credential upgrades an already-enrolled host in place, which
is the form to put in a scheduled job: the fleet-wide token no longer has to live on every machine
just to install a new binary. -Version pins a release, so an estate can qualify one
build and roll exactly that. There is deliberately no -NoRestart on Windows — a
running executable is locked, so the service has to stop before the file can be replaced; schedule
the whole install inside the maintenance window.
Keep the token out of the upgrade job's environment. Both installers read a
token they can see as an instruction to enrol rather than upgrade, so a job that exports
TORWARDEN_ENROLMENT_TOKEN is not running the credential-free form at all. What
happens next depends on the rest of the command: on its own it stops with
“--url is required”, and alongside a server address it re-registers the
host and issues it a new key every time it runs. Set the variable only where hosts are first
installed. On Windows, prefer the variable over
-EnrolmentToken for the initial install too: process creation auditing, Sysmon and
PowerShell transcription all capture command lines, into logs kept for years.
The Windows installer never actually registered the service.
sc.exe create exits 1639 — invalid command line — under Windows
PowerShell 5.1, the host our own instructions name, and the script discarded that and checked
nothing. Every Windows install therefore downloaded the agent, verified it, wrote its
configuration and then failed with “Cannot find any service with service name”. It
also used a name the agent itself does not know, so the torwarden-agent stop and
uninstall commands in our documentation addressed a service that was never there.
The installer now asks the agent to register its own service. Re-run it on any Windows host to
get a working one.
The agent keeps what the collector will not take
A snapshot that failed to send was gone, so any minute the collector was unreachable was a minute permanently missing from the history — and a host with no samples is marked not reporting, which the availability export counts as downtime. Restarting the server for its own upgrade therefore wrote a fleet-wide outage into the report an auditor reads. Undelivered snapshots now wait on disk and go out oldest-first when the collector answers, a bounded number at a time so a fleet does not replay days of history at a server that has only just started.
Linux agents need their service definition updated, not just the binary. The
unit runs under ProtectSystem=strict, so the agent has nowhere to write until it
declares StateDirectory=torwarden-agent. sudo sh install-agent.sh with
no arguments updates both. An agent with a newer binary than its unit keeps running and keeps
shipping; it just cannot buffer, and says so at startup and in
torwarden-agent check. If you installed 0.4.14 specifically, upgrade: buffering as
released there could stop an agent shipping altogether when the collector refused one queued
snapshot permanently.
Alerts carry a link to what fired
Links in notifications are built from the Base URL setting, which defaulted to empty — and nothing ever set it, because notifications are sent from a background tick with no browser request to learn an address from. Every alert therefore went out with nothing to click, across email, Slack, Teams, Discord and webhooks alike. A server now records an address when the first administrator is created, and one already past that point falls back to the name on its certificate. Where neither applies, set Base URL under Settings → General — it is left empty rather than guessed, because a link that goes nowhere is worse than none.
No password in the example configuration
server.conf.example contained admin_password = changeme, and an example
is copied verbatim. Worse than a weak password alone: configuring the console credential also
stops the server printing its one-time setup link, so the usual path to a real account never
appears and the install looks finished. The example now leaves that pair commented out, and a
server started with a well-known password says so at every start.
Installs made with install-server.sh were never affected. It
writes its own configuration with no administrator credential, so those servers printed the
setup link as intended. This reached only installs configured by hand from the example.
Tighter permissions on both sides
On the server, fleet.db and every metrics and rollup partition were created at
whatever the umask allowed — 0644 under systemd — inside 0750 directories. Upgrading
narrows them, history included: restricting only what a running server creates would have left
every partition whose day has closed at its old mode, which is up to a week of raw samples and
months of rollups. Nothing was reachable through those files, but the directory was
the only thing standing there, and a restore or a bind-mount can widen one without anyone thinking
about the files inside. Those bits are removed at startup now.
The secret key is treated the same way where it is readable machine-wide, and that one is
reported in the log rather than repaired quietly — it decrypts the TLS private key and every
stored channel credential, so a key that was readable may already have been read. A key
deliberately provisioned outside the data directory and shared with the group the server runs as,
which is what secret_key_file is for, is left alone: an alarm that fires on every
restart is how the real one gets ignored.
On Windows, agent.conf holds the host's ingest key in plain text and was written
before it was hardened, so for a moment the key was readable by every local account — a file
created under %ProgramData% inherits a read for BUILTIN\Users. The
directory is locked before anything is written into it now, and the permissions are rebuilt on
every run rather than added to, since removing inheritance leaves an explicit grant in place.
The spool the agent buffers into is on the same footing, and on Windows it was not: snapshots
list processes, filesystems and addresses, and a directory created under
%ProgramData% is readable by every local account by inheritance. Any host that
installed 0.4.14 has readable snapshots on disk until the installer is re-run.
Re-running the installer repairs permissions but does not rotate a key. If a Windows host's key may have been read, re-enrol it with an enrolment token.
Also
- A
spool_max_bytesthat would leave the spool uncapped, or one too small to hold a single snapshot —8from someone meaning 8 MiB — is corrected to the default and noted, rather than obeyed and rather than refused. Obeying the small one is the worse trap: the buffer deletes each snapshot as it arrives and reports itself healthy. An agent that will not start is a host that stops reporting, so neither is worth refusing over. torwarden-agent checkno longer suggests checking file permissions when the problem is a value inside the file it just read.- The installer says where to download itself — both the site and the documentation
previously showed
.\install-agent.ps1as though it were already on the machine. - Documentation gained an upgrading agents section for both platforms, and when the server is unreachable.
408and429from a proxy in front of the collector are treated as worth retrying rather than as a reason to discard the sample.
Released as: 0.4.11 Windows token enrolment and the
service registration fix · 0.4.12 credential-free Windows upgrades and
-Version · 0.4.13 base URL, the example configuration and database file modes
· 0.4.14 agent buffering · 0.4.15 the buffering fixes above · 0.4.16 Windows
configuration permissions · 0.4.17 and 0.4.18 the spool cap corrections · 0.4.19
file modes on newly created metrics and rollup partitions · 0.4.20 the same for partitions
already on disk · 0.4.21 a secret key narrowed only where it is readable machine-wide, and
that notice reaching the command-line tools as well as the server.
0.4.10 — 3 September 2026
Fixed: a monitored machine could put a formula in your audit export
The CSV exports wrote hostnames, rule names and entity labels into cells exactly as stored. A
cell beginning =, +, - or @ is a formula to
Excel and LibreOffice, and an entity label is built as
mount point + “ on ” + hostname — where the mount point comes
from an agent's own report and is not character-checked at all. So a single compromised machine,
holding nothing but its own metrics key, could get a formula into a file whose stated purpose is
being emailed to an auditor. Every text cell in both exports is now neutralised, and a hostname
can no longer begin with a formula character either.
Fixed: the enrolment token was visible to every local account during install
The installer passed the token to curl as a command-line argument, and arguments are
readable through /proc by any account on the machine for as long as the process runs.
It is now handed over on standard input and never appears in a command line.
The advice in the previous documentation was wrong. It said the
TORWARDEN_ENROLMENT_TOKEN variable kept the token out of ps. That is
true of the shell but not of the commands it runs, and the form we suggested —
sudo TORWARDEN_ENROLMENT_TOKEN=… — put the token in sudo's
arguments for the whole install, which was the longest exposure of the lot. Export the variable
and run as root, or let configuration management set it in the environment.
This token can claim an existing hostname and take over a monitored host's identity. If it has been through a command line on a machine where anyone else has an account, rotate it under Settings → Agents; agents already enrolled are unaffected.
0.4.9 — 3 September 2026
Upgrade if you have taken a backup with 0.4.8, or exported an audit pack. Two of the fixes below are silent: one produces a backup that restores into an instance whose stored credentials can no longer be decrypted, the other leaves server outages out of an export that was asked for them.
Fixed: a backup could lose the secret key
Only on instances using secret_key_file to keep the key off the data directory
— which is what a careful operator does. The key was archived at the wrong path and nothing
was written at the right one, so a restore found no key and the server generated a fresh one on
start, without saying so. Every stored channel credential, directory password and the HTTPS
private key then decrypt to nothing. The key is now archived wherever it actually lives.
Fixed: “critical” left server outages out of the incident export
A host going down fires the built-in reachability rule, which has no thresholds and resolves
through the no-data path, so the episode records no_data rather than
critical. Filtering the export to critical events therefore dropped every server
outage — from a file requested precisely to show server outages, and silently. Critical now
covers no_data and unreachable as well.
Fixed: the reconciliation column ignored what your targets count
new_downtime_seconds always used the three availability signals and always
subtracted planned maintenance, whatever the availability targets were set to. On the default
configuration — reachability only — it credited downtime to endpoint outages the
availability figure ignores entirely, so the column that exists to make the two halves of an
audit pack agree made them disagree on the commonest setup there is. It now follows the targets:
which signals they count, and whether they exclude maintenance.
Fixed: a period could run into the future
Asking for the current quarter measured availability over the days that have not happened yet, counting unmonitored future time as healthy, while the incident export measured an open outage to the end of the quarter and reported downtime that had not happened. The two contradicted each other in the same pack, in opposite directions. A period now stops at the present.
Fixed: a backup could report success after failing to finish
The archive's trailer is written when the file is closed, and that error was discarded — so a volume filling during the final write produced a truncated archive, and the command printed “Wrote” and exited zero. A scheduled backup would have reported success indefinitely. It now checks every close, and flushes to disk before reporting.
Two smaller ones alongside: a metric partition expiring mid-backup is skipped with a note rather than aborting the whole run, and a failure to stat the finished archive is reported instead of crashing.
0.4.8 — 3 September 2026
Added: backup without stopping the server
The documented procedure was to stop the service, archive the data directory and start it again. That is correct and it is unusable: nobody schedules downtime on a monitoring server to take a backup, so the backup does not get taken — and the one machine that cannot watch itself is the one that most needs restoring.
sudo torwarden-server backup --to /var/backups/torwarden-$(date +%F).tar.gz
Each database is snapshotted with SQLite's VACUUM INTO, which runs inside a read
transaction, so what lands is the database as of a single instant — compacted on the way
— while writes carry on. Every copy is integrity-checked before the archive is written.
The secret key and the configuration file are included, and the archive unpacks with the same
tar xzf … -C / as before. Backups.
Copying the files yourself while the server runs is not an alternative. A database in WAL mode is a file, a write-ahead log and a shared-memory index; copying the three at three different instants produces something that opens, looks right, and is missing whatever was in flight. That is worse than no backup, because it is a backup somebody trusts.
The archive records ownership by name as well as by number. That detail is the whole difference
between a restore that works and one that does not: torwarden is a different user id
on Rocky than on Debian, and an archive carrying only numbers restores the data onto whichever
account happens to hold that number on the target — after which the server cannot read its
own configuration.
0.4.7 — 3 September 2026
Added: availability for a period that has already ended
A target's window always ends now — a rolling one is the last so many days from this moment, a calendar one runs to this instant — so neither could answer “what was our availability last quarter”, which is the only form the question takes. The incident export already took a period; this one did not, so the two halves of an audit pack could not be made to cover the same months.
Availability now has the same date pickers. Give a period and every target is
measured over exactly that, instead of the window it is configured with; leave them empty and
nothing changes — you get how things stand, over each target's own window, which is what
the page shows. Two new columns, period_start_utc and period_end_utc,
say which it was, because two targets in one file can legitimately have different windows and the
filename can only carry one answer.
A file covering a period is named for it, and a snapshot is named for the day it was taken, so the two cannot be filed as the same thing.
0.4.6 — 3 September 2026
Fixed: the outage list and the availability figure did not add up
The availability calculation merges overlapping outages and subtracts planned maintenance before it reports a percentage. The export listed raw events and did neither, so summing its durations gave a larger number than the availability page showed for the same period — two figures disagreeing inside one audit pack, which is worse than either being slightly wrong.
Two columns fix it. in_maintenance_window marks events inside a planned window,
read from the same rows the availability figure subtracts. new_downtime_seconds is
what each event actually added: an endpoint with both an HTTP and a TCP check has two rules on one
entity, and when it goes down both fire, so the second event is credited only with the part the
first did not already cover. Threshold events get nothing in that column rather than a zero,
because a zero invites being added up.
Which figure ties to which. new_downtime_seconds reconciles to
the per-host rows of the availability export. The overall figure at the top is lower,
because it merges across hosts too: two machines down at the same moment is one period during
which the scope was not fully serving, not two outages added together.
Added: a third answer to “what counts as down”
An availability target could count only unreachable hosts, or every critical alert. Neither could express a promise about a service rather than a machine — “the payment gateway was reachable” — because the first ignores endpoint checks entirely and the second makes a full disk into downtime. There is now Unreachable, or an endpoint probe failing, which counts host reachability and the two endpoint probes and nothing else. Existing targets are untouched.
0.4.5 — 3 September 2026
Fixed: the audit export called a threshold an outage
0.4.4 exported every alert episode as an “incident” with a duration. A disk reaching 90% raises a critical alert and is not downtime — the service was up for every second of it — so a certificate warning that had been open for three hours appeared in an audit pack looking like three hours of unavailability. Handing that to an auditor is worse than handing them nothing.
Every row now says which it is. Availability covers the three signals that answer “was it serving” — host reachability and the two endpoint probes — and is the same definition the availability calculation already used, so the two cannot drift into disagreeing about what an outage is. Threshold is everything else: real alerts, worth acting on, and not downtime.
Added: choosing what to export, without editing a URL
The export button on Alerts now asks what you want before it gives you a file: outages only (the default), everything, or thresholds only; any severity or criticals only; and the period, as two dates. 0.4.4 had the period but only as a query string, which is not a feature anybody can find.
The filename follows the choice —
torwarden-outages-20260701-to-20260930.csv against
torwarden-events-… — because two files with the same name saying different
things is how the wrong one gets attached to an email.
0.4.4 — 3 September 2026
Added: audit exports
A regulated operator has to produce two things periodically and could produce neither from Torwarden: what their availability was over a period, and what happened during it. Both were already measured; neither could be got out of the screen it was drawn on. There is now an Export CSV button on Availability and on Alerts.
| Export | What is in it |
|---|---|
/availability.csv | Each target's overall availability and then every host inside it: measured percentage, whether the target was met, downtime, the window, how much of it there is data for, and the error budget. |
/alerts/incidents.csv | Every incident: host, entity, rule, metric, severity, when it started and resolved, how long it lasted, whether it is still open, when it was acknowledged and how long that took, how many notifications went out, and how it was closed. |
The incident export takes a period — ?from=2026-07-01&to=2026-09-30, dates
in UTC, the last day included — and defaults to the previous thirty days. It reports
anything that overlapped the period rather than anything that started inside it: an
outage that began on 30 September and ended on 2 October happened in the third quarter, and it is
usually the one being asked about. An incident still open at the end of the period is measured to
the end of the period, so a report of last quarter does not grow every time it is run.
CSV rather than PDF, deliberately: the first thing anyone does with this is filter it, total it and paste it into their own workbook. Timestamps are ISO 8601 in UTC so a spreadsheet parses them without being told, and every duration appears both in seconds and as text, because one column gets summed and the other gets read out in a meeting.
0.4.3 — 3 September 2026
Added: Microsoft Teams notifications
Alerts can now be routed into a Teams channel, alongside email, Slack, Discord and plain webhooks. It sends an Adaptive Card carrying the same rows every other channel shows — host, entity, rule, metric, value, threshold, state, why it fired and when — with the severity in the card’s own colour so it reads correctly in both the light and dark Teams themes, and a button through to the alert.
Use a Workflow, not an Office 365 connector. In Teams: the channel menu → Workflows → Post to a channel when a webhook request is received, then paste the URL it gives you. Microsoft is retiring connectors, and their URLs cannot render this card — so pasting one is refused when you save the channel, with a message saying what to create instead, rather than failing at three in the morning.
For the link in the notification to work, set Base URL under Settings. Without it every channel sends alerts with no way back to the dashboard, which is the most common reason alerts stop being read.
0.4.2 — 3 September 2026
Added: endpoints and certificates are alerted on out of the box
Every rule that shipped before this watched machines — processor, memory, swap, load, three filesystem rules and agent reachability. Nothing watched the things Torwarden probes from the outside. So you could add a website check, watch it go down, and be told only by a red row on a page you had to be looking at. Certificates were worse: warning about expiry is something this product is sold on, the day thresholds existed and coloured the certificates page, and nothing carried them into alerting.
Three rules now ship, and like Agent not reporting they can be retuned or switched off but not deleted:
| Rule | Fires when |
|---|---|
| Endpoint not responding | An HTTP check fails twice out of three probes — about two minutes at the default interval, so one blip does not page anyone. |
| TCP endpoint not responding | The same, for a port check. |
| Certificate expiring | 14 days before a monitored certificate expires, escalating at 7. Certbot renews at 30 days on a 90-day certificate, so anything still unrenewed at 14 means automatic renewal has already failed. |
They apply to endpoints only, and they do it without any endpoint-specific machinery: a machine
has no http.up reading at all, and the rules treat a missing reading as nothing to
say rather than as a fault. Existing installs get them on upgrade, and any thresholds you have
already set are untouched.
0.4.1 — 3 September 2026
Upgrade if you serve over HTTPS. A server restarted while something else held the database could come back on plain HTTP, saying so in one line of log. A restart during your own backup was enough to do it.
Fixed: a busy database could silently disable HTTPS
The HTTPS settings are read once at start-up. When that read failed — SQLite answers
SQLITE_BUSY while another process holds the write lock, which at start-up means a
backup, a migration or a restore finishing — the server logged
could not read the HTTPS settings and carried on serving plain HTTP, with the
certificate installed and HTTPS switched on.
Every other way HTTPS can fail still degrades to plain HTTP deliberately, and that is right: a
monitoring server nobody can reach is worse than one that is not yet encrypted, and the settings
page is where the fix is. But a read that fails is not a bad configuration, it is an unknown one.
The read is now retried for five seconds, and if it still fails the server refuses to start
rather than guess. To come up on plain HTTP deliberately, start it with -no-tls,
which does not consult the database and so still works when that is what is wrong.
Added: the host list is paged, and you choose the page size
It used to render every host. At five hundred that was 1.7 MB of HTML, twenty-four thousand elements and a table forty-four thousand pixels tall — about forty-four screens — taking a second to become usable. It now shows a hundred rows by default, and 10, 20, 50 or 100 on request. The same fleet is 365 KB at a hundred rows and 60 KB at ten.
The page size, the page number, the filter, the status and the sort all live in the URL, so a view survives a reload and can be sent to someone else. Sorting is by severity by default, so what needs attention is on the first page whatever the size, and the status counts above the table count the whole fleet rather than the page you are looking at.
GET /api/v1/hosts is unchanged and still returns every host.
0.4.0 — 2 September 2026
If you have ever taken a backup, read the first note below. The backup command these pages used to give could not be restored by any single command. Take a fresh one with the corrected instructions and confirm it restores.
Fixed: the documented backup could not be restored
The command in the server documentation archived the data directory relative to
/var/lib and the configuration relative to /. Those are two different
roots in one archive, and no single tar x -C puts both back where they belong:
unpacked at the root, the databases land in /torwarden and the server refuses to
start, because systemd cannot find the data directory its unit names. Both paths are now relative
to /, the restore side is written down, and the round trip is tested —
including onto a different machine and a different distribution.
Backups.
Added: agents can enrol themselves
Adding a host used to mean opening the dashboard, naming the machine and copying the key it showed once. That does not scale past a handful, and it made each key precious: lose one and the only way back was deleting the host, which threw away its history.
Set an enrolment token under Settings → Agents, turn auto-registration on, and the same command installs every machine in the estate — no per-machine secret to distribute. Registering a name that already exists re-keys that host rather than creating a second, so a rebuilt machine keeps its id, its metrics, its alert history and its SLA. Its previous key stops working at that moment, and the change is logged and audited. Turning auto-approve off lets hosts arrive with monitoring disabled, taking no licence seat until you enable them. Enrolling a fleet.
Fixed: the install command the dashboard handed out
It hardcoded http://, so a server whose operator had configured HTTPS still gave
out a plaintext address, and one reached by IP gave out an address no certificate covers. It also
bypassed the installer, and with it the checksum verification, and pointed at
releases.torwarden.com even on estates with no route to the internet. It now runs
the real installer against the name on your certificate, and names the variable that points it at
an internal mirror.
Fixed: an untrusted certificate was reported as an unreachable server
torwarden-agent check called a certificate it would not verify “cannot reach
the server” and suggested checking DNS and firewall rules — the two things that are
demonstrably fine, because the connection got as far as a handshake. It now separates the three
certificate failures, each of which is fixed somewhere different: an issuer this machine does not
trust, a certificate that does not cover the name being dialled, and one that has expired.
Added: how to trust a private certificate authority
Most on-premises servers use one, and the trust store paths differ between the RHEL and Debian
families — a .pem extension is silently ignored on Debian and Ubuntu, which is
the commonest way this is got wrong. Both are now written down, with the commands.
When the server uses a private CA.
Fixed: the host list mixed endpoints in with machines
GET /api/v1/hosts returned website and certificate checks alongside servers, with
every machine field blank and nothing in the response to tell the two apart. It returns machines
now, matching the hosts page, the overview and the licence seat count; endpoints have their own
collection. It also answered null rather than [] on a fleet with no
hosts, which is the state every new install starts in.
Added: endpoints can be read and changed through the API
GET and PATCH on /api/v1/endpoints/{id}. They could be
created and deleted but never read back or edited. A PATCH applies only what it
names, so changing an interval does not silently switch off certificate checking or certificate
verification.
Fixed: the sign-in page advertised the console credential
It said the administrator from server.conf also works there and is the way in when
everything else is misconfigured — on the one page anybody who can reach the server can
read. Anyone entitled to that credential can find it in the file.
Fixed: re-running the agent installer failed on minimal images
The installer computed a hostname it was not going to use, and hostname is a
separate package on the RHEL family that is absent from UBI and hardened builds. A scheduled
re-run exited 127 and the agent silently stayed on the old version. Installing and upgrading are
now tested on Rocky 9, Debian 12 and Ubuntu 24.04.
0.3.0 — 2 September 2026
Fixed: endpoints created through the API never ran
The API decoded the request straight into the endpoint record, so any field a caller left out
became false — including enabled. An endpoint created through the API arrived
switched off and was never checked, with nothing to say so, while the same endpoint created in
the interface worked because the form fills those fields in. Anyone scripting a bulk import got a
set of checks that did nothing.
verify_tls was the worse half: it defaulted off the same way, so an HTTPS check made
through the API would not have verified the certificate it was checking. A minimal request now
takes the same defaults as the form, and an explicit false is still honoured.
Fixed: endpoint checks ignored the proxy environment
The prober built its own HTTP client, which starts with no proxy configured. On a network where
outbound traffic goes through a proxy — most of the estates this runs in — every check
of an external URL failed to connect, while the same URL worked from a shell on the same machine.
Checks now honour HTTPS_PROXY, HTTP_PROXY and NO_PROXY, as
the notification channels already did. See
running behind a corporate proxy.
Fixed: the overview counted endpoints as hosts
Endpoint and certificate checks were counted alongside machines on the landing page. A website check has no CPU and no memory, so each one showed as unknown in the status band and sat at 0.0% in Busiest CPU and Most memory. On an estate with fifty endpoints the overview was mostly padding. It counts machines now; endpoints have their own pages.
Fixed: no navigation on a phone
Below 860 pixels the sidebar was hidden with nothing in its place, so the dashboard had no navigation at all on a phone: the toggle worked, but there was nothing for it to reveal. The rail is now a drawer that slides in, and closes on tapping away, following a link, or pressing Escape.
Fixed: empty lists returned null
GET /api/v1/endpoints and /api/v1/certificates answered
null rather than [] when nothing was configured, which breaks a client
that loops over the response. Other list endpoints already returned an empty array.
Added: managed accounts
An account can be marked managed, meaning an operator sets its password and the person signing in cannot change it. For logins that are handed out and shared — a wall display in an operations room, a read-only account for auditors — where one person changing the password locks out everyone else who was given it. The profile page hides the form rather than showing one that would only fail.
Added: a notice on the sign-in page
A new setting puts free text under the sign-in form: how to request an account, who to call, or which credentials a shared instance expects. Empty by default, so nothing renders unless you set it.
0.2.0 — 1 September 2026
Licensing now checks in
A licensed server validates its licence key against Polar roughly twice a day. This replaces the signed-file licence, which could never be withdrawn: a key issued for an order that was later refunded stayed valid for ever.
What this means in practice:
- The server needs outbound HTTPS to
api.polar.sh. Behind a proxy, see running behind a corporate proxy. - A cancelled or refunded licence returns the instance to the free limit of ten hosts at its next check. Hosts already enrolled keep reporting; only new enrolments are refused.
- If Polar cannot be reached, the last good answer stands for seven days before that happens. An outage on our side is not an outage on yours.
- The free tier makes no such request at all.
- No machine identifier, host count or inventory is sent — the licence key, and nothing else.
Fixed: upgrading did nothing
Re-running the installer replaced the binary on disk and left the old process running, because it
finished with systemctl enable --now, which does not restart a unit that is already
active. An operator applying a fix would have seen a success message while still running the old
build, and the schema migrations that run at start never ran either. The installer now stops the
service, replaces the binary, restarts it, verifies it stayed up, and prints the before and after
versions. If the new binary fails to start, it rolls back to the previous one.
Fixed: the shipped systemd unit could not start a new install
The unit restricted its capability set but did not set a user, so the server ran as root without
the right to override file permissions — and could not open the data directory the installer
had just created for it. A fresh install failed with unable to open database file. The
unit now runs as the torwarden user, which is what those capability settings were
always for.
Upgrading is safer and scriptable
- Re-running an installer when nothing has changed now does nothing at all, rather than restarting the service. Safe to run from configuration management on a schedule.
--version v0.2.0pins a specific release, so a rollout can be staged.--no-restartreplaces the binary and leaves the restart to you, for a maintenance window.- Upgrading an agent no longer asks for its API key. Run the installer with no arguments on an enrolled host and it keeps the existing configuration. Previously every upgrade meant looking up a per-host credential, which on a real estate meant agents never got upgraded.
0.1.0 — 31 August 2026
First release. Everything below shipped in it: a server and an agent, both static binaries with no runtime, no JVM and no external database.
Hosts
An agent that reads /proc and ships CPU, memory, swap, load, filesystem usage,
inodes, free space and network counters to a server you run. One static binary on the monitored
machine, and nothing written to its disk.
Endpoint checks
HTTP and TCP checks against anything reachable from the server: expected status code, keyword present or absent, redirects followed or not, per-check interval and timeout. Endpoints do not consume a licence seat.
TLS certificates and domain expiry
Certificate expiry, chain of trust and hostname verification for any endpoint, with severity tiers rather than one deadline. Verification is deliberately done in stages, so a certificate that fails to verify still reports its subject, issuer, dates and chain instead of just failing. Optional domain expiry lookups over RDAP, off by default and per certificate.
Alerting
Rules with thresholds, N-of-M sample matching and hysteresis on recovery, so a metric sitting on a boundary does not flap. Four no-data policies, guard conditions, per-entity overrides, and most-specific-rule-wins. Acknowledge with a mandatory comment, snooze with its own expiry notification, close with a reason. Maintenance windows suppress notification or detection, on a daily or weekly schedule. Dependency suppression marks a host unreachable only when every parent is down.
Notification channels
Email, Slack, Discord and generic webhooks, with per-channel rate limiting, routing rules, quiet hours that never hold a critical or a recovery, and grouping. Templates are editable, and the test button sends a real sample alert and shows the far end's verbatim error rather than "failed".
Availability and SLA
Uptime and availability reporting across hosts and endpoints, with the windows that matter for a service target rather than a single rolling number.
Dashboards
Dashboards you lay out yourself on a real grid, with variables, JSON export and import, and a server-side packer so a dashboard built through the UI, imported from a file or shipped with the product all go through the same code.
Accounts and sign-in
Local accounts with four roles, Argon2id password hashing, per-account and per-IP lockout, and sessions you can list and revoke. LDAP and Active Directory sign-in with group-to-role mapping, and a connection test that reports six stages separately with the raw error from each. API tokens capped at their creator's role.
Operations
HTTPS with certificates you upload or generate, configurable retention per resolution with a measured size projection rather than a modelled one, an audit trail of settings changes, and a diagnostic bundle built to be safe to send.