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 .deb and .rpm packages 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 --syslog sends;
  • 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 .deb and .rpm packages install the unit and create the sipnab user. 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.1 needs 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 any captures on every interface, headless.
  • --no-cli-print keeps the per-message stream out of the journal.
  • --syslog sends security alerts to syslog. See Where the log goes.
  • --metrics 127.0.0.1:9090 serves Prometheus metrics on loopback.
  • The unit runs as the sipnab user, with CAP_NET_RAW and CAP_NET_ADMIN as 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 sipnab writes the same lines to a drop-in named override.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 with setcap. NoNewPrivileges=true stops a file capability taking effect when systemd starts the binary. A unit with NoNewPrivileges=true, User=sipnab and setcap cap_net_raw+ep on 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/FAILURE
    

    The lines after the first suggest sipnab --setup-caps, sudo or setcap, none of which helps under NoNewPrivileges=true. Add the AmbientCapabilities= and CapabilityBoundingSet= 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/INVALIDARGUMENT
    

    Give 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.token
    

    A file systemd reads itself, such as an EnvironmentFile=, can stay root:root mode 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 sipnab shows it.

  • SIPNAB_LOG sets the level. It takes error, warn, info, debug, trace, or a filter such as sipnab::capture=debug. A headless run logs at info when the variable is unset, and at warn with --quiet.

  • --syslog sends security alerts, not the log. Each alert goes to syslog with facility local0, priority warning, and the identifier sipnab:

    facility=local0 identifier=sipnab priority=4 [ALERT] scanner src=127.0.0.1 method="REGISTER" ua="friendly-scanner" detection=ua_pattern
    

    The 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.toml turns one on.

  • sipnab colors its log lines even when stderr is not a terminal. The journal then stores the escape sequences, and journalctl -o cat or a grep over an export shows them as [2m and [32m. Set NO_COLOR to turn them off:

    [Service]
    Environment=NO_COLOR=1
    Environment=SIPNAB_LOG=info
    

Stop, rotate and reconfigure

sipnab handles three signals:

SignalWhat 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.
SIGUSR1Starts 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:N or --split duration:N, and --split-keep N to 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"}
  • seq continues across the rotation, so the new file starts at seq 2 here, not at 1. A gap is still a gap: compare the last seq of the old file with the first of the new.
  • copytruncate loses a record that sipnab writes between the copy and the truncation. The window is the time logrotate takes 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 statusMeaning
1A runtime failure, or a value in the config file that sipnab refuses
2A flag sipnab refuses, or a pair of flags it refuses together
101sipnab 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 saysExitFix
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.2Bind 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.2Bind 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.2Bind 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.1Add --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)2Give both halves of the pair
k.pem: the API TLS private key is world-readable (mode 0644); chmod 600 it2chmod 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)2Another process holds the port. ss -ltnp names it
Permission denied on 'eth0'. Grant capture capabilities once (Linux), then re-run without sudo:1Grant capture in the unit, as in Write a unit of your own
--mcp-token-file '/etc/sipnab/mcp.token': Permission denied (os error 13)2Make the file readable by the unit’s user
--mcp-audit-file /var/log/sipnab2/mcp-audit.jsonl: Read-only file system (os error 30)2Add LogsDirectory=sipnab and write under /var/log/sipnab
invalid config value: [capture] snaplen must be > 01Correct 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

ListenerRouteCredentialAnswer
REST API (--api)GET /healthnone200, body ok
MCP over HTTP (--mcp-transport http)GET /healththe bearer token200, body ok; 401 without it
Metrics (--metrics)GET /metricsthe Basic credential, on a routable bind200 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.