Most FreeBSD monitoring guides jump straight to installing a monitoring server. Start one step earlier. FreeBSD ships tools that answer most "why is this box slow?" questions in seconds, and a nightly report that will tell you about a failing disk before you think to ask — once you turn on the one check that's off by default.
This guide goes from there to alerting on a single server, metrics across several, and getting logs off the machine. Every package name, rc.conf variable and default below was checked against FreeBSD 15.1's package repository, the ports' own rc scripts and a 15.1 base system in October 2026.
What FreeBSD already ships
| What you want to know | Command |
|---|---|
| What's using CPU and memory, by process and thread | top -SHP |
| Load, memory and paging, once a second | vmstat 1 |
| A full-screen live view of the same | systat -vmstat 1 |
| Which disk is busy, and how busy | gstat -p |
| Per-device throughput and latency | iostat -x 1 |
| Traffic per network interface | netstat -iw 1 or systat -ifstat 1 |
| What's listening on which port | sockstat -4 -6 -l |
| ZFS pool health and I/O | zpool status -x and zpool iostat -v 1 |
| What a single process has open | procstat -f <pid> |
A few of these deserve a sentence each.
top -SHP is three flags most people never learn. -S includes system processes, -H breaks processes out into threads, and -P gives each CPU core its own line — the quickest way to spot one single-threaded process pinning a core while the rest sit idle.
gstat -p is the one Linux users miss most. It lists every physical disk with its queue length, operations per second, read and write latency and a %busy column, updated live. It's the fastest way to find out whether "the server is slow" really means "one disk is saturated".
zpool status -x prints all pools are healthy or tells you exactly which pool isn't. It's worth running by hand, and worth automating — which is the next section.
When none of that is enough, DTrace is built in. Load it, then count system calls by process name until you press Ctrl-C:
kldload dtraceall
dtrace -n 'syscall:::entry { @[execname] = count(); }'Let FreeBSD email you every night
FreeBSD already runs a daily maintenance report from cron through periodic(8) and mails it to root. Out of the box it checks disk status, network interfaces, uptime and the mail queue, and runs the security checks.
The one thing it doesn't check by default is ZFS — daily_status_zfs_enable ships as NO. On a ZFS system, turn it on in /etc/periodic.conf:
daily_status_zfs_enable="YES"The report only helps if someone reads it, and root's mail stays on the machine unless you forward it. Add root: you@example.com to /etc/mail/aliases. FreeBSD's base mailer is dma(8); if your provider blocks outbound port 25, as most do, point it at a relay by setting SMARTHOST in /etc/dma/dma.conf. Then run the report once by hand to check it arrives:
periodic dailyAlerts and automatic restarts on one server: Monit
Monit watches services, resources and filesystems, restarts what crashes, and emails you about it — a good fit for one or two machines.
pkg install monit
sysrc monit_enable=YESThe package installs a heavily commented example at /usr/local/etc/monitrc.sample. A minimal working configuration looks like this — save it as /usr/local/etc/monitrc:
set daemon 60
set mailserver localhost
set alert you@example.com
set httpd port 2812 and
use address localhost
allow localhost
allow admin:change-me
check system $HOST
if loadavg (5min) > 4 then alert
if memory usage > 90% then alert
check filesystem rootfs with path /
if space usage > 85% then alert
check process nginx with pidfile /var/run/nginx.pid
start program = "/usr/sbin/service nginx start"
stop program = "/usr/sbin/service nginx stop"
if failed port 80 protocol http then restartchmod 600 /usr/local/etc/monitrc
monit -t
service monit start
monit statusMonit refuses to start if its control file is readable by other users, hence the chmod. monit -t checks the syntax first and reports the line number of any mistake. The local web interface on port 2812 is what monit status talks to: keep it bound to localhost and change that password. Set the load-average threshold to roughly your core count.
Metrics across several servers: Prometheus, node_exporter and Grafana
Once you have more than a couple of machines, collect metrics centrally and alert on them. On every server, run the exporter:
pkg install node_exporter
sysrc node_exporter_enable=YES
service node_exporter startnode_exporter listens on port 9100 on every interface by default. If Prometheus runs on the same machine, bind it to localhost:
sysrc node_exporter_listen_address="127.0.0.1:9100"Otherwise let only the Prometheus host reach it — in a default-deny pf ruleset:
pass in on $ext_if proto tcp from 192.0.2.10 to any port 9100On the monitoring host:
pkg install prometheus3 alertmanager grafana
sysrc prometheus_enable=YES alertmanager_enable=YES grafana_enable=YESPoint Prometheus at your servers and its alert rules in /usr/local/etc/prometheus.yml:
global:
scrape_interval: 15s
rule_files:
- prometheus-alerts.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["localhost:9093"]
scrape_configs:
- job_name: node
static_configs:
- targets: ["localhost:9100", "web2.example.com:9100"]Then two rules to start with, in /usr/local/etc/prometheus-alerts.yml — a server that stops answering, and a filesystem running out of space:
groups:
- name: node
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
- alert: FilesystemAlmostFull
expr: node_filesystem_avail_bytes{fstype=~"ufs|zfs"} / node_filesystem_size_bytes{fstype=~"ufs|zfs"} < 0.10
for: 15mAlertmanager decides where alerts go. Its rc script won't start until alertmanager.yml exists in /usr/local/etc/alertmanager/, so configure a receiver there — email is the simplest — and then start everything:
service prometheus start
service alertmanager start
service grafana startPrometheus answers on port 9090; its Status → Targets page should list each server as UP. Grafana answers on port 3000 — sign in as admin/admin, set a new password, and add Prometheus at http://localhost:9090 as a data source. Keep both off the public internet, or put them behind a reverse proxy with authentication.
One expectation to set: node_exporter exposes fewer metrics on FreeBSD than on Linux, so many community Grafana dashboards — built for Linux — will show empty panels. A small dashboard of your own (CPU, memory, disk, network, filesystem free space) is usually more useful anyway.
Other tools worth knowing
- Zabbix 7 (
zabbix7-server,zabbix7-agent, and thezabbix7-frontend-php8xpackage matching your PHP). Full agent-based monitoring with templates and built-in alerting, at the cost of a database and a PHP frontend. The agent's switch iszabbix_agentd_enable. The 2024 version of this guide used Zabbix 5, which is no longer in the ports tree. - Netdata (
netdata, enabled withnetdata_enable). Per-second dashboards with almost no configuration, served on port 19999 — excellent for diagnosing one box live. - Nagios (
nagios4, withmonitoring-pluginsandnrpe). Classic check-and-alert monitoring. Mature, but its configuration model shows its age. - Uptime Kuma (
uptime-kuma). Checks your sites from the outside. Run it on a different machine — a monitor on the box it watches goes down with it. - Telegraf (
telegraf), if your metrics already go to InfluxDB.
Logging
FreeBSD's logs live in /var/log. Start with /var/log/messages for most system and daemon messages, /var/log/auth.log for logins, and /var/log/cron and /var/log/maillog for their namesakes.
Rotate with newsyslog
newsyslog runs hourly from cron and already rotates the system logs. Don't edit /etc/newsyslog.conf for your own applications — it includes every file in /usr/local/etc/newsyslog.conf.d/, so add one there, for example myapp.conf:
# logfilename [owner:group] mode count size when flags
/var/log/myapp.log www:www 640 7 * @T00 JCThat keeps seven rotated copies, rotates every day at midnight, compresses old ones with bzip2 (J), and creates the log file if it's missing (C).
Send logs to a central host
syslogd reads every file in /usr/local/etc/syslog.d/ too, so forwarding is one line. On each server, create /usr/local/etc/syslog.d/forward.conf:
*.* @loghost.example.comand run service syslogd restart. On the log host, syslogd ships with -s, which refuses messages from the network; allow your servers instead:
sysrc syslogd_flags="-a 192.0.2.0/24"
service syslogd restartOpen UDP port 514 to those hosts in pf. Plain syslog over UDP isn't encrypted, so keep it on a private network or VPN.
Make logs searchable
- Loki and Alloy (
grafana-loki3,alloy). Loki stores logs and Alloy — Grafana's successor to Promtail — ships them, so you query logs in the same Grafana you use for metrics. The lightest searchable option if you already run the Prometheus stack. - OpenSearch (
opensearch), for full-text search at scale. It's the easier path from packages than the ELK stack this guide recommended in 2024: as of October 2026 theelasticsearch8package is version 8.11 whilekibana8is 8.19, and Kibana requires an Elasticsearch of the same version.
What to set up, by size
- One server: the built-in tools, the nightly report with the ZFS check on and root's mail forwarded, Monit, and newsyslog rules for your applications.
- A handful of servers: add
node_exportereverywhere, one host running Prometheus, Alertmanager and Grafana, and syslog forwarding to it. - A larger fleet: Zabbix, or Prometheus at scale, plus Loki or OpenSearch for logs.
Monitoring tells you something is wrong; the rest of keeping a server healthy is patching it, hardening SSH and, when the alerts point at performance, tuning it.
Package names, rc.conf variables and defaults were checked against FreeBSD 15.1's package repository, the ports' rc scripts and a 15.1 base system on October 7, 2026. Package versions move; the names and settings change much less often.
