Resource monitor
The resource monitor and the metrics history.
noust monitor watches the machine and writes down what stands out: resource use, processes
worth a look, units that stopped, certificates about to expire and full disks. It keeps what
it noticed in a local database and can send you a report by email, or through any notification
channel you configure.
It reports and does nothing else:
- it never signals, terminates or restarts a process;
- it never deletes or modifies a file, except the systemd unit it installs;
- it never decides anything from a process's command line (the process samples keep a redacted, truncated copy of the busiest processes' command lines so an operator can read what ran; see Process samples);
- what it sends leaves the machine only through channels you configured: raw observations by
email when
monitor.notifyis on, and the disk, certificate and unit events through whichever notification channels you set up - webhook, Slack, Discord, Telegram, email, Pushover, ntfy, Gotify, Microsoft Teams, Mattermost, Matrix, Lark or PagerDuty (see Notifications). Nothing is sent anywhere else.
Before 1.0 the monitor sent process data to OpenAI and could kill processes and delete their
files. All of that is gone. The flags --force-ai and --all on noust monitor scan are
still accepted, ignored with a warning; the settings monitor.auto_terminate,
monitor.terminate_malicious_only and monitor.dry_run are pinned to their safe values
whatever the file says; monitor.use_ai, monitor.ai_interval and monitor.openai.* have no
effect and are ignored on load. noust config show lists them apart as obsolete, and noust config clean removes them from config.yaml (comments kept, dated copy first), deleting the
OpenAI API key an old file may still hold. See the configuration reference.
Commands
noust monitor scan # look at the machine once and print what stands out
noust monitor run # scan on a loop in this terminal, until Ctrl+C
noust monitor install # write the systemd unit, without starting it
noust monitor enable # start it now and at every boot, installing it if needed
noust monitor autoenable # what the package runs: enable it unless it was turned off
noust monitor disable # stop it and keep it from starting at boot
noust monitor uninstall # remove the unit; recorded observations stay
noust monitor status # whether it runs, and what it watches
noust monitor config # the settings in effect and where the database lives
noust monitor test-email # send one email to the recipients, to prove the settingsThe unit is noust-monitor.service. It runs noust monitor run as root, restarts on failure,
and is sandboxed (NoNewPrivileges, ProtectSystem=strict, ProtectHome=read-only,
PrivateDevices). The console has the same controls under Server > System, and the Overview
says when the history is not being recorded, why, and what to run.
The package installs and enables the monitor on install, and on an upgrade that finds none,
because the charts' history is recorded here (see Metrics history). It
never overrides a decision: a monitor that is installed stays as it is, and one you disabled or
removed stays off until you run noust monitor enable. Where there is no systemd (a container)
nothing is installed, and the console records the history while it runs.
"Turned off" is remembered in /var/lib/noust/monitor-declined, which noust monitor disable
and noust monitor uninstall write from 3.1 on. A monitor removed with 3.0's noust monitor uninstall left no such file, so on the upgrade to 3.1 the package also leaves the monitor off
when the journal still has lines from noust-monitor, or the observation
database holds anything a monitor recorded, and writes the file then. Where neither is left
(a rotated journal), keep the monitor off with:
sudo touch /var/lib/noust/monitor-declined # before upgrading; noust monitor enable removes itWhat it watches
Every scan interval (60 seconds by default, never less than 10):
| Watch | Recorded when | Severity |
|---|---|---|
| Process names | The executable's name starts with a known miner or malware name: xmrig, minerd, cpuminer, cgminer, bfgminer, ethminer, ccminer, kdevtmpfsi, kinsing, kerberods, watchdogs. Common daemons are never flagged. |
warning |
| Process resource use | A process uses more CPU or memory than monitor.cpu_threshold or monitor.memory_threshold percent (80 by default). |
notice |
| Units | A unit Noust manages, or one listed in monitor.watch_units, fails: it is failed, it crash-loops (its automatic restarts grow between two scans), or it stopped after a failed run. A unit stopped on purpose does not count. All units are read with one systemctl show per scan; one message per outage. Each failure and each recovery is also kept in the metrics database (for metrics.retention_days), where the timeline reads it. |
notification unit_failed |
| Applications | An application whose unit runs stops answering what the deploy's health gate asks (its health path and expected statuses, on the port that serves now): three failed probes in a row, the first and the last at least a minute apart, so a restart or a deploy is never announced. One message per outage, and one when the first probe passes again. Not asked while a deploy, update, rollback or job of that application runs, nor when its unit was stopped on purpose or failed (that is unit_failed). A Compose stack that publishes no port is judged by its containers instead: one that exited with an error or keeps restarting is an outage. A static site and a PHP-FPM pool have no process to ask and are never reported. Up to eight applications are asked at once, so one that hangs does not hold up the rest. The run of failures is kept in app-reachability.json next to the observation database (/var/lib/noust), so a restart of the monitor neither forgets it nor repeats the message; a gap of more than ten minutes between two probes (the monitor was stopped) starts the count again, but an outage already announced is kept so its end is still told. |
notifications app_unreachable, app_recovered |
| Certificates | A certificate has fewer than monitor.cert_warning_days (14) days left; at most one message per certificate per day. |
notification cert_expiring |
| Disks | A filesystem crosses monitor.disk_threshold (90) percent used; one message per crossing, looked at every monitor.disk_check_seconds (0: every scan) and repeated every monitor.disk_renotify_hours while it stays over (0: not repeated). |
notification disk_threshold |
| Backups | An application with a backup schedule, a database policy, an application's data policy or Noust's own state backup has no successful backup newer than monitor.backup_missing_days (2; 0 turns it off), stretched to a week or a month for those schedules. Looked at every monitor.backup_check_hours (6), repeated every monitor.backup_missing_renotify_hours (24) while it stays missing. |
notification backup_missing |
| Backup destinations | A destination some policy sends to does not answer the test noust backup destination test makes. Asked every monitor.backup_check_hours (6); said once per outage with rclone's own words, repeated every monitor.backup_missing_renotify_hours, and once more when it answers. |
notification destination_unreachable |
| Cron jobs | A job's last run ended with an error (with the job's own last lines of output), or ended well when the heartbeat is on. Each run is announced once; the first look at a job only remembers it. A job whose timer is enabled but not running (inactive or failed) will not start: that is told once per stretch, and once more when the timer runs again; a job you disabled is not a fault. |
notifications cron_failed, cron_success, cron_missed |
| System updates | The package manager has updates waiting, every monitor.os_patches_check_hours (24; 0 turns it off). Security ones are named and repeated weekly; ordinary ones are announced once per new set and at most weekly; a check that cannot be made is announced once per stretch. Nothing is installed. |
notification os_patches |
| API tokens | A token expires within monitor.token_warning_days (7; 0 turns it off), expires today, or expired in the last week; once per stage and token. |
notification token_expiring |
| Probes | An address the monitor asks from outside the application stops answering what the probe asks of it (fail_after bad answers in a row), and answers again. See probes.md. |
notifications probe_failed, probe_recovered |
| Alert rules | Every rule of Settings > Alert rules is judged against the recorded history; a subject that stayed past its limit for the whole duration fires, and one that is back inside clears. | notifications alert_firing, alert_resolved |
| Web server | nginx (or Apache, for the sites that use it) fell. See The web server. Looked at every monitor.webserver_check_seconds (60; 0 turns it off). |
notification unit_failed (webserver.*) |
| Containers | A container of a Compose stack that Docker restarted monitor.container_restart_limit times (5; 0 turns it off) since the monitor first saw it. See Containers that keep restarting. Looked at every monitor.container_check_seconds (60). |
notification container_restart_loop |
| Security releases | The update check found a Noust release that fixes a vulnerability and this server runs an earlier one. Once per release and again every monitor.security_renotify_hours (72; 0 never) while it stays behind; looked at every monitor.security_check_hours (6; 0 turns it off). updates.check: false silences it. |
notification security_release |
| Recipes | Recipes installed from the catalogue have a newer version in it (noust recipe outdated). Said when the list changes and every monitor.recipe_renotify_days (7; 0 never repeats) while it does not; looked at every monitor.recipe_check_hours (24; 0 turns it off). The catalogue ships inside Noust, so the look costs no request. |
notification recipe_outdated |
| Central | The central that manages this server, and said it keeps watch (its own monitor probes every server every half minute), has not called for notifications.central_silent_minutes (15; 0 turns it off). Said once, and again when it calls. A central that never said so is not missed |
notification central_silent |
Observations are kept in /var/lib/noust/observations.db (~/.local/share/noust/ when run
without root). The same process and signal within an hour is recorded once; observations
older than monitor.retention_days (30) are purged hourly, and at most
monitor.max_observations (5000) are kept. Acknowledging one, from the console or
POST /api/monitor/observations/{id}/acknowledge, marks it seen and does nothing to the
process.
Every one of these checks, the alert rules and the retries of failed deliveries only happen while the monitor runs.
The web server
Every site of every application goes through one web server, and its unit is the one thing they have in common. When nginx falls, each application would otherwise be "unreachable" on its own and nothing would start the server again until somebody logged in. On every look the monitor reads the unit of the web server in use (nginx, and Apache for the sites that use it):
- Running: nothing. If an outage was open, it is closed.
- Crashed (
failed, or stopped after a run that did not end well): the server's own configuration test runs first, because starting a configuration that does not pass takes nothing up and hides the cause. If it passes the unit is restarted and the notification says so (webserver.restarted); if it does not, the notification carries the test's words verbatim and nothing is restarted (webserver.config_invalid). A restart that does not hold iswebserver.restart_failed. - At most
monitor.webserver_restart_limitrestarts an hour (3). Past it the limit is said once (webserver.limit) and the rest is yours, so a server that falls at every start is not restarted forever. - Stopped cleanly and still enabled: somebody stopped it. It is said once
(
webserver.stopped) and left alone. A web server that is disabled and stopped is off on purpose.
monitor.restart_webserver: false keeps the announcement and drops the restart
(webserver.down). It is the one check of the monitor that acts, and the only thing it does is
what systemctl restart would do; each restart is in the audit trail as monitor.webserver_restart.
All of these go through the unit_failed switch of the notifications.
Containers that keep restarting
A Compose stack's unit runs docker compose up -d and finishes, so systemd cannot say that a
container of the stack restarts every few seconds, and a stack whose web still answers reads as
healthy while one of its workers dies in a loop. The monitor reads the restart counter Docker keeps
for each container (RestartCount):
- A container Docker restarted
monitor.container_restart_limittimes (5) since it was first seen is announced once (container_restart_loop), with the exit code of its last run and its last lines. The count is a baseline, not an absolute: a container that restarted forty times last month is not a loop today. The announcement closes when the container has run five minutes without restarting. - Docker's events (
die,oom,restart,health_status) of those containers are kept beside the metrics, so that the timeline of an application can say that the container was killed for lack of memory at 03:12, long after the unit that started it finished.
Metrics history
The monitor also records the history behind the console's charts: CPU, memory, swap, disk, network and load of the machine, and CPU, memory, disk reads and writes and network traffic of every application, every 5 seconds. Until 3.1 the console recorded it, so it existed only while a console was running.
| Tier | Step | Kept for | Answers |
|---|---|---|---|
| raw | 5 s | 2 hours | the last hour |
| 1m | 1 minute | 26 hours | the last day |
| 10m | 10 minutes | 8 days | the last week |
| 1h | 1 hour | metrics.retention_days (400) |
a month and beyond |
Every tier keeps the mean and the maximum of each bucket, so a five-minute spike is still
there in the month view. The history lives in /var/lib/noust/metrics.db. The whole of it
takes about 60 MB for twenty applications with the default retention (measured with 49
series), most of it the hourly tier: 24 rows per series per day, so metrics.retention_days
scales it directly.
Only one collector writes at a time: the monitor takes a lease in the database, and the console
samples only while no monitor does (a container, where there is no systemd). The console reads
the history with GET /api/metrics/query, several series per request, at the tier the window
needs. noust monitor status says whether the history is being
recorded and, when it is not, why and what to run.
What is measured per application depends on how it runs:
| Application | CPU and memory | When there is nothing |
|---|---|---|
| Node, Python, Go... (in place, releases, units with the legacy prefix of before 0.14.1) | its unit's cgroup, located by systemd (ControlGroup) |
stopped, unit missing, accounting off |
| Zero-downtime | both instances, summed | as above |
| Monorepo | every workspace's unit, summed | as above |
| Docker Compose | the cgroup of each container of the project | no docker, no containers |
| PHP-FPM | the worker processes of its pool | the FPM service is stopped |
| Static site | none: there is no process | its requests and 5xx per minute come from the web server's access log |
An application behind the proxy has its requests and 5xx per minute counted from the same access log as well, whatever it runs as. See Traffic of an application.
A Docker Compose stack also has series for each of its containers
(app.<domain>.ctr.<container>.<what>: CPU, memory, disk reads and writes, network received and
sent), beside the stack's total, so a chart can say which service is the one reading the disk. The
Metrics tab of the stack charts them by container; a container that lives for one command
(docker compose run) is in the total and gets no series of its own.
Memory is the working set (memory.current less reclaimable page cache), CPU is a percentage
of one CPU. Disk is the bytes per second the unit's cgroup read and wrote (IOAccounting) and
network the bytes per second its processes received and sent (IPAccounting), both of which
the unit Noust writes switches on; a unit written before them has no counters until its next
update or restart, and a Compose stack's network is read from its containers' namespaces. To tell the workers of one PHP-FPM pool from another's, the monitor reads the title
FPM gives each worker (php-fpm: pool <name>): the only process title it reads, and nothing
is decided from it.
Traffic of an application
The proxy is where an application's requests, its errors and its visitors are seen, and its access
log (<domain>.access.log, which every site Noust writes has) says all of it. The monitor reads
what each log gained since the last look (a few hundred kilobytes at most per tick, never twice,
rotation recognised) and keeps it by the hour in metrics.db:
- requests, how they ended (2xx, 3xx, 4xx, 5xx) and the bytes of the bodies served, kept as long as
the hourly metrics (
metrics.retention_days); - the pages asked for most (
GET /orders/:n), the networks they came from (203.0.113.0/24), the status codes and the referrers (a host), kept 90 days. Each hour of each application keeps at most 50 keys of each, so a scan of a thousand paths cannot make the table grow; - how long requests took, as the median and the 95th percentile (
p50,p95), from a histogram kept by the hour: 13 buckets from 5 milliseconds to 30 seconds (and one for what takes longer), so a day or a month is the sum of its hours and the percentile is read from the sum, never the mean of percentiles; - how many different visitors there were, by the hour and by the stretch asked for. A person who came at nine and again at ten is one visitor of the day, so the hours cannot just be added. What is kept is a sketch of hashes (a short list up to 100 visitors, so a small site's count is exact; a HyperLogLog past that, within about 5 percent) from which no address can be recovered.
noust app traffic shop.example.com # the last 24 hours
noust app traffic shop.example.com --window 30d --top 20 --jsonGET /api/apps/{domain}/traffic?window=24h|7d|30d|90d&top=10 answers the same: by the hour up to two
days, by the day beyond, every step present (a quiet hour is a point of zeros), and the Metrics tab of
the application charts it. What is kept is chosen to be safe to keep:
- No query string, ever. It is where tokens, e-mail addresses and session ids live. A page is its
method and path, and a segment that is a number, a UUID or a long hexadecimal string becomes
:n,:uuidor:hash, soGET /orders/48211andGET /orders/48212are one row. - No address of a person. A client is its network: the first 24 bits of an IPv4 address, the first 48 of an IPv6 one. Whoever needs the exact address has the access log, which the web server rotates.
- Behind a CDN the log shows the CDN's addresses, not the visitors': tell the site where the real
address is (
noust app policy set DOMAIN --real-ip cloudflare, see network.md) and the networks become the visitors'.
There is no GeoIP: it needs a database whose licence is not Noust's to ship. The networks are the key to put one on yourself.
The log format. The time of a request is not in nginx's default format. The sites Noust writes
define their own (log_format noust_<domain>: the combined format and then rt=$request_time; an
Apache vhost ends its format with rtus=%D, microseconds) and log with it. A site the operator wrote
is never rewritten, so it keeps its own format and shows no times; a log line in the old format, in a
file that also holds new ones (the site was written before this release and rewritten since), is
counted like any other and simply has no time. A request without a time is not part of the
percentiles. An alert rule can watch app_latency_p95 (milliseconds, see
notifications.md). The Metrics tab shows the 95th percentile (with
the median beside it) and the visitors as tiles above the charts.
Comparing servers
A central keeps no series of its own. noust fleet metrics --metric cpu --window 24h (and the fleet Overview's
metrics comparison, GET /api/fleet/metrics) asks every server for the same metric over the same window, from
each one's own recorded history, and puts the answers side by side: the points, the latest value, the average and
the peak. --metric is one of cpu, memory, swap, disk, load, net_rx and net_tx (memory, swap and
disk are the share of what each server has, so a small server and a large one compare); the windows are 1h, 6h,
24h, 7d and 30d. A server that is down is a gap in the comparison, said so in its own words, never an error;
one too old to offer the history is unsupported. Nothing is stored on the central.
Exporting the metrics
The history answers the console. A Prometheus, a Grafana Alloy, a Netdata or an OpenTelemetry
Collector asks "what is it now?" every few seconds, in a text format they all read, and
GET /api/metrics/prometheus answers that: the newest value of every metric of the server, of each
application and each container of a stack, and of each database, as gauges.
curl -H "Authorization: Bearer $TOKEN" https://noust.example.com/api/metrics/prometheus
noust monitor export # the same text, from this server's own history
noust monitor export --format openmetrics# prometheus.yml
scrape_configs:
- job_name: noust
scheme: https
metrics_path: /api/metrics/prometheus
authorization:
credentials_file: /etc/prometheus/noust.token
static_configs:
- targets: ["noust.example.com"]The credential is an API token that cannot change anything (noust token create prometheus --scope read, or narrowed to the one permission the route asks for, --permission server.read): there is no
separate export secret. Send Accept: application/openmetrics-text (a
Prometheus configured for it does) and the answer is OpenMetrics. An OpenTelemetry Collector reads the
same endpoint with its prometheus receiver, which is how OTLP consumers are served.
| Metric | Labels | Unit |
|---|---|---|
noust_cpu_percent, noust_load1 |
percent of all cores, load average | |
noust_memory_used_bytes, noust_memory_total_bytes, noust_swap_*, noust_disk_* |
bytes | |
noust_network_receive_bytes_per_second, noust_network_transmit_bytes_per_second |
bytes per second | |
noust_app_cpu_percent, noust_app_memory_bytes |
app |
percent of one core, bytes |
noust_app_disk_read_bytes_per_second, noust_app_disk_write_bytes_per_second |
app |
bytes per second |
noust_app_network_receive_bytes_per_second, noust_app_network_transmit_bytes_per_second |
app |
bytes per second |
noust_app_http_requests_per_minute, noust_app_http_errors_5xx_per_minute |
app |
per minute, from the access log |
noust_container_* (the same six as an application's) |
app, container |
|
noust_database_size_bytes, _connections, _cache_hit_percent, _transactions_per_second, _queries_per_second, _memory_bytes, _operations_per_second |
engine, database |
|
noust_info |
version, server |
always 1 |
noust_metrics_recording |
1 while the history is being recorded |
Only the series described here are exported, never whatever the store happens to hold, so a new
series cannot change the shape or the size of a scrape by accident. A value older than two minutes (an
application that stopped being recorded) is left out, and noust_metrics_recording says whether the
collector is running at all.
Process samples
A chart says that the CPU peaked five hours ago, not what did it. Wherever the metrics history is recorded (the monitor, or a console that records it itself), the collector also keeps, once a minute, the five processes that used the most CPU and the five that used the most memory:
| Field | What it holds |
|---|---|
pid, name, user |
The process, and the account it runs as |
cpu_percent |
CPU over the minute that just ended, 100 being one core. Measured from the CPU seconds the process used between two samples, so the first minute after the collector starts only primes the counters |
memory_bytes, memory_percent |
Resident memory at the sample |
app, owner_kind, owner |
Who it belongs to, read from its control group like the application charts: a Noust application (app is its domain), a Docker Compose container of its stack, a PHP-FPM pool, or the systemd unit its cgroup names. Empty for a login shell or a kernel thread |
command |
Its command line, redacted and cut at 200 characters |
- Retention. The samples live as long as the per-minute metrics they explain (26 hours; see
the table above), in the same database,
/var/lib/noust/metrics.db. There is no history from before the collector first ran with 3.2, and a process that started and ended between two minutes is never seen. There is no setting to turn the samples off. - Command lines are secrets carriers, so each argument is redacted before it is stored: the
value of an option or a
NAME=valuewhose name looks secret (--password,--api-key,DB_PASS=),-p<password>of the MySQL clients, any argument that looks like a token or a key on its own, and the password inside every URL. What is stored is shown only to a caller who holdssecrets.reveal; anyone else gets the process name. The monitor decides nothing from the command line. - Where they are read.
GET /api/timeline(below), and the console's Investigate this stretch on a chart (console.md).
What happened in a stretch
GET /api/timeline?start=<epoch>&end=<epoch> merges, ordered by time, everything Noust knows
about a stretch of up to 31 days: the process samples above, and
| Source | What | Needs |
|---|---|---|
journal |
The journal of every unit, warnings and worse (all priorities for one application) | secrets.reveal |
audit |
CLI commands, console and central actions, sign-ins | audit.read |
deployments |
Deployments that ran in the stretch | apps.read |
jobs |
Background jobs: deploys, backups, cron runs | apps.read |
monitor |
The observations above, unit failures and recoveries, server boots | server.read |
processes |
The process samples | server.read |
The route itself needs server.read, and each source keeps the permission its own page needs.
A source the caller may not read comes back as withheld with the permission it lacks, never
silently dropped; one that cannot be read says why, with the system's own words, and the rest
are still answered. app=<domain> narrows the stretch to one application: its units' journal,
the audit events that name it, its deployments and jobs, its units' failures and its processes.
sources=journal,audit (repeated or comma-separated) asks for only some. Each source has a
ceiling and says when it reached it. On a central the call goes through the node's proxy, and
each server answers for what it saw.
noust config set monitor.smtp.host smtp.example.com
noust config set monitor.smtp.port 465
noust config set monitor.smtp.username alerts@example.com
printf '%s' "$SMTP_PASSWORD" | noust config set monitor.smtp.password --stdin
noust config set monitor.email_recipients admin@example.com,oncall@example.com
noust config set monitor.notify true
noust monitor test-emailThe password never has to be typed into the command line: --stdin reads it from standard
input (a single trailing newline is stripped), and noust config set monitor.smtp.password --prompt asks for it interactively without echoing it back. Typing a secret straight into
VALUE on a real terminal prints a warning suggesting one of the two - see
Secrets via --stdin. monitor.email_recipients takes a
comma-separated list directly, the same as --list, because it already defaults to a list.
Mail can go through a provider's HTTP API instead (Resend, Postmark, Brevo or Amazon SES), for a host
that blocks outgoing SMTP: monitor.email_api.provider and its credentials replace the SMTP account,
see Email through a provider's API.
With monitor.notify on, a scan that records new observations mails them to the
recipients. A process that keeps matching the same signal does not send a message every
scan. Credentials are only sent over TLS: use_ssl (implicit TLS, the default) or use_tls
(STARTTLS) - not both at once, and never neither when a username or password is set.
monitor.smtp.host, .port, .from_address and every address in monitor.email_recipients
are validated when set - a bad hostname, a port outside 1-65535, both use_ssl and use_tls
enabled, or an address that is not name@domain are refused with the reason, whether set with
noust config set monitor.smtp.* or through the console's Settings form
(GET/PUT /api/config/smtp, see API).
Configuration
| Key | Default | Meaning |
|---|---|---|
monitor.enabled |
false |
Whether it is meant to run at boot |
monitor.scan_interval |
60 |
Seconds between scans, at least 10. Above 300, noust monitor status warns that a failure may go unnoticed that long |
monitor.cpu_threshold |
80.0 |
CPU percent above which a process is recorded |
monitor.memory_threshold |
80.0 |
Memory percent above which a process is recorded |
monitor.watch_units |
[] |
Units watched in addition to every unit Noust manages |
monitor.notify |
false |
Mail new observations |
monitor.disk_threshold |
90.0 |
Percent used at which a disk is announced (1 to 100) |
monitor.disk_check_seconds |
0 |
Seconds between looks at the disks; 0 looks at every scan |
monitor.disk_renotify_hours |
0 |
Hours between repeats while a disk stays over; 0 sends one message per crossing |
monitor.cert_warning_days |
14 |
Days before a certificate expires that it is announced |
monitor.backup_missing_days |
2 |
Days without a new backup before it is announced; 0 turns it off |
monitor.backup_check_hours |
6 |
Hours between looks at the backups |
monitor.backup_missing_renotify_hours |
24 |
Hours between repeats of a missing backup |
monitor.os_patches_check_hours |
24 |
Hours between looks for system updates; 0 turns it off |
monitor.token_warning_days |
7 |
Days before an API token expires that it is announced; 0 turns it off |
monitor.webserver_check_seconds |
60 |
Seconds between looks at the web server; 0 turns the watch off (0 to 3600) |
monitor.restart_webserver |
true |
Start a web server that fell, after its configuration test passes; false only announces it |
monitor.webserver_restart_limit |
3 |
Restarts of the web server in an hour before the monitor stops and says so (1 to 20) |
monitor.container_check_seconds |
60 |
Seconds between looks at the containers of the stacks; 0 turns the watch off |
monitor.container_restart_limit |
5 |
Restarts of one container that make a loop worth announcing; 0 turns the announcement off |
monitor.security_check_hours |
6 |
Hours between looks for a security release; 0 turns it off |
monitor.security_renotify_hours |
72 |
Hours between repeats while the server stays below the fix; 0 says it once |
monitor.recipe_check_hours |
24 |
Hours between looks for recipes that have a newer version; 0 turns it off |
monitor.recipe_renotify_days |
7 |
Days between repeats of the same list; 0 says it only when the list changes |
metrics.retention_days |
400 |
Days the hourly tier of the metrics history is kept (35 to 3650); also how long the unit failures and recoveries the timeline reads are kept. The process samples are kept 26 hours whatever it says |
monitor.retention_days |
30 |
Days observations are kept |
monitor.max_observations |
5000 |
Observations kept at most |
monitor.email_recipients |
[] |
Who receives the mail |
monitor.smtp.host, .port, .username, .password |
"", 465, "", "" |
SMTP relay |
monitor.smtp.use_ssl, .use_tls |
true, false |
Implicit TLS, or STARTTLS |
monitor.smtp.from_address |
the username | Sender |
monitor.smtp.timeout |
30 |
Seconds, at most 120 |
noust config get and noust config set take these keys; secret values print as ***. Give
monitor.watch_units as a list: noust config set monitor.watch_units nginx.service,postgresql.service --list.
Notifications
The events above (unit_failed, app_unreachable, app_recovered, cert_expiring,
disk_threshold, backup_missing, cron_failed, cron_success, os_patches, token_expiring,
alert_firing, alert_resolved, alert_acknowledged, cron_missed, cleanup_failed, cleanup_success,
probe_failed, probe_recovered, security_release, container_restart_loop, recipe_outdated and central_silent) go through Noust's notification channels (webhook, Slack,
Discord, Telegram, email, Pushover, ntfy, Gotify, Teams, Mattermost, Matrix, Lark, PagerDuty, and any
others you add by name), which are
configured under notifications.* in the console's Settings > Channels or with noust notify
and noust config set, and only when notifications.enabled is on and the event is enabled. Each
channel can hear its own events, only some applications, and not at night; a delivery that fails
is tried again and kept on record. All of that is in notifications.md. Test a
channel with noust notify test <channel>.
app_unreachable and app_recovered are on by default, like unit_failed
(notifications.events.*, see CONFIG.md). The first names the application, says
since when it has not answered, quotes the last probe verbatim (the request and what came back,
or one line per container that is not fine for a stack with no published port) and points at
noust diagnose DOMAIN; the second says how long it was down. An application stopped on
purpose, being deployed or running a job, or whose unit failed (that is unit_failed) is not
announced.
API
| Endpoint | |
|---|---|
GET /api/monitor/status |
Unit state, and the list of things the monitor never does |
GET /api/monitor/config |
Settings in effect |
GET /api/monitor/metrics |
One live reading of CPU, load, memory, swap, disks, network |
GET /api/metrics/query |
The recorded history: several series, a window, mean and maximum per bucket |
GET /api/logs/search |
A search over the logs of every application you can see: logs.md |
GET /api/fleet/metrics |
On a central, one metric of every server over a window, side by side |
GET /api/apps/{domain}/traffic |
An application's requests by the hour or day, how they ended, and its top pages, networks, status codes and referrers |
GET /api/metrics/prometheus |
The newest value of every metric, for a scraper: Exporting the metrics |
GET /api/timeline |
Everything that happened in a stretch (journal, audit, deployments, jobs, what the monitor saw) with the process samples; see What happened in a stretch |
GET /api/monitor/processes |
The process table, sortable, at most 500 |
POST /api/monitor/scan |
Run one scan now |
GET /api/monitor/observations |
Recorded observations and their counts |
POST /api/monitor/observations/{id}/acknowledge |
Mark one as seen |
POST /api/monitor/install, /uninstall, /enable, /disable, /start, /stop |
Manage the unit |
GET /api/alerts/rules, PUT /api/alerts/rules/{name} |
The alert rules and where each stands, and a rule created or replaced |
POST, DELETE /api/alerts/rules/{name}/acknowledge |
Take a firing alert (?subject= an application), or hand it back |
POST /api/monitor/test-email |
Send a test email; a failure carries the SMTP server's own reply verbatim in output |
GET /api/config/smtp |
SMTP settings and recipients; the password is never returned, only whether one is set |
PUT /api/config/smtp |
Update them (sudo mode); a blank password keeps the one already stored |
Troubleshooting
- Nothing is recorded.
psutilmust be installed (apt install python3-psutil,dnf install python3-psutil,zypper install python3-psutil, orpip install psutil; the RPM package only recommends it), and the monitor needs root to see every process. - An application has no chart. The chart says why: the unit is stopped or missing, it is a
static site (see its requests instead), or accounting is off for its unit. Units written from
3.1 on carry
MemoryAccounting=yesandCPUAccounting=yes; for an older one, redeploy it or add them (systemctl edit <unit>) and restart it. - No email. Run
noust monitor test-email: it prints the SMTP server's own error. Check that the firewall allows outbound 465 or 587. - Too many resource-use observations. Raise
monitor.cpu_thresholdormonitor.memory_threshold. The same process with the same signal is recorded at most once an hour. - A chart has no process samples for the stretch. They are kept 26 hours and only exist from the minute the collector first ran with 3.2; an older peak has the chart and nothing that explains it. The console says so instead of showing an empty list.
- An application is announced as unreachable and answers when you try. The monitor asks
what the deploy's health gate asks: its health path and expected statuses
(
noust app health DOMAIN), on the port that serves now. Fix the check, not the monitor. A Compose stack with no published port is judged by its containers (docker compose ps -a).