Two paths
You can get a systemd-managed Comis daemon two ways: Recommended: the one-line installer. The script athttps://comis.ai/install.sh
creates the comis system user, lays out /etc/comis, writes a managed
comis.service unit, sets up sudoers rules so the service user can start/stop/restart
its own daemon without root, and starts everything. This is the path the official
VPS install guide uses.
# managed-by: comis-installer. If
you edit it by hand, the installer will refuse to overwrite it on upgrade — so
manual edits stick around.
Manual: the steps below. Use this when you need a unit file under your own
control (different paths, different hardening, different user). The rest of this
page walks through that.
Prerequisites
Before setting up the systemd service, make sure you have:- Node.js 22 or newer installed on your server
- Comis built — run
pnpm buildin the Comis directory - A dedicated system user for running Comis (created in step 1 below)
Setup
1
Create a system user
Create a dedicated The
comis user that has no login shell. This is a security
best practice — the daemon runs under its own user with limited permissions.-r flag creates a system user (no home directory, no login). The
-s /sbin/nologin flag prevents anyone from logging in as this user.2
Install Comis
Copy your built Comis files to a system directory and create the data directory:
/opt/comis/— where the application code lives (read-only at runtime)/var/lib/comis/— where the database, logs, and runtime data are stored (read-write)
3
Create the configuration
Create a directory for the config file and environment variables:Create the environment file at
/etc/comis/env with your API keys and config path:4
Install the service file
Create the systemd unit file at Then reload systemd to pick up the new file:
/etc/systemd/system/comis.service:--permission also disables fd-based fs APIs (fsync, fchmod, fchown) at the daemon-process level. Credential file writes are best-effort durability (no fsync) and file permissions are best-effort. This is guarded at all call sites — the daemon will not crash — but the tradeoff is documented in Node Permissions — Production fd-API Disablement.5
Start the service
Enable the service (so it starts on boot) and start it immediately:Expected output:
6
Verify it is running
Check the service status:You should see output similar to:The key indicators are:
- Active: active (running) — the daemon is running
- Status: “Comis daemon started” — the daemon completed its startup sequence
Service file explained
Here is what each important section of the unit file does:Type=exec
systemd considers the service started onceexecve() returns. In-process
liveness is observed by Comis’s ProcessMonitor (event loop delay tracking)
and surfaced on the /health HTTP endpoint; crash recovery is handled by
Restart=on-failure below. Comis does not participate in the systemd
liveness-ping protocol — operators who require kernel-level watchdog
integration can add it via a systemd drop-in.
Restart=on-failure + RestartSec=5s
If the daemon crashes (exits with a non-zero code), systemd waits 5 seconds and then starts it again automatically. This covers unexpected errors, out-of-memory kills, and unhandled exceptions. Normal stops (viasystemctl stop) do not trigger
a restart.
KillMode=process
On stop, systemd signals only the main daemon process, not the entire control group (the defaultKillMode=control-group would SIGKILL every process in the
cgroup). This is required for durable terminal drives
(drive.durable): a durable session runs its child
inside a detached tmux server that, although reparented to init, stays a member of
the daemon’s cgroup. Under the default control-group kill, every systemctl restart
would destroy that tmux server and the session could never survive a restart. With
KillMode=process, the detached tmux server is left running and a restarted daemon
re-attaches to it by name.
Non-durable sessions are still cleaned up on stop: graceful shutdown runs the daemon’s
own teardown, and the Terminal Worker exits as soon as its stdin closes (its sandboxed
children carry --die-with-parent). The trade-off is that after a hard daemon crash
(no graceful shutdown), other long-lived children such as MCP servers or the browser may
linger for a few seconds until Restart=on-failure respawns the daemon.
MemoryMax=2G + TasksMax=100
Resource limits prevent the daemon from consuming too many system resources. If memory exceeds 2 GB, systemd kills the process (which then triggers a restart). TasksMax limits the number of threads and processes the daemon can create.Node.js —permission flags
The--permission flag enables the Node.js permission model, which restricts what
the daemon can access:
--allow-fs-read=/opt/comis— can read application code only from/opt/comis--allow-fs-write=/var/lib/comis— can write data only to/var/lib/comis--allow-child-process— can spawn child processes (needed for some tools)
<data-dir>/models/whisper/ (e.g. /var/lib/comis/models/whisper/), so the
existing --allow-fs-write=/var/lib/comis and ReadWritePaths=/var/lib/comis already
cover it — no additional --allow-fs-write or ReadWritePaths entry is needed for
local STT.
See Node Permissions — Production fd-API Disablement for the impact of
--permission on daemon-process fd-based APIs (fsync, fchmod, fchown), and Keyless local STT (whisper) engine for why local STT needs no new flag.EnvironmentFile=-/etc/comis/env
Loads environment variables (like API keys andCOMIS_CONFIG_PATHS) from the
specified file. The - prefix means systemd will not fail if the file is missing —
it simply skips loading it.
Security hardening directives
The bottom section of the unit file locks down the service:Browser tool wiring (when installed with --with-browser / --with-xvfb / --with-cloakbrowser)
The browser-tool flags don’t change the security posture — they widen specific write paths just enough for the chosen browser binary to launch:
Every additional write path is named, scoped to the daemon’s home, and matches what the binary actually writes. The Chrome variant needs
~/.local/share/applications for mimeapps.list (Chrome’s default-browser registration; no flag disables it). The cloak variant doesn’t.
comis-xvfb.service companion (when installed with --with-xvfb)
A second managed unit at /etc/systemd/system/comis-xvfb.service runs Xvfb on display :99 as the same comis user:
-nolisten tcp keeps the X server on a Unix socket only; -ac is safe because the socket is owned by the comis user. The companion unit also binds a shared host dir onto its /tmp/.X11-unix so the socket it creates is reachable from the daemon:
comis.service unit picks up the display with:
PrivateTmp=yes on each unit gives the daemon its own /tmp and the X11 socket at /tmp/.X11-unix/X99 is unreachable. (JoinsNamespaceOf= was tried first but does not share the PrivateTmp /tmp content on systemd 255 — the daemon’s namespace gets an empty /tmp/.X11-unix.) /run/comis-x11 is created with mode 1777 by /etc/tmpfiles.d/comis-x11.conf so it survives reboot before the units mount it.
Manage the companion unit the same way as the main one:
bash install.sh --uninstall (no separate command needed).
Common commands
Viewing logs
systemd sends all daemon output to the journal. Usejournalctl to view logs:
Live logs (follow mode):
The daemon also writes logs to
~/.comis/logs/daemon.log (resolved against the
service user’s home — /var/lib/comis/.comis/logs/daemon.log for an installer-managed
service). See Logging for details on configuring log
levels and rotation.Related pages
Daemon
How the daemon starts, runs, and shuts down.
pm2
Alternative process manager for macOS and Linux.
Docker
Run Comis in a Docker container.
Logging
Configure log levels, rotation, and structured output.
Troubleshooting
Solutions to common issues.
