Torwarden

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

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

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

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.

ExportWhat is in it
/availability.csvEach 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.csvEvery 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:

RuleFires when
Endpoint not respondingAn 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 respondingThe same, for a port check.
Certificate expiring14 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:

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

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.