---
summary: "Linux support + companion app status"
read_when:
  - Looking for Linux companion app status
  - Enabling camera, location, or notifications on a Linux node host
  - Planning platform coverage or contributions
  - Debugging Linux OOM kills or exit 137 on a VPS or container
title: "Linux app"
---

The Gateway is fully supported on Linux. Node is the primary, default, and
recommended runtime; Bun 1.4.3+ builds with WAL-reset-safe `node:sqlite` can run
OpenClaw as an explicit opt-in. Use `pnpm` rather than Bun for dependency
installation.

## Desktop companion

The OpenClaw Linux companion is a Tauri desktop app for local and remote
Gateways. On Windows, the native [Windows Hub](/platforms/windows#recommended-windows-hub)
is recommended; this cross-platform [Tauri app](/platforms/windows#backup-tauri-desktop-app)
is the backup, available in regular OpenClaw releases with bundled Bun for a local
Windows Gateway. On Linux, it:

- automatically sets up a local Gateway for fresh installs, with manual local,
  discovered remote, direct URL, and SSH choices available if setup fails
- installs the OpenClaw CLI and runs fresh local installations on the bundled
  OpenClaw Bun fork, without requiring a global CLI install; release builds
  install their matching stable version, while development builds use
  Development for automatic setup
- attaches to a healthy Gateway before attempting service changes
- delegates install, start, stop, and restart operations to the CLI-managed systemd user service
- discovers nearby Bonjour Gateways and opens each Control UI in a route-scoped window, so several
  Gateway dashboards can stay connected and be used simultaneously
- opens the Gateway-served Control UI with its resolved authentication URL
- opens custodian onboarding after automatic local setup to prepare AI access
  and explain the selection; manual setup retains the explicit Model Setup picker
- continues into guided onboarding after connecting a new model; onboarding can
  import detected Claude Code, Codex, or Hermes memories into the agent workspace
  (the same import stays available later under Settings → Import Memory)
- remains available from the system tray when its window is closed

The window controls share the dashboard's top row. Drag empty header space or a
session title to move the window, and double-click to maximize or restore it.
The thin strip below the top resize edge also moves the window. Minimize,
maximize/restore, and close sit at the top right; the window edges remain
resizable. Closing the main window leaves OpenClaw available in the system tray.
When connecting to an older Gateway whose dashboard does not support this layout,
the companion keeps the system title bar. Update the Gateway to enable the unified
window controls.

The local startup, setup, recovery, Gateway manager, and Quick Chat screens share
light and dark styling and follow system appearance changes while open. Connection
drafts, credential visibility, and Quick Chat replies stay intact. The connected
dashboard retains its own web UI appearance setting.

Remote setup and Connection Settings use one **Authentication** choice for token
or password. **Show credential** reveals the entered value; switching types clears
the draft and masks the new field. Press Enter or **Connect to Gateway** to connect.
In Connection Settings, blank credentials reuse the saved credentials for the same
endpoint.

### Chrome extension setup

The app prepares the local Chrome native helper at startup and after CLI
installation. Release builds reuse a matching CLI or install a version-matched
browser runtime under their own app-data directory. This download does not
create, check, refresh, or restart a Gateway service, replace its runtime, or
change the selected remote connection. It requires an internet connection.

Choose **Set Up Chrome Extension…** in the tray to retry setup and open the
official Chrome Web Store listing after native registration succeeds. Google
Chrome on Linux still requires **Add to Chrome** in the Store; the app does not
use enterprise force-install policies or reopen the Store at every startup.
Once enabled, supported host-local setups pair automatically without a copied
credential. A remote-only desktop connection still needs a browser node on this
computer to expose its tabs to the remote Gateway.

Development builds use an existing local CLI rather than downloading an
unrelated stable runtime. The Windows Tauri build does not provide this
runtime installer. See [Chrome extension](/tools/chrome-extension) for approval,
disconnection, and manual recovery.

### Desktop compatibility

The Linux companion shares one Bun fork pin with the native macOS app and CI.
Runtime admission, SQLite safety checks, and the fork's disabled implicit package
auto-install remain enabled. macOS Tauri test builds keep their existing runtime
behavior; the native macOS app owns its separate bundled runtime. Windows Tauri
releases use the signed Windows fork from the shared pin for local setup; an
unsigned dry-run is not shippable. See
[Bun compatibility](/install/bun-compatibility).

Published AMD64 AppImages are built on Ubuntu 22.04 and require glibc 2.35 or
newer plus a `libstdc++` that provides `GLIBCXX_3.4.30`. Ubuntu 22.04 and
Debian 12 meet that ABI floor. RHEL 9 and Rocky Linux 9 ship glibc 2.34, so
they cannot run the published AppImage. Extracting the AppImage does not bypass
this requirement.

`.deb` installs stay owned by the system package manager; installing the download
does not add an APT repository. AppImages use the signed in-app updater.

Global shortcuts are available on X11. On Wayland, use the tray's **Quick Chat**
entry when your desktop provides a tray host; global shortcuts are unavailable.
Tray access is a shortcut fallback, not a native Wayland compatibility guarantee.

The shell does not grant microphone capture to its embedded WebKitGTK WebView,
so `getUserMedia` is expected to fail there. Open the Gateway's Control UI in a
regular browser for [Talk mode](/nodes/talk).

The desktop connects as a Gateway operator and uses the local CLI to share this
computer's desktop with its Primary Gateway. Its app-owned node exposes desktop
streaming only. Other device commands belong to the [CLI node host](/cli/node) and its
[Linux Node plugin](/platforms/linux#node-capabilities).

The [native macOS app](/platforms/macos) and [Windows Hub](/platforms/windows)
are separate applications. Windows Hub is the recommended Windows app; this
Tauri shell is the backup. The macOS Tauri build remains an opt-in test bundle.
See the platform pages for requirements and capabilities.

### Gateway selection

Open **Gateways → Manage Gateways…** from the native app or tray menu to save a
direct URL or SSH connection. Choose **Add Gateway** or **Edit** to open the
connection form; **Back to Gateways** returns to the saved list and discards
unsaved changes. Under **Authentication**, choose token or password and enter a
credential only if needed. Saved credentials stay hidden; leave the field blank
to keep them for the same connection. Switching authentication types clears the
credential you have entered. SSH certificate pins are under **Advanced connection
settings**.

The dashboard's profile menu switches only its current window; Control-click
opens an additional window. Choosing a Gateway from the native menu focuses its
existing window without reloading it, while **Open … in New Window** creates an
independent one.

The Primary Gateway continues to own Quick Chat and the desktop connection.
Changing it requires the separate **Set as Primary** confirmation on a saved
token-authenticated connection. Other Gateway windows retain their own targets.
The companion remembers successful explicit selections, returns to Primary when
that saved connection is removed, and keeps credentials in the operating
system's credential store. Linux requires an unlocked Secret Service, such as
GNOME Keyring or KWallet's Secret Service support.

An unavailable credential store shows a dismissible notice without blocking the
dashboard. Saved connections remain intact; use **Manage Gateways… → Try again**
after resolving the reported credential-store problem.

When a saved Gateway fails to load, the same window returns to its local
connection editor. Correcting the endpoint updates the remembered selection only
after the new dashboard loads successfully.

The macOS Tauri build is named **OpenClaw-Tauri** and keeps its saved connections
separate from the native **OpenClaw** app.

<a id="adopt-the-bundled-runtime" />

### Use the bundled runtime

On Linux, fresh local setup installs the Gateway on the bundled OpenClaw Bun
fork through the standard CLI. The install guard requires the service to still
be absent. A service that appears during setup blocks that installation. The app's
runtime marker is informational and never authorizes automatic service changes.

For any existing Gateway that uses another runtime, choose **Use bundled
runtime…** in the tray menu. This includes Node, an older bundled Bun after an
app update, and an operator-selected runtime. The confirmation shows the current
runtime. The CLI install checks that the service definition and runtime pin still
match what you confirmed before switching to the current bundled Bun. If either
changed, the action refuses. A paused or stopped Gateway stays stopped; choose
**Start Gateway** before switching runtimes.

The app checks Gateway health after installation. On failure it shows the error
and a CLI command to return to the previous runtime. It does not automatically
restore a runtime. To return to Node, install a
[supported Node version](/install/node-compatibility) if needed, then run:

```sh
openclaw gateway install --force --runtime node
```

To return to a particular previous executable, use
`openclaw gateway install --force --runtime-path /absolute/path/to/node-or-bun`.
The CLI owns service installation. Switching an existing Gateway's runtime
leaves its CLI launcher unchanged.

Startup and app updates never change an existing Gateway service or runtime pin.
After an app update, the current service keeps its existing runtime until you
choose **Use bundled runtime…** again. Immutable app runtime directories are
retained, so updates never remove a runtime referenced by a service. These actions
are Linux-only; macOS Tauri keeps its existing behavior, separate from the
[native macOS app](/platforms/mac/bundled-gateway).

### Desktop sharing

Open **Settings → This computer → Capabilities → Desktop sharing** to change the
setting. The macOS Tauri build labels this section **This Mac**. Sharing starts
enabled; an existing `desktop.host.enabled: false` stays off until you explicitly
enable it in the app. Your choice persists across app restarts and is independent
of **Keep computer awake**.

Sharing requires a local OpenClaw CLI, including when your Gateway is remote,
and an authenticated local VNC server. On macOS, enable **Screen Sharing** in
System Settings. Approve the computer's desktop capability on the Primary Gateway
when requested, then open its desktop from **Systems**. See
[paired node desktops](/gateway/config-browser-ui-desktop#paired-node-desktops)
for authentication, pairing, and upgrade behavior.

The status row shows whether the app's desktop process is running or needs
attention. Pairing approval and the local VNC server must also be ready before
the desktop can open. Missing CLI or invalid configuration errors appear here.
Turning sharing off, changing Primary Gateway, or quitting the app stops the old
desktop connection. Closing the window to the tray keeps sharing active.

### First-run setup

With no saved OpenClaw configuration or Gateway profiles, the app starts local
setup automatically and opens custodian onboarding in the Control UI. The
custodian prepares available AI access and explains the selection and alternatives.
**Use a different Gateway** opens Manage Gateways, **Review permissions** opens
this computer's permissions, and **Open AI setup** opens Model Setup.
The card labels the local Gateway **This computer** and saved remote Gateways
by name; its native actions retain the selected Gateway's base path.

Automatic setup uses the same bundled runtime, installer, and CLI-managed service
as manual local setup. It never automatically joins a discovered Gateway. Existing
configurations and remote setups keep their current behavior. An unreadable saved
profile store or any automatic setup failure returns to the manual choices with
the error visible.

In manual setup, choose where your assistant should live:

- **On this computer** installs any missing local prerequisites and starts the
  Gateway as a systemd user service.
- **On another computer** connects to an existing Gateway. Select a discovered
  Gateway, enter its address under **Gateway URL**, or choose **SSH tunnel**
  and enter an SSH target such as `user@gateway-host`. The Gateway port defaults
  to `18789`.

If the remote Gateway requires authentication, expand **Gateway authentication**
and enter its token or password. Use one credential type, matching the remote
Gateway's configuration. Remote setup does not install or start a local Gateway
service; the remote host owns its model, provider credentials, and agent state.

Use HTTPS or `wss://` for public direct connections. Plain HTTP or `ws://`
should be limited to loopback, trusted private networks, and Tailnet hosts.
When the saved configuration includes `gateway.remote.tlsFingerprint`, select
**SSH tunnel** instead of a direct connection. The embedded browser cannot
enforce a certificate pin, so the app rejects direct connections before loading
the remote dashboard or exposing its credentials. Saved remote token and
password values can use environment- or file-backed SecretRefs; exec and
shared-store references must be resolved on their owning Gateway host.
SSH uses your existing OpenSSH authentication and host-key verification. See
[Remote access](/gateway/remote) for secure Gateway configuration.

After a manual connection succeeds, Model Setup discovers AI access available to the
selected Gateway and shows it as a choice. Discovery does not import or copy an
account. On a fresh visit, the companion does not select, test, install, or save
a provider until you choose its action. Provider sign-in or API-key entry is
offered when needed, and a successful model response is required before opening
the agent. An already configured Gateway opens its normal dashboard after
verification; newly configured access continues into guided onboarding.

If the Gateway confirms that a live model test failed before saving the model
and credentials, close the error and retry or choose another connection.
An uncertain error keeps replacement setup blocked because settings may already
have been saved. Confirmed cancellation and requests rejected before setup
started can be retried immediately.

Model Setup can resume an activation across a Gateway restart or app reopen
while its temporary recovery record is valid. Recovery stays bound to the same
Gateway, agent, and authentication. When the known activation target still
matches the selected model, OpenClaw verifies that exact model before continuing
guided onboarding rather than activating the provider again. For an unresolved
result, use **Verify & use selected model** to explicitly verify and adopt a
displayed model, or wait for the setup attempt's bounded window to end before
choosing **Check again**.
Recovery is not guaranteed after that record expires, browser storage becomes
unavailable or is cleared, or the Gateway, agent, or authentication changes.

Ollama automatic discovery uses eligible models already loaded in memory, not
all models installed on disk. To use an idle installed model, choose **Choose
connection** on its Ollama card, then **Local only**. See [Ollama](/providers/ollama).

For OpenAI, choose **ChatGPT Login** to use a ChatGPT or Codex subscription, or
**OpenAI API Key** for API billing. Browser sign-in completes on the Gateway
host. If that host is remote or its localhost callback cannot be reached,
choose **ChatGPT Device Pairing** from the additional sign-in options instead;
device pairing works without a localhost callback. See
[OpenAI](/providers/openai) and [OAuth](/concepts/oauth).

When the desktop app starts with a supported provider API key in its environment,
the Gateway service keeps that dedicated inference credential in an owner-only
environment file. Provider admin keys, GitHub tokens, and unrelated environment
variables are not copied into the service.

### Host sleep

Choose **Keep computer awake** in the native tray menu to prevent idle sleep
while the desktop companion is running, including when its windows are closed.
The setting is off by default and remembers your choice across app restarts.
Turning it off or quitting OpenClaw releases the keep-awake request. It does not
change your permanent power settings or unlock the computer. If the operating
system cannot honor a saved request, the menu marks the checked preference
**inactive** and reports the error. You can still uncheck it to turn the saved
preference off.

Linux uses GNOME’s session manager or an xdg-desktop-portal backend that supports
idle inhibition. Depending on the desktop, this can also prevent display dimming and automatic
locking; manual locking remains available. The optional macOS and Windows Tauri
builds prevent system idle sleep without requesting that the display stay on.

On systems with systemd-logind, the companion prepares a suspension lease for
its local Gateway before the host sleeps. After wake, it reconnects and resumes
the Gateway; remote Gateway routes are left untouched. If logind or the system
bus is unavailable, the sleep hook disables itself and the app continues
normally.

Stable releases built from `main` or their matching `release/YYYY.M.PATCH` branch
ship `.deb` and AppImage bundles as assets on the
[GitHub release](https://github.com/openclaw/openclaw/releases) for the tag,
named `OpenClaw-<version>-amd64.deb` and `OpenClaw-<version>-amd64.AppImage`,
with a `SHA256SUMS.linux-app.txt` checksum file next to them. Download the
`.deb` and install it with `sudo apt install ./OpenClaw-<version>-amd64.deb`,
or mark the AppImage executable and run it directly. The AppImage runtime
needs FUSE 2 (`sudo apt install libfuse2`, or `libfuse2t64` on Ubuntu 24.04+);
without it, run the AppImage with `APPIMAGE_EXTRACT_AND_RUN=1`.

Regular stable publication requests Linux bundles automatically after the
Gateway release becomes visible. Linux build, signing, and publication finish
independently. While those bundles are pending, the app updater continues to
offer the previous published Linux version through its original signed download.

Download only a release that contains the named Linux bundles and checksum
file; a new Gateway release alone does not prove a new Linux app is available.
The shipped updater still uses `releases/latest/download/latest.json`.
Independent `linux-stable` publication tooling is not a client endpoint or
download-link migration. That activation requires separate release approval and
signed installed-client proof; see [Linux companion publication](https://github.com/openclaw/openclaw/blob/main/.agents/skills/release-openclaw-maintainer/references/platform-publication.md#linux).

### Media codecs

The companion uses GStreamer plugins for audio and video playback.
WebM/VP9, Opus, Vorbis, and WAV normally work through `plugins-good`.
H.264/MP4, AAC, and MP3 require the `libav` and/or `plugins-bad` packages.
The `.deb` uses the host's plugins and declares all three packages as
dependencies. The AppImage bundles the GStreamer media framework and the
plugins required for those formats. For a source build or when rebuilding
either Linux bundle, install the packages and inspection tool explicitly:

```bash
sudo apt update && sudo apt install gstreamer1.0-libav gstreamer1.0-plugins-good \
  gstreamer1.0-plugins-bad gstreamer1.0-tools patchelf xdg-utils unzip
```

The packaging script stages only that media capability set before Tauri invokes
linuxdeploy. This prevents optional host plugins from adding unrelated system
libraries to the AppImage dependency closure.

The packaging flow provisions Tauri's five AppImage tools into a clean,
digest-pinned cache. After Tauri builds the AppImage, the finalizer re-verifies
that cache, removes bundled Wayland client libraries from the retained AppDir,
and rebuilds the artifact. WebKitGTK and Mesa then use one compatible host
stack.

You can also build the same bundles from a source checkout:

```bash
plugins=$(mktemp -d)
cache=$(mktemp -d)
trap 'rm -rf "$plugins" "$cache"' EXIT
export XDG_CACHE_HOME="$cache"
apps/linux/scripts/stage-appimage-gstreamer.sh "$plugins"
apps/linux/scripts/tauri-appimage-tools.sh prepare
apps/linux/scripts/tauri-appimage-tools.sh verify pre-build
export LDAI_RUNTIME_FILE="$(apps/linux/scripts/tauri-appimage-tools.sh runtime-path)"
(
  cd apps/linux/src-tauri
  GSTREAMER_PLUGINS_DIR="$plugins" \
    pnpm dlx @tauri-apps/cli@2.11.4 build --bundles deb,appimage \
      --config '{"bundle":{"createUpdaterArtifacts":false,"useLocalToolsDir":false}}'
)
apps/linux/scripts/finalize-appimage.sh \
  apps/linux/src-tauri/target/release/bundle/appimage
```

The `Linux App` workflow checks affected pull requests with Rust tests, native
builds, and the native inline-browser smoke; it does not build bundles for pull
requests. Manual runs build and upload the `.deb` and AppImage as the
`openclaw-linux-companion` workflow artifact; they do not publish a release.
See `apps/linux/README.md` in the repository for Linux build dependencies and
development commands.

### Quick Chat

`Ctrl+Shift+O` opens a new session only in the focused dashboard. The companion
does not reserve this chord globally, so other foreground apps keep their own
shortcut behavior.

Open Quick Chat with `Ctrl+Shift+Space` or the **Quick Chat** tray item. The agent
chip shows the configured avatar, emoji, or monogram; select it to switch agents.
Messages use the selected agent's main session and honor global session scope.
The native Rust client owns a persistent Ed25519 device identity. It uses the
CLI handoff's shared token or password only to bootstrap pairing, then stores and
prefers the Gateway-issued device token on later connections. The identity and
device token live in the app config directory in a mode `0600` file; Quick
Chat's WebView receives neither credentials nor the WebSocket.

When the native connection is unavailable, Quick Chat shows **Gateway
unreachable — retrying** and disables send until reconnection. A remote device
that has reached the pairing phase shows **Approve this device in the dashboard
(Nodes)** instead, with a short device ID when the Gateway provides one. A
Gateway that requires a missing shared credential shows **Gateway requires a
credential — open the dashboard on the gateway host**; no pairing request is
waiting for approval in that state. Server-provided remediation guidance
replaces these fallback notices when it is more specific.
For TLS Gateways, the CLI hands the app the Gateway certificate's SHA-256
fingerprint; the native client pins that certificate and reports **Gateway TLS
trust failed — check the certificate fingerprint** separately from downtime.
Gateways whose shared secret is configured through a SecretRef omit it from the
CLI handoff. Existing paired installs keep working through their stored device
token, but a fresh install cannot create a pending pairing request under shared-secret
authentication without that bootstrap credential.
Setup-code and `bootstrapToken` redemption need dedicated product UI and remain
a follow-up; Quick Chat does not attempt either flow.

On X11, use the gear in Quick Chat to record or reset a custom shortcut. The
**Quick Chat shortcut** tray toggle enables or disables it without disabling the
plain **Quick Chat** tray item. Global shortcuts are not available on Wayland, so
the shortcut settings are hidden and the tray item remains the entry point.
After an accepted send, Quick Chat stays open and streams the selected agent's
plain-text reply above one bottom composer, with your submitted message alongside
the reply. Collapse the reply to keep a compact composer; expanding it restores
the live text and any widget contents. You can prepare the next draft while a
reply streams, then send it when the turn finishes. Return sends, Shift-Return
adds a newline, and `Ctrl+Enter` sends and opens the dashboard. **Open dashboard**
is also available beside the composer controls. Press `Esc` to dismiss the bar
and its reply.

## CLI and SSH alternative

The CLI remains the simplest option for a headless server or VPS. Use a manual
SSH tunnel when connecting without the Linux desktop companion:

1. Install Node 26 (recommended), or another supported release: Node 24.16+ or Node 26.1+.
2. On npm 12 or npm 11.16+, run `npm i -g openclaw@latest --allow-scripts=openclaw`. On npm 11.15 and earlier, omit `--allow-scripts=openclaw`.
3. `openclaw onboard --install-daemon`
4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
5. Open `http://127.0.0.1:18789/` and authenticate with the configured shared
   secret (token by default; password if `gateway.auth.mode` is `"password"`).

Full server guide: [Linux Server](/vps). Step-by-step VPS example:
[exe.dev](/install/exe-dev).

## Node capabilities

The bundled Linux Node plugin gives the CLI `openclaw node` service device capabilities without requiring the desktop app. Commands are advertised to the Gateway only when their capability is enabled and the required local tool exists.

| Capability                              | Default | Requirement                                                           |
| --------------------------------------- | ------- | --------------------------------------------------------------------- |
| Desktop notifications (`system.notify`) | On      | `notify-send` from libnotify and a desktop notification session       |
| Camera photos and clips (`camera.*`)    | Off     | FFmpeg, V4L2 camera access, and PulseAudio or PipeWire for clip audio |
| Location (`location.get`)               | Off     | GeoClue2 and its `where-am-i` demo                                    |

Configure the plugin in `openclaw.json`:

```json5
{
  plugins: {
    entries: {
      "linux-node": {
        config: {
          notify: { enabled: true },
          camera: { enabled: true },
          location: { enabled: true },
        },
      },
    },
  },
}
```

Restart the node service after changing these settings. Availability is determined once per process and the node advertisement is rebuilt on restart.

The Gateway approves the node's command and capability surface separately from device pairing. On first start, or after enabling more capabilities, approve the pending surface:

```bash
openclaw nodes pending
openclaw nodes approve <requestId>
```

A node can be connected and device-paired while its effective `caps` and `commands` remain empty until this approval completes.

Camera devices must be readable by the service user, commonly through the `video` group. Camera clips use the default PulseAudio or PipeWire source when `includeAudio` is true; microphone audio exists only as that clip track, not as a standalone command. Location requires the node-service user to be permitted by the host's GeoClue policy.

`camera.snap` and `camera.clip` also require explicit Gateway arming through `gateway.nodes.commands.allow`. See [Camera capture](/nodes/camera) and [Location command](/nodes/location-command) for payloads, limits, and errors.

## Retired Linux Canvas

The bundled Linux Canvas bridge and its desktop Canvas window have been removed.
For inline widgets in the Control UI, use [`show_widget`](/tools/show-widget).
The separate [macOS widget panel](/platforms/mac/canvas) requires a connected
Mac and is render-only. These widget surfaces do not restore the former Linux
Canvas bridge or its A2UI push commands.

## Install

- [Getting Started](/start/getting-started)
- [Install & updates](/install/updating)
- Optional: [Bun package workflow](/install/bun), [Nix](/install/nix), [Docker](/install/docker)

## Gateway service (systemd)

On Linux hosts without a supported service manager, run the Gateway in the
foreground or through your own supervisor, such as rc.d. `openclaw gateway status
--deep` reports **no supported service manager detected** and identifies a
remaining service unit as stale. That recorded unit does not select the status
check's configuration or port. Updates continue with a service warning; restart
your manually launched Gateway after the update. An unavailable user session bus
on a systemd host remains a separate service-access diagnostic.

Install with one of:

```bash
openclaw onboard --install-daemon
openclaw gateway install
openclaw configure   # select "Gateway service" when prompted
```

Repair or migrate an existing install:

```bash
openclaw doctor
```

`openclaw gateway install` renders a systemd **user** unit by default. Full
service guidance, including the **system**-level unit variant for shared or
always-on hosts, lives in the [Gateway runbook](/gateway#supervision-and-service-lifecycle).

Managed units escape literal paths automatically. In a custom unit, do not add
shell quotes around `WorkingDirectory=` or `EnvironmentFile=` paths, even when
they contain spaces. Use a separate `EnvironmentFile=` directive for each absolute
path; systemd ignores relative paths. Write `%%` for a literal percent sign.
`EnvironmentFile=` also accepts glob patterns, so escape literal glob characters
with a backslash. Managed working-directory paths must not end in spaces or
tabs: systemd 255 loses that trailing whitespace when starting the process.
OpenClaw rejects those paths rather than risk using a different directory;
choose a path without trailing whitespace.

Write a unit by hand only for a custom setup. Minimal user-unit example
(`~/.config/systemd/user/openclaw-gateway[-<profile>].service`):

```ini
[Unit]
Description=OpenClaw Gateway (profile: <profile>)
After=network-online.target
Wants=network-online.target
StartLimitBurst=10
StartLimitIntervalSec=300

[Service]
ExecStart=/usr/local/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5
RestartPreventExitStatus=78
TimeoutStopSec=330
TimeoutStartSec=30
SuccessExitStatus=0 143
OOMPolicy=continue
KillMode=mixed

[Install]
WantedBy=default.target
```

Hand-written units do not inherit the adaptive heap sizing that `openclaw gateway install` writes for managed Gateway services. Prefer the managed installer, or set an explicit heap limit in the custom supervisor after accounting for native-memory headroom.

`TimeoutStopSec=330` covers the Gateway's five-minute cooperative drain plus teardown reserve. To inspect the current managed unit body, run `systemctl --user cat openclaw-gateway.service` (or `systemctl --user cat openclaw-gateway-<profile>.service` for a named profile).

Enable it:

```bash
systemctl --user enable --now openclaw-gateway[-<profile>].service
```

## Memory pressure and OOM kills

On Linux, the kernel picks an OOM victim when a host, VM, or container cgroup
runs out of memory. The Gateway is a poor victim because it owns long-lived
sessions and channel connections, so OpenClaw biases transient child
processes to be killed first when possible.

For eligible Linux child spawns, OpenClaw wraps the command in a short
`/bin/sh` shim that attempts to raise the child's own `oom_score_adj` to
`1000`, then `exec`s the real command. This is unprivileged: a process may
always raise its own OOM score.

The small spawn broker and service-child anchor avoid this extra shell exec:
they temporarily raise their own score around the native spawn, then restore it.
The child inherits `1000` before it can execute or fork descendants. If the
helper cannot adjust its score, it uses the shim. Direct launches and PTYs
keep the shim so the Gateway's own score never needs to change.

Covered child process surfaces:

- Supervisor-managed command children
- PTY shell children
- MCP stdio server children
- Managed local model and embedding service children
- OpenClaw-launched browser/Chrome processes (via the plugin SDK process runtime)

Sandbox backend transports keep their prepared environment and inherited OOM
score instead of receiving this wrapper. Workload resource policy belongs to
the sandbox backend; ordinary host commands and PTYs retain the child-first bias.

The wrapper is Linux-only and skipped when `/bin/sh` is unavailable, or when
the child env sets `OPENCLAW_CHILD_OOM_SCORE_ADJ` to `0`, `false`, `no`, or
`off`.
Use this opt-out only for controlled diagnosis: it removes child-first OOM
protection and makes the Gateway more likely to be selected as the victim under
real memory pressure.

Managed local model and embedding services fall back to direct spawn when their
effective environment defines `SHELLOPTS`, `BASHOPTS`, a `BASH_FUNC_*` key, or
a reserved `OC_INTERNAL_OOM_EXEC_{BASH_ENV,ENV,CDPATH,PS4}` carrier. Exact
environment fidelity and shell startup safety take precedence in these cases,
so OpenClaw does not attempt to change `oom_score_adj`; use the verification
below to check the child's effective value.

Verify a child process:

```bash
cat /proc/<child-pid>/oom_score_adj
```

When the write succeeds, the expected value for covered children is `1000`.
If `/proc` is unavailable or unwritable, the child still runs without the OOM
bias. The Gateway process itself keeps its normal score (usually `0`).

The systemd unit's `OOMPolicy=continue` keeps the Gateway service alive when
a transient child is selected by the OOM killer instead of marking the whole
unit failed and restarting all channels; the failed child/session reports its
own error.

This does not replace normal memory tuning. If a VPS or container repeatedly
kills children, raise the memory limit, reduce concurrency, or add stronger
resource controls (systemd `MemoryMax=`, container memory limits).

## Related

- [Install overview](/install)
- [Linux server](/vps)
- [ChromeOS (Crostini)](/platforms/chromeos)
- [Raspberry Pi](/install/raspberry-pi)
- [Gateway runbook](/gateway)
- [Gateway configuration](/gateway/configuration)
