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.
| Fleet | CPU | Memory | Disk |
|---|---|---|---|
| Up to 50 hosts | 2 cores | 2 GB | 20 GB SSD |
| Up to 500 hosts | 4 cores | 8 GB | 150 GB SSD |
| Beyond 500 | 4–8 cores | 16 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.
| Key | Default | What it does |
|---|---|---|
listen | :8080 | Address for the plain HTTP listener. |
data_dir | /var/lib/torwarden | Where fleet.db, the daily metrics and rollup databases, and secret.key live. |
admin_username | admin | The break-glass console administrator. |
admin_password | — | Its password, in plaintext. This is why the file is 0640. |
secret_key_file | <data_dir>/secret.key | Encrypts stored credentials and the TLS private key. |
retention_raw_days | 7 | Full-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.
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.