The audit log#
The audit log is the agent's own record of every action it took and every one it refused: who asked, with which role, from which address, what they asked for, whether it worked, and the reason when it did not. It is append-only, one JSON object per line, in its own file beside the agent's state. It is not the router's system log, and it is not the configuration history.
Three records, three questions#
| Record | Where | Answers |
|---|---|---|
| The system log | The router's journal, on the Logs page | What the system and its services said. |
| The configuration history | The router's numbered revisions, on the System → History tab | Which configurations the router applied, when, and what each change did. |
| The audit log | audit.jsonl in the agent's data directory, on the System → Audit log tab | Who asked the agent to do something, from where, and what came of it. |
For a change made through the web interface or the API, the audit log answers "who changed my NAT rule, and when" on its own: the entry for applying the changes names the person, and carries the changes themselves. The configuration history also holds the changes made at the router's console, which never pass through the agent.
What the Audit log tab shows#
The tab puts each entry in words rather than codes. The action is a sentence — "Applied changes", "Kept changes applied with auto-revert", "Restored an earlier configuration", "Restarted the agent" — and an entry that applied changes lists them the way the pending changes are listed before they are applied, one sentence per change: "Add time server time.cloudflare.com", with "With NTS" beneath it. The table gives when, who, the action with its first change ("… and 2 more"), and Done or Failed; an entry's panel gives the role, the address, what it concerned, the reason for a failure, and every change it carried.
The operation codes, the configuration paths and the commands themselves appear only in Advanced mode (Account → Preferences), which adds an Operation column and, in an entry's panel, what the entry Recorded. The screen is described in System — audit log.
What an entry holds#
{"timestamp":"2026-09-18T14:07:11Z","actor":"kate","role":"operator",
"remote_ip":"192.0.2.40","operation":"commit",
"commands":["set system host-name edge-1"],"success":true,
"seq":4187,"prev":"6f1c…","hash":"9ab2…"}| Field | Meaning |
|---|---|
timestamp | RFC 3339, UTC, stamped when the entry is recorded. |
actor | The principal's name: an account name, token:<label> for an API token, admin-token for the break-glass credential, reconcile-loop for the desired-state loop, rescue for wheelhouse-rescue at the console, upnp for ports the router opened or closed for a device, system for the agent itself. |
role | The role that principal held. |
remote_ip | The client address as the agent saw it — see the --trust-proxy note in Accounts, sessions and tokens. |
operation | What was done: commit for applying changes, commit-confirm for keeping them, discard, rollback, save, login, create-user, agent-restart, denied POST /api/commit, and so on. |
path | What the operation names, where it names something: the revision file for a restore, the account and role for a new user, the address for a restarted agent, the router for a fleet write. |
commands | The changes the entry carried, as the router's commands: what was applied, discarded or reconciled. |
success, error | Whether it worked, and the router's or the agent's own words if it did not. |
note | The agent's own remark about the entry, where it made one. |
seq, prev, hash | The chain: this entry's number, the hash of the one before it, and its own. hash is always the last field on the line. |
Every operation and field is listed in Audit entries.
Refusals are recorded too#
A request whose role is insufficient is recorded as denied <method> <path>, with the
actor, the role held, the address and the role required. So are failed sign-ins, with the
user name that was tried and the reason — no such user, bad password, bad totp —
even though the caller is told only invalid credentials, because telling the caller
which one it was would reveal which user names exist.
Failed actions are recorded as failures, with the router's message, before the caller is answered. That includes a write to another router in a fleet, which records the actor, the router and the outcome, whether it worked or not.
Adding changes to the pending changes is not recorded; applying them is, and so is discarding them, with what was discarded.
How it is stored#
Each entry is one line of JSON appended to audit.jsonl in the agent's data directory —
/config/wheelhouse/audit.jsonl on an appliance — mode 0600. Each append is flushed to
disk before the call returns, so a power cut straight after an action does not lose the
entry describing it.
A state file written by an older agent may still carry audit entries inside it; they are
moved into audit.jsonl once, when it is opened.
| Bound | Value | Effect |
|---|---|---|
| Rotation | 8 MiB | The file rotates to audit.jsonl.1; exactly one previous generation is kept, so the log costs at most twice that on disk. |
| Audit entries kept | 2000 by default | How many entries the agent keeps in memory to serve the Audit log tab and the API. Set on Agent settings. |
| Hard ceiling | 10 000 | Whatever the setting says, the in-memory tail stops there, and the agent logs a warning saying older entries stay in the file. |
At start-up the tail is loaded from the rotated generation first, so a restart shortly after a rotation still serves a full window rather than the few lines written since. A line torn by a power cut is skipped and the entries either side of it are still good.
Reading it#
The System → Audit log tab shows the newest entries, and needs only the viewer role: everyone who can see the router can see who changed it. Reading needs no licence, like every other read. From automation:
curl -sk -H "Authorization: Bearer $T" "https://<router>:8443/api/audit?limit=300"The hash chain#
Every entry carries a sequence number and the hash of the entry before it. The hash
covers the previous entry's hash and this entry's own JSON text, which is why hash
stays the last field on the line — a field a later agent adds is covered without
invalidating what was written before it.
That makes three kinds of edit detectable. Changing an entry breaks its own hash.
Deleting one breaks the sequence and the link of the entry that followed. Inserting one
breaks the same link. GET /api/audit/verify walks both generations from disk and names
the first place the record does not add up — file, line, sequence number and which of the
three it was.
A log written before the chain existed is not rewritten to acquire one. The first chained
entry seals it instead: its prev is a digest of every byte before it, and those older
entries verify as legacy rather than as tampered with. A file that simply stops,
because the power went out mid-write, is reported as stopping.
Getting a copy off the router#
Two mechanisms, answering different questions.
The syslog export copies each entry to a collector as it is written, framed as RFC 5424, before the entry is flushed to disk — deliberately, so the copy off the router survives the power cut that loses the one on it. The message is the exact JSON line the file holds, hash and sequence number included, so the collector's copy verifies with the same recipe as the file. It is set on Admin → Agent settings, under Audit log export — see Agent settings.
It is off by default, and it is not a spooler: an unreachable collector means entries
are dropped and counted, not queued on the router's flash. GET /api/audit/verify
reports that count beside the chain's verdict, because an export that has been failing
unnoticed for a week is worse than none: it was relied on.
GET /api/audit/export streams the whole log, both generations, byte for byte, as
JSON lines. Admin only. Taking the log off the router is itself recorded, as
"Downloaded the audit log", and the entry is written after the body — so a copy never
contains the record of its own export.
What it does not do#
See also#
- Accounts, sessions and tokens — where
actorcomes from. - Read plane, write plane, admin plane — what produces a
deniedentry. - Revisions and rollback — the configuration history, the other record of what changed.
- System — audit log — the screen.
- Audit entries — every field and every operation.
- Ship logs off the box — configuring the collector.
- The agent's files — where
audit.jsonlsits.