Skip to content

systemd Service

The netclaw daemon (netclawd) runs as a systemd user service — no sudo, no root, just your own account. One CLI command sets everything up.

  • Linux with systemd 236+ (Ubuntu 20.04+, Debian 11+, Fedora 36+, RHEL 8+, etc.) — check with systemctl --version
  • dbus-user-session installed — required for systemctl --user on headless/minimal servers (e.g., sudo apt install dbus-user-session on Debian/Ubuntu, then re-login)
  • Netclaw installed — see Installation if you don’t have it yet
  • An initialized ~/.netclaw directory — run netclaw init first
  • A configured provider and model — see Models
Terminal window
netclaw daemon install

This writes a unit file to ~/.config/systemd/user/netclaw.service, enables it, and runs loginctl enable-linger $USER so the service survives logout.

Start it:

Terminal window
systemctl --user start netclaw

Verify:

Terminal window
netclaw status

Netclaw status output showing a running daemon

netclaw daemon install generates this at ~/.config/systemd/user/netclaw.service:

[Unit]
Description=Netclaw Daemon
After=network.target
[Service]
Type=simple
ExecStart=/path/to/netclawd
ExecStop=/path/to/netclaw daemon stop
Restart=always
RestartSec=5
Environment=DOTNET_ENVIRONMENT=Production
[Install]
WantedBy=default.target

The /path/to/ placeholders are resolved to actual binary locations by netclaw daemon install — the literal string never gets written. ExecStop uses the CLI’s daemon stop command instead of a raw signal, which lets the daemon fire shutdown webhooks and drain active sessions before exiting. After ExecStop completes, systemd sends SIGTERM with a 10-second grace period. If the process is still alive after that, SIGKILL finishes it. In practice the daemon shuts down well within that window.

If you prefer to create the service file yourself, adjust the ExecStart and ExecStop paths to wherever your binaries actually live:

Terminal window
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/netclaw.service << 'EOF'
[Unit]
Description=Netclaw Daemon
After=network.target
[Service]
Type=simple
ExecStart=/path/to/netclawd
ExecStop=/path/to/netclaw daemon stop
Restart=always
RestartSec=5
Environment=DOTNET_ENVIRONMENT=Production
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable netclaw.service
loginctl enable-linger $USER
systemctl --user start netclaw

The %h specifier (expands to your home directory) also works in ExecStart/ExecStop paths if you prefer relative-to-home references.

ActionCommand
Startsystemctl --user start netclaw
Stopsystemctl --user stop netclaw
Restartsystemctl --user restart netclaw
Statussystemctl --user status netclaw
Logs (journal)journalctl --user -u netclaw
Enable on bootsystemctl --user enable netclaw
Disable on bootsystemctl --user disable netclaw

The CLI commands netclaw daemon start, netclaw daemon stop, and netclaw daemon status also work, regardless of whether systemd is managing the process.

systemd kills user services when you log out. Lingering prevents that by keeping your user slice alive.

Terminal window
# Check if lingering is enabled
ls /var/lib/systemd/linger/$USER
# Enable it (netclaw daemon install does this automatically)
loginctl enable-linger $USER

If your daemon stops every time you disconnect SSH, lingering isn’t enabled.

Config lives at ~/.netclaw/config/netclaw.json. To relocate the base directory, set NETCLAW_HOME — useful when you want data on a separate volume or need to run multiple instances with isolated state. Add it to the [Service] section of the unit file:

[Service]
Environment=NETCLAW_HOME=/data/netclaw
PathContents
~/.netclaw/config/netclaw.jsonDaemon settings
~/.netclaw/config/secrets.jsonProvider API keys and credentials
~/.netclaw/netclaw.dbSQLite database (sessions, stats)
~/.netclaw/logs/daemon.logRolling log file
~/.netclaw/netclaw.pidPID file
~/.netclaw/netclaw.lockSingleton lock (OS-backed exclusive lock)

Default binding is 127.0.0.1:5199, loopback only.

{
"Daemon": {
"Host": "127.0.0.1",
"Port": 5199
}
}

To bind a different address or port via environment variables, add them to the unit file:

[Service]
Environment=NETCLAW_Daemon__Host=127.0.0.1
Environment=NETCLAW_Daemon__Port=5200

Then reload and restart:

Terminal window
systemctl --user daemon-reload
systemctl --user restart netclaw

For remote access via Tailscale or Cloudflare Tunnel, see Exposure Modes.

A file watcher on netclaw.json picks up changes and triggers a graceful drain-and-restart automatically. One exception: Daemon.Host, Daemon.Port, and Daemon.ExposureMode require a manual systemctl --user restart netclaw — the file watcher ignores these fields because changing the listener binding mid-flight isn’t safe.

The daemon exposes two HTTP endpoints:

EndpointAuthPurpose
GET /api/health/readyNoneReturns 200 OK when the daemon is ready
GET /api/health/statusRequiredDetailed runtime status

Smoke test:

Terminal window
curl -sf http://127.0.0.1:5199/api/health/ready && echo "OK"

To block systemd from reporting the service as “started” until the daemon is actually ready, add an ExecStartPost readiness gate to your [Service] section. This delays dependent services until netclaw is genuinely accepting connections:

ExecStartPost=/bin/sh -c 'until curl -sf http://127.0.0.1:5199/api/health/ready; do sleep 2; done'

For richer metrics and log export, see OpenTelemetry.

Logs go to ~/.netclaw/logs/daemon.log as a rolling file — journalctl --user -u netclaw captures stdout/stderr from the process, but the structured application logs live in the file. Change the log level via Logging:LogLevel:Default in netclaw.json:

{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}

Valid levels: Trace, Debug, Information, Warning, Error, Critical.

Follow the log in real time:

Terminal window
tail -f ~/.netclaw/logs/daemon.log

Schema migrations are forward-only with no automatic rollback, so back up before upgrading.

Unlike the Docker deployment, bare-metal installs can self-update — the daemon periodically checks for new releases and applies them. After an update, the process exits and systemd’s Restart=always brings it back on the new version. For manual upgrades:

Terminal window
# 1. Stop the daemon
systemctl --user stop netclaw
# 2. Back up the database
cp ~/.netclaw/netclaw.db ~/.netclaw/netclaw.db.bak.$(date +%s)
# 3. Replace binaries (method depends on how you installed)
# For manual installs, download and extract the new release.
# For package manager installs, update the package.
# 4. Start the daemon
systemctl --user start netclaw
# 5. Verify
until curl -sf http://127.0.0.1:5199/api/health/ready; do sleep 2; done
echo "Daemon is ready"

To rollback: stop the daemon, restore the database backup, put the old binaries back, and start again.

Terminal window
netclaw daemon uninstall

Your data in ~/.netclaw stays untouched.

To remove manually:

Terminal window
systemctl --user stop netclaw
systemctl --user disable netclaw
rm ~/.config/systemd/user/netclaw.service
systemctl --user daemon-reload

Before diving into specific symptoms, run netclaw doctor. It checks provider connectivity, config validity, daemon health, and common misconfigurations in one pass.

Netclaw doctor output showing health check diagnostics

Lingering isn’t enabled. Fix it with loginctl enable-linger $USER and verify with ls /var/lib/systemd/linger/.

”Failed to connect to bus” when running systemctl

Section titled “”Failed to connect to bus” when running systemctl”

XDG_RUNTIME_DIR isn’t set. Common when running systemctl --user from cron or a non-login shell. On headless servers, also make sure dbus-user-session is installed (sudo apt install dbus-user-session). Export the runtime dir manually:

Terminal window
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user status netclaw

Check that the daemon is actually listening:

Terminal window
ss -tlnp | grep 5199

If nothing shows, check the daemon log:

Terminal window
tail -20 ~/.netclaw/logs/daemon.log

Common causes: port conflict (another process on 5199), JSON syntax error in netclaw.json, or missing provider configuration.

”Daemon already running” when starting

Section titled “”Daemon already running” when starting”

The lock file at ~/.netclaw/netclaw.lock is held by another process — either a daemon is already running or a previous instance didn’t exit cleanly. Check for it:

Terminal window
pgrep -a netclawd

If nothing shows, the lock file is stale. Remove it and restart:

Terminal window
systemctl --user stop netclaw
rm ~/.netclaw/netclaw.lock
systemctl --user start netclaw

User-level services require systemd 236+. Check with systemctl --version. On older systems, skip systemd and run netclaw daemon start directly — it detaches as a background process.