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 daily

Alerts 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=YES

The 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 restart
chmod 600 /usr/local/etc/monitrc
monit -t
service monit start
monit status

Monit 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 start

node_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 9100

On the monitoring host:

pkg install prometheus3 alertmanager grafana
sysrc prometheus_enable=YES alertmanager_enable=YES grafana_enable=YES

Point 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: 15m

Alertmanager 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 start

Prometheus 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 the zabbix7-frontend-php8x package 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 is zabbix_agentd_enable. The 2024 version of this guide used Zabbix 5, which is no longer in the ports tree.
  • Netdata (netdata, enabled with netdata_enable). Per-second dashboards with almost no configuration, served on port 19999 — excellent for diagnosing one box live.
  • Nagios (nagios4, with monitoring-plugins and nrpe). 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  JC

That 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.com

and 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 restart

Open 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 the elasticsearch8 package is version 8.11 while kibana8 is 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_exporter everywhere, 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.