Monitoring

VoxelBench includes a built-in real-time monitoring system with a web dashboard, boss bars, push mode for external services, and remote monitoring to your voxelbench.com dashboard. /bench monitor opens the monitoring GUI in-game, and /bench monitor status shows the state of each service.

Web Dashboard

Starting the Dashboard

/bench monitor web start

By default, the dashboard listens on this machine only: http://localhost:8080 (see Configuration to reach it from elsewhere). It shows, with live charts:

  • TPS (Ticks Per Second) - Target is 20.0
  • MSPT (Milliseconds Per Tick) - Lower is better
  • RAM used (share of the maximum heap)
  • CPU usage
  • Entities, with a breakdown (hostile, passive, neutral, items, projectiles, vehicles, drops, other)
  • Loaded chunks

The header also shows the server software and the number of players online (without a chart).

The page is available in English and French (selector at the top, or the browser language).

Stopping the Dashboard

/bench monitor web stop

/bench monitor web without argument toggles it. /bench monitor web start and /bench monitor web status print the local addresses of the dashboard and of /api/metrics, with the scheme and port the server actually listens on: http://localhost:8080 by default, https://localhost:8443 when HTTPS is on. status also shows the security settings.

Configuration

monitor:
  web-port: 8080              # Port for the web server
  bind-address: "127.0.0.1"   # Network interface (127.0.0.1 = this machine only)
  dashboard:
    enabled: true             # false = only /api/metrics, no HTML page

To reach the dashboard from other machines, set bind-address to "0.0.0.0" (all interfaces) or to one address of the server. The dashboard then refuses to start until a password is set (see Authentication): without one, it would publish your TPS, players and server version to anyone who finds the port. Behind a reverse proxy that has its own authentication, keep 127.0.0.1 and let the proxy reach it locally. An existing config.yml that still says 0.0.0.0 without a password gets the same refusal, with the fix in the message.

/bench monitor web dashboard off switches to API-only mode. After changing these settings, restart the web server (/bench monitor web stop, then start).

Auto-Start

To automatically start the web dashboard when the server boots:

monitor:
  auto-start:
    web-server: true

Profiles Section (experimental)

Below the charts, a read-only Profiles section lists the profiles saved by the profiler (/bench profile), newest first, and shows the summary of the one you click: who runs on the tick thread, the busiest methods, the warnings, and whether it was sent to voxelbench.com or shared by public link (with the links). Everything else β€” capturing, sharing, sending, deleting β€” stays in the game.

The page reads GET /api/profiles (the list) and GET /api/profiles/<id> (one profile). They use the same login or API key as /api/metrics, obey both whitelist modes (dashboard and api), accept only GET (and HEAD), and never return the token that deletes a public link. They exist only while the HTML dashboard is enabled.

HTTPS (SSL/TLS)

For secure access, you can enable HTTPS.

Generate a Certificate

/bench monitor https generate [days]

This creates a self-signed certificate valid for 365 days by default, in plugins/VoxelBench/certificates/keystore.jks, and saves its password in config.yml. It uses the keytool command from the JDK, which must be available on the server.

Enable HTTPS

/bench monitor https on

or in config.yml:

monitor:
  https:
    enabled: true
    port: 8443
    keystore-path: "certificates/keystore.jks"
    keystore-password: ""     # Filled in by the generate command
    key-alias: "voxelbench"

When HTTPS is enabled, the dashboard is served on the HTTPS port (monitor.https.port) instead of the HTTP port, and /bench monitor web start and status print https://localhost:<port>. Restart the web server to apply.

Using Your Own Certificate

If you have your own SSL certificate, import it into a Java keystore:

keytool -importkeystore -srckeystore your-cert.p12 -srcstoretype PKCS12 \
    -destkeystore plugins/VoxelBench/certificates/keystore.jks -deststoretype JKS

Then set the keystore path and alias in config.yml, and the password with /bench monitor https password <password>.

Authentication

Protect your dashboard with a username and password.

Set Up Authentication

  1. Set a password from the server console (a command typed in game is written to latest.log with the password in clear, so the plugin refuses it there). It needs at least 12 characters, and also enables authentication:
bench monitor auth password YourSecurePassword
  1. Optionally change the username (default admin):
/bench monitor auth username myname

The password is stored in config.yml as a PBKDF2 hash (210,000 iterations), never in plain text; a password set with an earlier version still works and is upgraded to PBKDF2 at the next login. After 5 failed logins within a minute from the same address, the login page answers "too many attempts" for that minute, and it follows the IP whitelist like the dashboard. Over HTTPS, the session cookie is marked Secure. Sessions last session-timeout minutes:

monitor:
  auth:
    enabled: true
    username: "admin"
    session-timeout: 60    # Session duration in minutes

/bench monitor auth shows the current authentication status. Restart the web server after a change.

API Key

For programmatic access (curl, Postman, scripts) when authentication is enabled:

/bench monitor auth key pull

This generates a key saved as monitor.auth.api-key. Send it in either header to read /api/metrics without a web session:

curl -H "Authorization: Bearer YOUR_API_KEY" http://your-server:8080/api/metrics
curl -H "X-API-Key: YOUR_API_KEY" http://your-server:8080/api/metrics

IP Whitelist

Restrict access by IP address:

monitor:
  whitelist:
    enabled: true
    mode: "all"            # all, dashboard, or api
    allowed-ips:
      - "127.0.0.1"
      - "::1"
      - "192.168.1.100"
    denied-message: "Access denied: Your IP is not whitelisted"

Whitelist Modes

ModeProtects
allBoth dashboard and API
dashboardOnly the web interface
apiOnly the /api/metrics endpoint

The profiles JSON (/api/profiles) is dashboard content served as JSON: it obeys the whitelist in all three modes.

Manage Whitelist In-Game

/bench monitor whitelist on
/bench monitor whitelist add 192.168.1.100
/bench monitor whitelist remove 192.168.1.100
/bench monitor whitelist list
/bench monitor whitelist mode dashboard

Behind a Reverse Proxy

If the dashboard is only reachable through a trusted reverse proxy (nginx, Caddy, Cloudflare Tunnel...), set monitor.behind-proxy: true so the client IP is read from the X-Forwarded-For header. Leave it false on a directly exposed dashboard: otherwise any client could forge that header and get past the whitelist.

Boss Bars

Display live metrics as boss bars at the top of the screen:

/bench monitor bars              # Toggle TPS + MSPT
/bench monitor bars all          # Show every metric
/bench monitor bars cpu          # Toggle one metric
/bench monitor bars off          # Hide all boss bars

Metrics: tps, mspt, ram, ramfree, cpu, entities, chunks, players.

When boss bars start, they are shown to every online player, and to every player who joins while they are active. A metric added while bars are already shown (bars all, bars <metric>) only appears for the player who typed the command and for players who join afterwards. Started by auto-start (below), the bars are shown to the operators and players with voxelbench.monitor online at that moment, then to every player who joins.

Auto-Start Boss Bars

monitor:
  auto-start:
    boss-bars: true

Push Mode

Send metrics to an external service (Grafana, custom dashboard, etc.).

Configuration

monitor:
  push:
    url: "https://my-server.com/api/metrics"
    api-key: "your-api-key"
    interval-seconds: 1
    allow-insecure: false    # Accept unverified SSL certificates

/bench monitor auth key push generates a random key and saves it as monitor.push.api-key.

Start/Stop Push Mode

/bench monitor push start
/bench monitor push stop
/bench monitor push test      # Send one request to check the settings
/bench monitor push status    # Success and error counters

The monitor.push.enabled value is only displayed in the GUI: push mode starts with the command above or with auto-start.

Auto-Start Push Mode

monitor:
  auto-start:
    push-service: true

Push Data Format

Each push is a POST request whose JSON body is the same document as /api/metrics. When an API key is configured, it is sent in the Authorization: Bearer <api-key> and X-VoxelBench-ApiKey headers. The X-Server-Name header carries the server software name.

voxelbench.com Dashboard

Remote monitoring sends metric snapshots and detected events to the Monitoring page of your dashboard on voxelbench.com. Unlike the web dashboard above, nothing listens on your server: the plugin sends, the site stores and charts. It requires a linked server and a plan that includes monitoring.

/bench monitor remote on       # Turn it on and check the connection now
/bench monitor remote status   # Link, service state, last check-in, last upload, what the site kept
/bench monitor remote off      # Stop sending

on writes remote-monitoring.enabled: true, starts the service, lists what will be sent (metrics, and the event sources that are on), asks the site right away, then says in game what is left to do: nothing (data is flowing), enable monitoring for this server on the site, check the plan, turn monitoring off for another server when the plan's limit of monitored servers is reached, or link the server again. off tells the site that monitoring was turned off on purpose (monitoring_paused) before it stops, so the site does not take the silence for a crash.

What a snapshot contains

Each snapshot describes the whole interval since the previous one, not a single instant:

FieldMeaning
timestampWhen the snapshot was taken
tpsTicks per second over the interval (0 when the server was frozen the whole time)
mspt, mspt_max, mspt_p95Average, worst and 95th-percentile tick time
ticks_over_50msTicks that went over the 50 ms budget
lag_secondsSeconds of the interval below 18 TPS
region_count, regions, regions_omitted, region_source, tps_avg, mspt_avgFolia: the regions measured, each one in detail, how many were only counted, how they were read, and the averages over all regions (see below)
pausedThe server was paused because it was empty (see below)
heap_after_gc_mb, rss_mb, container_limit_mbHeap still used after garbage collection, memory used by the process, container memory limit (Linux)
gc_stw_msStop-the-world GC pauses during the interval
ping_avg_ms, tile_entities, worlds_topAverage player ping, tile entities (Paper), the three busiest worlds
player_count, max_players, entity_count, loaded_chunks, world_count, plugin_countPlayers online and player slots, entities, loaded chunks, worlds and plugins
ram_used_mb, ram_max_mbHeap in use and maximum heap
cpu_usage, cpu_system, cpu_coresCPU used by the server process and by the whole machine, in percent, and the CPU threads available
gc_pause_ms, gc_countTime spent in garbage collection and number of collections during the interval, as the JVM counts them

Remote monitoring measures with its own windows, fed by the plugin's single tick source (see Tick, GC and CPU Metrics): a benchmark running on the server does not reset them. The heartbeat, every 30 seconds, also says how long ago the main thread (the global region on Folia) last ticked, so a frozen server does not look online.

Folia: every region. On Folia each region ticks on its own thread, so one TPS for the whole server means little. Each snapshot measures every region Folia ticks β€” including regions without players, such as farms and chunk loaders β€” and lists them worst first in regions: world, center block (the coordinates Folia's /tps shows), chunks, players, entities, TPS, average, 95th-percentile and worst tick time. tps, mspt, mspt_p95, mspt_max, ticks_over_50ms and lag_seconds carry the worst region for each value, tps_avg and mspt_avg the average over all regions. The list holds 64 regions at most (remote-monitoring.folia-regions.max-detailed); the others are only counted. A region that merged into another, split or unloaded is dropped, not reported as frozen. On a Folia fork whose regions cannot be read, the plugin measures the areas around players instead (TPS only) and says so once in the console.

Empty server pause. Since Minecraft 1.21.2, a server left without players for pause-when-empty-seconds (server.properties, 60 seconds by default) stops ticking. The heartbeat and the snapshots then carry paused: true, so voxelbench.com does not mistake the pause for a freeze. It is never set while a player is online. /bench monitor remote status shows how many snapshots voxelbench.com kept and refused since the service started, each refusal reason with its count and the last one, the snapshots re-sent after a timeout (kept once), and, from 5 seconds, how far the server's clock is from the site's (the site corrects it).

Monitoring also has to be enabled for the server on voxelbench.com. The order does not matter: while the site refuses the data, the plugin pauses and checks again on its own, every minute (every 10 minutes when the plan does not include monitoring, or when this server is over the plan's limit of monitored servers), then starts sending as soon as the site accepts. If the site no longer recognises the link, the service stops until you run /bench link again; remote-monitoring.enabled is left as you set it.

Over the plan's limit of monitored servers. When your plan includes monitoring but more servers use it than the plan allows (after moving to a smaller plan, for example), the site only accepts the most recently linked ones. The others pause: /bench monitor remote on and the console say that the limit is reached and that this server is not among the most recently linked ones, /bench monitor remote status shows "Paused - over your plan's limit of monitored servers", and the plugin checks again every 10 minutes. To resume, turn monitoring off for another server on the Monitoring page of your dashboard, or change plans.

If your plan does not allow one category of events (for example the moderation events of LiteBans), that category stops sending until the next /bench reload, and /bench monitor remote status lists it. Metrics and the other categories carry on.

Settings (intervals, events) are in the remote-monitoring section; /bench reload applies them.

Tick, GC and CPU Metrics

VoxelBench measures every server tick from startup, whatever else is enabled, without depending on spark. A single tick source and a single garbage-collection listener serve the whole plugin: the /api/metrics performance section, the tick duration placeholders and remote monitoring read the same measurements, so each tick is measured once and every view agrees. There is no setting, and benchmark scores are measured exactly as before.

Nothing in this section is sent to voxelbench.com: remote snapshots carry only the fields listed in What a snapshot contains. A value that cannot be measured on your server is left out, never shown as 0.

Where tick durations come from

Platformtick_time_sourceWhat is measured
Paper and its forks (Purpur, Pufferfish…)paper_tick_eventThe exact duration of every server tick.
Spigotmain_thread_cpuThe main thread's CPU time between two ticks. A tick that waits on disk or network counts for less than its wall-clock time; it is the best the Spigot API allows, and the same measurement as the mspt remote monitoring sends from Spigot.
Foliafolia_region_tick_eventThe exact duration of every region tick, all regions together. There is no tps: region ticks per second are not TPS. A server where no region is ticking (no player online, nothing force-loaded) measures nothing. Remote monitoring reads the same region ticks and measures each region on its own (see What a snapshot contains).

A Paper fork that ships the tick event but never fires it switches to main_thread_cpu about five seconds after startup.

Windows

/api/metrics shows rolling windows of 10 seconds, 1 minute, 5 minutes and 15 minutes, recomputed at most once per second. They overlap. Right after a restart, a window only covers the time since startup (covered_s). The tick history holds about 20,000 ticks (17 minutes at 20 TPS): on Folia, with several regions ticking, the longest windows can cover less than their length, and covered_s says so.

Fields

FieldMeaning
tick_time_sourceWhere tick durations come from (see above).
heap_after_gc_mbHeap still in use after garbage collection: the same value remote monitoring sends. One value for the section, not per window.
covered_s, ticksSeconds the window actually covers, and the ticks in it.
tpsTicks per second over the window, capped at 20 like the remote tps (Paper and Spigot; not on Folia).
mspt_mean, mspt_p50, mspt_p95, mspt_p99, mspt_maxTick duration in ms: mean, median, 95th and 99th percentile, longest tick. Percentiles are nearest-rank values of real ticks, never interpolated, like the remote mspt_p95.
gc_stw_msStop-the-world garbage collection time over the window, from the JVM's cumulative counters: the same measurement as the remote gc_stw_ms.
gc_major_count, gc_major_pause_msCollections of the old generation or the whole heap (G1 Full GC, Parallel and Serial full collections, Shenandoah pauses, non-generational ZGC pauses, generational ZGC major pauses) and their total pause time.
gc_minor_count, gc_minor_pause_msYoung collections (including generational ZGC minor pauses) and their total pause time.
gc_max_pause_ms, gc_max_pause_collector, gc_max_pause_causeLongest single pause, including G1's Remark and Cleanup pauses, with its collector and cause; 0 when there was none.
alloc_rate_mb_sEstimate of the allocation rate: memory freed by collections plus heap growth, divided by the duration. Close to exact with G1, Parallel and Serial; underestimated with ZGC and Shenandoah, which report only what a concurrent cycle freed minus what was allocated meanwhile.
cpu_processAverage CPU used by the server process over the window, in % of all the processors the JVM may use: the whole machine, unless a container limit or CPU affinity restricts the JVM.
cpu_systemAverage CPU use of the whole machine, all processes included (Linux only).

The major, minor and longest-pause fields come from the JVM's per-collection notifications, which report whole milliseconds: their sum is close to gc_stw_ms without matching it exactly, and ZGC's sub-millisecond pauses mostly read as 0 there. ZGC and Shenandoah report each pause phase separately, so one cycle counts several pauses. Concurrent GC work that does not stop the server is never counted as a pause. On a JVM without GC notifications (no com.sun.management), these fields and the allocation rate are left out; gc_stw_ms stays.

The values at the top of /api/metrics (metrics.tps, metrics.mspt, metrics.cpu_usage…) keep their meaning: they come from VoxelBench's tick monitor (the server's own TPS and MSPT while it is not running) and from the operating system, not from these windows.

API Endpoint

The /api/metrics endpoint returns current server metrics as JSON. It is available while the web server is running, even when the HTML dashboard is disabled.

# Without auth
curl http://your-server:8080/api/metrics

# With API key (authentication enabled)
curl -H "Authorization: Bearer YOUR_KEY" http://your-server:8080/api/metrics

Response shape (values shortened):

{
  "timestamp": 1757937600000,
  "metrics": {
    "tps":       { "value": 19.98, "formatted": "20.0/20", "name": "TPS", "unit": "ticks/s" },
    "mspt":      { "value": 12.4, "formatted": "12.4 ms", "name": "MSPT", "unit": "ms" },
    "ram_used":  { "value": 50.0, "formatted": "2048/4096 MB (50%)", "name": "RAM Used", "unit": "%" },
    "ram_free":  { "value": 50.0, "formatted": "2048 MB (50%)", "name": "RAM Free", "unit": "%" },
    "cpu_usage": { "value": 23.5, "formatted": "...", "name": "CPU", "unit": "%" },
    "entities":  { "value": 812.0, "formatted": "...", "name": "Entities", "unit": "" },
    "chunks":    { "value": 1024.0, "formatted": "...", "name": "Chunks", "unit": "" },
    "players":   { "value": 15.0, "formatted": "...", "name": "Players", "unit": "" }
  },
  "server": { "name": "Paper", "version": "...", "maxPlayers": 100, "onlinePlayers": 15 },
  "entityBreakdown": {
    "hostile": 120, "passive": 300, "neutral": 40, "items": 250,
    "projectiles": 12, "vehicles": 5, "drops": 60, "other": 25, "total": 812
  },
  "performance": {
    "tick_time_source": "paper_tick_event",
    "heap_after_gc_mb": 812,
    "windows": {
      "10s": {
        "covered_s": 10.0, "ticks": 200, "tps": 20.0,
        "mspt_mean": 3.1, "mspt_p50": 2.8, "mspt_p95": 5.9, "mspt_p99": 8.4, "mspt_max": 11.2,
        "gc_stw_ms": 6, "gc_major_count": 0, "gc_major_pause_ms": 0, "gc_minor_count": 1, "gc_minor_pause_ms": 6,
        "gc_max_pause_ms": 6, "gc_max_pause_collector": "G1 Young Generation", "gc_max_pause_cause": "G1 Evacuation Pause",
        "alloc_rate_mb_s": 38.5, "cpu_process": 7.4, "cpu_system": 12.9
      },
      "1m": { "...": "same fields" },
      "5m": { "...": "same fields" },
      "15m": { "...": "same fields" }
    }
  }
}

In metrics, the value of ram_used and ram_free is a percentage of the maximum heap, and their unit is %; the megabytes are in formatted. Up to VoxelBench 2.0.2, their unit said MB for the same percentage: a tool that trusted unit read 50 MB where the server meant 50 %. The same document is sent by push mode.

performance is described in Tick, GC and CPU Metrics. It comes after the other sections, which are unchanged, and fields that are not measured on your server are absent.

This endpoint is useful for:

  • Custom monitoring scripts
  • Grafana data sources
  • External dashboards
  • Alerting systems