Torwarden

Install

Installing the server

One static binary, one configuration file, two SQLite databases. The server embeds its own dashboard — there is no separate web tier to deploy.

Requirements

  • Linux, x86-64 or arm64. No runtime, no packages, no JVM.
  • A port to listen on — 8080 by default, 443 once HTTPS is configured.
  • No internet access, at any point, for anything.

Sizing

What the server has to keep up with is not the number of hosts but the number of measurements per second, which is the series each host reports divided by its interval. A small virtual machine reports around 20 series; a large database server with many cores, disks and filesystems reports 50 or more. Halving the interval doubles the load exactly as surely as doubling the fleet.

FleetCPUMemoryDisk
Up to 50 hosts2 cores2 GB20 GB SSD
Up to 500 hosts4 cores8 GB150 GB SSD
Beyond 5004–8 cores16 GB+NVMe, sized from the figures below

What to spend money on

Disk first, memory second, cores last — and that order is not a rule of thumb, it follows from how the server is built.

  • Disk. Ingest is dominated by writing and flushing, so a fast SSD does more for capacity than anything else. NVMe over SATA is a real difference here.
  • Memory. Used as cache for the databases. While the working set fits, writes stay fast; when it stops fitting, ingest slows markedly — we measured it halving once the database outgrew the cache available to it.
  • Cores. Buy fast ones rather than many. The database takes one writer at a time by design — that is what keeps lock contention off the ingest path — and alert evaluation is a single pass. Sixteen cores will not take in twice what eight will.

Disk over time

Roughly 1.8 GB per day of raw samples at 500 hosts reporting every 30 seconds. The rolled-up tiers that make long ranges fast cost about twice that again, so budget around three times the raw figure for the default retention. The settings page projects all of it from what is actually on your disk rather than from this estimate, and it is the number to trust once you have a week of real data.

Measured, not estimated. On a four-core machine with 15 GB of memory, a fleet of 5,000 hosts and 270,000 series stayed comfortable to use: the host list rendered in 0.07 s with paging that did not slow down at page 50, chart queries ran in single-digit milliseconds, and a 5,000-row availability export took 0.11 s. Sustained ingest on that machine was about 4,000 measurements per second, which is roughly four to five times what 500 hosts at a 30-second interval require. Server hardware with a faster disk has more headroom again; these are figures to size from, not a ceiling.

Install

The installer is the supported path. It exists because a manual install left server.conf world-readable, and that file holds the break-glass password. Since 0.4.19 the server narrows its own databases at startup however it was installed, but nothing except the installer sets up the system user, the unit and the configuration file's ownership.

curl -fsSLO https://releases.torwarden.com/install-server.sh
sudo sh install-server.sh                    # asks what it needs
sudo sh install-server.sh --listen :8080     # or answer up front

Re-running it upgrades an existing install in place, keeping server.conf and the data directory. Downloads are verified against SHA256SUMS; set TORWARDEN_RELEASE_BASE to install from an internal mirror instead.

It creates a system user, installs to /usr/local/bin/torwarden-server, writes /etc/torwarden/server.conf at mode 0640 owned by that user, creates /var/lib/torwarden at 0750, installs a systemd unit and grants CAP_NET_BIND_SERVICE so the server can bind 443 without running as root.

On Red Hat, Rocky and Alma, call it by its full path under sudo. Those distributions set secure_path for sudo and it does not include /usr/local/bin, so sudo torwarden-server check is "command not found" on a perfectly good install. Use sudo /usr/local/bin/torwarden-server check. The service itself is unaffected — the unit uses an absolute path. Tested on Rocky 10 with SELinux enforcing, Debian 13 and Ubuntu 24.04.

Configuration

/etc/torwarden/server.conf is key = value, one per line, # for comments.

KeyDefaultWhat it does
listen:8080Address for the plain HTTP listener.
data_dir/var/lib/torwardenWhere fleet.db, the daily metrics and rollup databases, and secret.key live.
admin_usernameadminThe break-glass console administrator.
admin_password—Its password, in plaintext. This is why the file is 0640.
secret_key_file<data_dir>/secret.keyEncrypts stored credentials and the TLS private key.
retention_raw_days7Full-resolution retention. Also settable in the dashboard.

Keep the secret key file. It encrypts the TLS private key and every stored notification credential. Lose it and those cannot be decrypted — the server still starts, but you will be re-entering the certificate and the channel passwords. Back it up with the databases, and with the same care.

The first administrator

On first start the server prints a one-time setup link to its log:

journalctl -u torwarden-server -n 40

The token is valid until the process restarts and can be used once. The console administrator from server.conf also works, and is the way back in if the account system is misconfigured — but it is a recovery credential, not a login to use day to day.

HTTPS

Configure it in Settings → HTTPS, or from the command line, which is what you want for provisioning and for renewals driven by cron:

sudo torwarden-server tls import \
  -cert /etc/letsencrypt/live/torwarden.example.com/fullchain.pem \
  -key  /etc/letsencrypt/live/torwarden.example.com/privkey.pem \
  -hostname torwarden.example.com -enable

sudo systemctl restart torwarden-server

The certificate is validated before anything is stored: chain order, whether the key matches, whether it covers the hostname, and whether it has expired. A certificate that would stop the server booting is refused rather than saved.

torwarden-server tls status
subject     CN=torwarden.example.com
issuer      CN=E5
expires     2026-11-27T13:15:26Z (90 days)
https       on on :443

tls status exits non-zero when the certificate cannot be served, so it works as a cron check on the one machine Torwarden cannot watch for you.

Agents verify this certificate against the operating system trust store, so a private CA or a self-signed certificate has to be installed on every machine running an agent. The paths differ between the RHEL and Debian families and are set out in installing the agent.

If a certificate goes wrong. Start with -no-tls. It ignores the stored HTTPS configuration and serves plain HTTP, so you can reach the settings page and fix it — the way back from a certificate that will not load or HTTPS switched on before DNS was ready.

The HTTPS settings page before a certificate is installed: an empty current-certificate
            panel, the hostname and HTTPS address fields, and the redirect and HSTS options.

Behind a corporate proxy

If outbound traffic on your network has to go through a proxy, Torwarden follows the usual environment variables: HTTPS_PROXY, HTTP_PROXY and NO_PROXY. Every outbound path honours them — endpoint checks, certificate checks, notification channels, domain expiry lookups and the licence check.

The shape that works on most estates is direct for anything internal, proxy for everything else. NO_PROXY is the half that does the first part: list the hosts and ranges the server should reach without help, and let the proxy carry the rest.

sudo systemctl edit torwarden-server
[Service]
Environment=HTTPS_PROXY=http://proxy.corp.example:3128
Environment=HTTP_PROXY=http://proxy.corp.example:3128
Environment=NO_PROXY=localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.corp.example
sudo systemctl restart torwarden-server

Read that NO_PROXY line as the list of things that are yours: loopback, the RFC 1918 ranges your servers live in, and your own domain suffix. Everything not on it — a public website you are watching, the certificate on your customer portal, a Slack webhook, the licence check — goes out through the proxy.

Set these before the server starts. Go reads the proxy environment once, at the first outbound request, and caches it for the life of the process. Exporting a variable in a shell next to a running server changes nothing; the unit has to carry it and the service has to be restarted.

Two things worth checking once it is on. An endpoint check that fails with a connection error while the same URL works from curl on the same machine is almost always a NO_PROXY that is too broad or a proxy variable the unit never saw. And an internal target that suddenly starts failing has usually been sent through a proxy that is not allowed to reach it — add its suffix to NO_PROXY.

Agents are usually simpler: an agent talks only to your Torwarden server, which is normally internal, so most installations need no proxy configuration on the agent at all. If your agents sit outside the network the server is on, the same three variables work in torwarden-agent.service.

Backups

Everything that matters is in data_dir and /etc/torwarden. There is no external database and no cluster state, so a backup is one archive.

Without stopping the server:

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 one consistent instant, compacted on the way, while writes carry on. Every copy is then integrity-checked before the archive is written, because a copy nobody checked is a directory of files rather than a backup. The secret key and the configuration file go in too.

Do not copy the files yourself while the server runs. A database in WAL mode is a file, a write-ahead log and a shared-memory index; cp of 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.

Or with a maintenance window, if you would rather not have the server running at all while it happens:

sudo systemctl stop torwarden-server
sudo tar czf torwarden-backup.tar.gz -C / var/lib/torwarden etc/torwarden
sudo systemctl start torwarden-server

Restoring is the same either way — the archive unpacked at the root, on a machine where the server is installed and stopped. It does not have to be the machine it came from, or the same distribution:

sudo systemctl stop torwarden-server
sudo tar xzf torwarden-backup.tar.gz -C /
sudo systemctl start torwarden-server

Archive both paths relative to /. Taking the data directory relative to /var/lib and the configuration relative to / makes an archive that no single tar x -C can put back: the databases unpack into /torwarden, and the server then fails to start with status=226/NAMESPACE, because its data directory is not where the unit says.

The data directory holds fleet.db, one metrics database per day under metrics/, the rollups under rollups/, and secret.key. That last file is the one to be careful with: channel credentials, directory bind passwords and the HTTPS private key are all encrypted with it, so a backup taken without it restores a fleet that cannot reach its notification channels and cannot serve its own certificate.

Upgrading

Replace the binary and restart. Schema migrations run automatically at start and are forward-only.

sudo sh install-server.sh          # stops the service, replaces the binary, restarts
sudo torwarden-server check       # confirms the migrations ran

Back up before an upgrade. Migrations are forward-only and there is no downgrade path. A migration that removes a feature deletes the data behind it.