Docs Run sipnab as a service
Run sipnab as a service
Start and change the packaged systemd unit, write one of your own, read the log, signals and crash reports, rotate the audit files, and read a service that does not start.
On this page
Use this page to keep sipnab running under systemd on a Linux server: a capture that survives a reboot and an SSH disconnect, or a listener that other tools reach over the network.
It covers:
- the unit the
.deband.rpmpackages install, and how to change it; - a unit of your own, for an install from the installer or a tarball;
- where the log goes, and what
--syslogsends; - what each signal does, and how a change to the configuration takes effect;
- where a crash report goes under a service;
- rotating the audit and provenance files;
- reading a service that does not start;
- checking that a running service is healthy.
Every output on this page comes from sipnab 0.5.232 from the arm64 .deb,
installed with apt on Debian 13 with systemd 257, unless a section
says otherwise.
Before you start
- A Linux host with systemd. macOS and other platforms are not covered here.
- sipnab installed. See Installing sipnab. The
.deband.rpmpackages install the unit and create thesipnabuser. The installer and the tarballs install the binary only. - Nothing to change on the SIP server for a capture of the host’s own interfaces. A HEP listener needs the SIP server’s HEP tracing pointed at it: see Wire HEP from your SIP stack to a central sipnab.
- A firewall rule for any port you bind to a routable address. A unit that
binds only
127.0.0.1needs none.
Start the packaged unit
The .deb and .rpm packages install sipnab.service and leave it stopped.
It runs:
/usr/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090
-N -d anycaptures on every interface, headless.--no-cli-printkeeps the per-message stream out of the journal.--syslogsends security alerts to syslog. See Where the log goes.--metrics 127.0.0.1:9090serves Prometheus metrics on loopback.- The unit runs as the
sipnabuser, withCAP_NET_RAWandCAP_NET_ADMINas ambient capabilities. Nothing in it runs as root. - It may write only to
/var/lib/sipnab(StateDirectory=sipnab, mode 0700).
Enable it and start it:
sudo systemctl enable --now sipnab
Check it worked
Ask systemd for its state:
systemctl status sipnab --no-pager
● sipnab.service - sipnab SIP/RTP capture and analysis daemon
Loaded: loaded (/usr/lib/systemd/system/sipnab.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-11-03 14:34:43 UTC; 3s ago
Main PID: 218 (sipnab)
CGroup: /system.slice/sipnab.service
└─218 /usr/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090
Read its startup lines. The first line, the generated capture filter, is long and left out here:
journalctl -u sipnab --no-pager -o cat
INFO sipnab::capture::live: Capturing on 'any' (link_type=113, snaplen=65535)
INFO sipnab::security::alerting: Syslog output enabled (facility=LOCAL0)
INFO sipnab::output::prometheus_server: Prometheus metrics server listening on 127.0.0.1:9090
Scrape the metrics endpoint:
curl -s 127.0.0.1:9090/metrics | head -3
# HELP sipnab_responses_total Total SIP responses by code class
# TYPE sipnab_responses_total counter
sipnab_responses_total{code="1xx"} 0
Confirm it runs as sipnab and holds only the two capabilities:
# Run all of these, in order.
ps -o user=,args= -C sipnab
grep CapEff "/proc/$(pgrep -x sipnab)/status"
sipnab /usr/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090
CapEff: 0000000000003000
0x3000 is bit 12 (CAP_NET_ADMIN) and bit 13 (CAP_NET_RAW).
Prometheus also listens on port 9090 by default. On a host that runs both,
move one of them, for example with --metrics 127.0.0.1:9100 in a drop-in.
Change the packaged unit with a drop-in
A package upgrade replaces /usr/lib/systemd/system/sipnab.service. Put your
changes in a drop-in, which the upgrade leaves alone. A drop-in that changes the
command clears ExecStart= first, then gives the whole new command.
This drop-in turns on the REST API on every address. sipnab refuses a routable
--api with no credential, so the signing key comes from a file that only root
reads. systemd reads that file before it starts sipnab, so the key never
appears in the process list.
-
Write the signing key:
sudo sh -c 'umask 077; printf "SIPNAB_API_SIGNING_KEY=%s\n" "$(openssl rand -hex 32)" > /etc/sipnab/api.env' -
Create the drop-in.
sudo systemctl edit sipnabwrites the same lines to a drop-in namedoverride.conf, through an editor:# Run all of these, in order. sudo mkdir -p /etc/systemd/system/sipnab.service.d sudo tee /etc/systemd/system/sipnab.service.d/api.conf >/dev/null <<'EOF' [Service] EnvironmentFile=/etc/sipnab/api.env ExecStart= ExecStart=/usr/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090 \ --api 0.0.0.0:8080 --api-allowed-host capture01.example.net EOF -
Load it and restart:
# Run all of these, in order. sudo systemctl daemon-reload sudo systemctl restart sipnab
Check it: mint a token from the same key, then ask for the statistics with and without it.
# Run all of these, in order.
TOKEN="$(sudo sh -c 'set -a; . /etc/sipnab/api.env; sipnab --mint-token')"
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: capture01.example.net' http://127.0.0.1:8080/v1/stats
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: capture01.example.net' -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/v1/stats
401
200
Bearer-token authentication covers token lifetimes, scopes and revocation. Serve the API over HTTPS before you hand a token to a client on another host: see Secure every listener.
Write a unit of your own
The installer and the release tarballs install /usr/local/bin/sipnab and
nothing else. Give the service its own user and grant the capabilities in the
unit.
-
Create the user:
sudo useradd --system --home /nonexistent --shell /usr/sbin/nologin sipnab -
Write the unit. It is the packaged unit with the path changed:
sudo tee /etc/systemd/system/sipnab.service >/dev/null <<'EOF' [Unit] Description=sipnab SIP/RTP capture and analysis daemon After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090 User=sipnab Group=sipnab AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true StateDirectory=sipnab StateDirectoryMode=0700 LimitNOFILE=65535 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF -
Start it:
# Run all of these, in order. sudo systemctl daemon-reload sudo systemctl enable --now sipnab
Then run the checks in Check it worked.
Two rules decide whether such a unit can capture and read its files:
-
Grant capture in the unit, with
AmbientCapabilities=, not withsetcap.NoNewPrivileges=truestops a file capability taking effect when systemd starts the binary. A unit withNoNewPrivileges=true,User=sipnabandsetcap cap_net_raw+epon the binary fails to capture:Permission denied on 'any'. Grant capture capabilities once (Linux), then re-run without sudo: sipnab.service: Main process exited, code=exited, status=1/FAILUREThe lines after the first suggest
sipnab --setup-caps,sudoorsetcap, none of which helps underNoNewPrivileges=true. Add theAmbientCapabilities=andCapabilityBoundingSet=lines instead. -
Every file the unit names must be readable by the unit’s user. sipnab reads token, key and certificate files as
sipnab, not as root. A token file that root owns with mode 0600 stops the run with exit status 2:--mcp-token-file '/etc/sipnab/mcp.token': Permission denied (os error 13) sipnab.service: Main process exited, code=exited, status=2/INVALIDARGUMENTGive the group read access and keep everyone else out:
# Run all of these, in order. sudo chown root:sipnab /etc/sipnab/mcp.token sudo chmod 640 /etc/sipnab/mcp.tokenA file systemd reads itself, such as an
EnvironmentFile=, can stayroot:rootmode 0600.
To confine the process further, sipnab has a Landlock file sandbox
(--sandbox) and a seccomp filter (--seccomp). Their reference rows are in
the Security section of the CLI reference.
Where the log goes
-
sipnab writes its log to stderr. systemd puts stderr in the journal, so
journalctl -u sipnabshows it. -
SIPNAB_LOGsets the level. It takeserror,warn,info,debug,trace, or a filter such assipnab::capture=debug. A headless run logs atinfowhen the variable is unset, and atwarnwith--quiet. -
--syslogsends security alerts, not the log. Each alert goes to syslog with facilitylocal0, prioritywarning, and the identifiersipnab:facility=local0 identifier=sipnab priority=4 [ALERT] scanner src=127.0.0.1 method="REGISTER" ua="friendly-scanner" detection=ua_patternThe alert also appears in the log on stderr. Every detector is off by default and the packaged unit names none, so it sends no alert until a drop-in or
sipnab.tomlturns one on. -
sipnab colors its log lines even when stderr is not a terminal. The journal then stores the escape sequences, and
journalctl -o cator agrepover an export shows them as[2mand[32m. SetNO_COLORto turn them off:[Service] Environment=NO_COLOR=1 Environment=SIPNAB_LOG=info
Stop, rotate and reconfigure
sipnab handles three signals:
| Signal | What sipnab does |
|---|---|
SIGTERM (systemctl stop) | Stops capturing, flushes the open -O file, prints the end-of-run summary, and exits 0. |
SIGINT (Ctrl-C) | The same as SIGTERM. |
SIGUSR1 | Starts a new -O file at the next captured packet. |
A stop under systemd ends with lines such as these:
INFO sipnab::capture::live: Live capture on 'any' finished: 0 packets, no drops
INFO sipnab::app::batch: sipnab: 0 packets captured, 0 SIP messages, 0 RTP packets across 0 streams
sipnab.service: Deactivated successfully.
SIGHUP is not handled. It ends the process at once, with no summary and
no flush. systemd counts a process ended by SIGHUP as a clean exit, so
Restart=on-failure does not restart it, and the service stays down:
# Run all of these, in order.
sudo kill -HUP "$(systemctl show -p MainPID --value sipnab)"
systemctl show -p ActiveState,Result,ExecMainCode,ExecMainStatus sipnab
Result=success
ExecMainCode=2
ExecMainStatus=1
ActiveState=inactive
That state held past the unit’s RestartSec=5. ExecMainCode=2 means a signal ended the process, and ExecMainStatus=1 is
SIGHUP. The packaged unit has no ExecReload=, so systemctl reload
refuses:
Failed to reload sipnab.service: Job type reload is not applicable for unit sipnab.service.
A configuration change takes effect at the next start. sipnab reads
sipnab.toml once, at startup, and reads its flags once. After you edit the
file or a drop-in, restart:
# Run all of these, in order.
sudo systemctl daemon-reload
sudo systemctl restart sipnab
A restart starts a new capture: sipnab no longer holds the dialogs, streams
and findings of the previous one. Two files sipnab does re-read while it runs: a token revocation list
(--api-revoked-file, --mcp-revoked-file), when its modification time
changes, and a key log named by --keylog with --keylog-watch.
Rotate a capture file on demand
SIGUSR1 closes the current -O file and starts the next, numbered after the
first. In this run, sipnab wrote three packets, received SIGUSR1, wrote two,
received it again, and wrote one:
sudo kill -USR1 "$(pgrep -x sipnab)"
INFO sipnab::capture::writer: Writing packets to '/root/cap/sip.pcap' (pcap, mode=raw)
INFO sipnab::capture::writer: Rotating output to '/root/cap/sip_00001.pcap' (seq=1, wrote 867 bytes in 2.035899678s)
INFO sipnab::capture::writer: Rotating output to '/root/cap/sip_00002.pcap' (seq=2, wrote 578 bytes in 2.027884822s)
- The new file opens when the next packet arrives, not when the signal does. On an idle link the file named in the last line appears later.
- sipnab buffers the open file. It can read as 0 bytes until sipnab flushes it at the next rotation or at the stop.
- To rotate by size or time instead, use
--split filesize:Nor--split duration:N, and--split-keep Nto delete the oldest files. See Run a capture that stops on its own.
Keep crash reports
When sipnab panics it writes a crash report. The default directory is
$HOME/.local/state/sipnab. The packaged sipnab user’s home is
/nonexistent, and ProtectSystem=strict makes the rest of the file system
read-only, so under the packaged unit sipnab cannot write the report:
sipnab panicked at src/main.rs:297:9:
panic-selftest: intentional panic to verify crash handling
failed to write crash report: Read-only file system (os error 30)
sipnab.service: Main process exited, code=exited, status=101/n/a
The report’s text still reaches the journal. To keep the file as well, point
report_dir at the unit’s state directory in /etc/sipnab/sipnab.toml:
[crash]
report_dir = "/var/lib/sipnab"
After a restart the same panic writes the file:
crash report written to /var/lib/sipnab/sipnab-crash-20261103-143506-290-0.log
To check this on your own unit, add --panic-selftest to a drop-in’s
ExecStart= and set Restart=no in it, restart, read the journal, and then
remove the drop-in. --help does not list --panic-selftest: it panics as
soon as sipnab starts. Send us a crash
report says what the report holds
and where to send it.
Rotate the audit and provenance files
Three flags append one JSON line per event to a file and never truncate it:
--mcp-audit-file: every MCP tool call;--tui-audit-file: every operator action in the terminal interface;--run-provenance-file: one line per run, at startup.
The two audit files stay open for the whole run. Rename one, and sipnab keeps
writing to the renamed file until it restarts. Rotate them with
copytruncate, which copies the file and then empties it in place:
/var/log/sipnab/*.jsonl {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
copytruncate
}
Save that as /etc/logrotate.d/sipnab. ProtectSystem=strict leaves the unit
no writable directory outside /var/lib/sipnab, so add LogsDirectory=sipnab
to a drop-in. systemd then creates /var/log/sipnab for the sipnab user. An
audit file in a directory the unit cannot write stops the run with exit
status 2:
--mcp-audit-file /var/log/sipnab2/mcp-audit.jsonl: Read-only file system (os error 30). sipnab refuses to serve MCP without the audit trail it was asked for
This run used the unit from Write a unit of your own
with a drop-in adding LogsDirectory=sipnab and
--mcp-audit-file /var/log/sipnab/mcp-audit.jsonl. After one tool call,
logrotate -f /etc/logrotate.d/sipnab, and a second tool call, the second
record landed in the emptied file as a complete line:
-rw------- 1 sipnab sipnab 195 Nov 3 14:43 mcp-audit.jsonl
-rw------- 1 sipnab sipnab 195 Nov 3 14:43 mcp-audit.jsonl.1
{"args":"{}","caller":"127.0.0.1:38978 bearer-verified scope=full","elapsed_ms":0,"error":null,"id":"2","outcome":"ok","seq":2,"tool":"capture_status","ts":"2026-11-03T14:43:22.918525017+00:00"}
seqcontinues across the rotation, so the new file starts atseq2 here, not at 1. A gap is still a gap: compare the lastseqof the old file with the first of the new.copytruncateloses a record that sipnab writes between the copy and the truncation. The window is the timelogrotatetakes to copy the file.- sipnab writes the provenance file once per run, at startup, so any rotation method works for it.
A service that does not start
systemctl status sipnab names the exit status, and the journal names the
reason:
# Run all of these, in order.
systemctl status sipnab --no-pager | sed -n 3p
journalctl -u sipnab -n 20 --no-pager -o cat
sipnab prints the reason it stops on stderr at every log level, so the journal
has it even with SIPNAB_LOG=off.
| Exit status | Meaning |
|---|---|
1 | A runtime failure, or a value in the config file that sipnab refuses |
2 | A flag sipnab refuses, or a pair of flags it refuses together |
101 | sipnab panicked. See Keep crash reports |
Exit codes lists every code. Error messages explains each message and what to do about it.
The refusals a service meets most often:
| The journal says | Exit | Fix |
|---|---|---|
REST API refuses to start: --api 0.0.0.0:18080 is non-loopback but no --api-key / SIPNAB_API_KEY or --api-signing-key / SIPNAB_API_SIGNING_KEY was supplied. | 2 | Bind 127.0.0.1, or give a key as in Change the packaged unit with a drop-in |
MCP HTTP refuses to start: --mcp-bind 0.0.0.0:18731 is non-loopback but no --mcp-token / --mcp-token-file / SIPNAB_MCP_TOKEN / --mcp-signing-key / --mcp-signing-key-file / SIPNAB_MCP_SIGNING_KEY was supplied. Bind 127.0.0.1, or supply one of them. | 2 | Bind 127.0.0.1, or give a token file |
metrics server refuses to start: --metrics 0.0.0.0:19090 is non-loopback but no --metrics-auth / --metrics-auth-file was supplied. | 2 | Bind 127.0.0.1, or give --metrics-auth-file |
HEP listener refuses to start: --hep-listen 0.0.0.0:19060 is non-loopback but neither a shared secret (--hep-auth / --hep-auth-file) nor a source allowlist (--hep-allow) was configured. | 1 | Add --hep-allow with your SIP servers’ addresses, or a secret |
--api-tls-cert (or [api] tls_cert) c.pem was given without --api-tls-key (or [api] tls_key) | 2 | Give both halves of the pair |
k.pem: the API TLS private key is world-readable (mode 0644); chmod 600 it | 2 | chmod 600 the key, or 640 with the sipnab group |
failed to bind API to 127.0.0.1:18081: Address already in use (os error 98) | 2 | Another process holds the port. ss -ltnp names it |
Permission denied on 'eth0'. Grant capture capabilities once (Linux), then re-run without sudo: | 1 | Grant capture in the unit, as in Write a unit of your own |
--mcp-token-file '/etc/sipnab/mcp.token': Permission denied (os error 13) | 2 | Make the file readable by the unit’s user |
--mcp-audit-file /var/log/sipnab2/mcp-audit.jsonl: Read-only file system (os error 30) | 2 | Add LogsDirectory=sipnab and write under /var/log/sipnab |
invalid config value: [capture] snaplen must be > 0 | 1 | Correct the key the message names in sipnab.toml |
The HEP listener’s refusal exits 1 where the other three listeners exit 2.
A misspelled key in sipnab.toml does not stop the run. sipnab logs a
warning and ignores the key:
WARN sipnab::config: Unknown config key: capture.snaplenn
Run sipnab -D --config /etc/sipnab/sipnab.toml after each edit. It prints the
same warning and the configuration sipnab would use, then exits.
Check that a running service is healthy
| Listener | Route | Credential | Answer |
|---|---|---|---|
REST API (--api) | GET /health | none | 200, body ok |
MCP over HTTP (--mcp-transport http) | GET /health | the bearer token | 200, body ok; 401 without it |
Metrics (--metrics) | GET /metrics | the Basic credential, on a routable bind | 200 and the metrics |
The standalone metrics server has no /health route: it answers 404 there.
Use /metrics as its check.
A capture that runs but sees nothing is not a failed service. See A live capture that sees nothing and A HEP collector that receives nothing.
Related
- Installing sipnab: packages, upgrades, and what an upgrade keeps.
- Bearer-token authentication: tokens for the REST API and MCP.
- Connect an AI agent to sipnab: an MCP server as a service.
- Run a live capture without giving sipnab root.
- Config reference:
[crash],[privilege]and every other key.