SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Run Orca's coding agents on a VPS

Run Orca's coding agents on a VPS so they keep working when your laptop sleeps: orca serve on headless Ubuntu, under systemd and firewalled from the internet.

What Orca is, and why you would run it on a VPS

You can run Orca's coding agents on a VPS (virtual private server) so they keep working after your laptop sleeps. Orca is a desktop app that runs several CLI (command line interface) coding agents side by side, each one in its own git worktree, tracked in a single window. It works with Claude Code, Codex, OpenCode, Pi and any other agent that runs in a terminal, and it signs in with the agent subscriptions you already hold. Orca orchestrates those CLIs. It does not sell you model access.

Everything below is taken from the project's README and its headless Linux server guide as they stood at release v1.4.217, published on 29 September 2026. Package names, flags and paths move between releases, so read the guide for the version you install.

The reason to move this to a server is simple. Five agents in parallel means five worktrees compiling, testing and installing dependencies at the same time, on the machine you also use for everything else. Closing the lid stops the work. A server does not sleep, does not leave the network when you walk to lunch, and can be steered from the mobile app while the laptop is shut.

Two documented ways to put the work on a server

Orca supports two, and they solve different problems.

SSH worktrees. Your laptop keeps the Orca runtime. A worktree you point at a remote host is created on that host over SSH (secure shell), the agent runs there, and file events are synced back so the editor, the diff view and the terminals still feel local. Port forwarding and auto-reconnect are handled for you. The remote host needs git for the repository, Node, and network access for the first-time relay install. On Linux, if terminals never spawn, the docs say to add a C/C++ toolchain: make, g++ or clang++, and python3. You add a target by filling in host, user, port and an optional identity file, or by picking an entry out of ~/.ssh/config in the same dialog.

SSH worktrees fit the case where the heavy build belongs on a bigger box but you are sitting at the laptop. The runtime still lives on the laptop, which means the laptop is the machine the session depends on.

orca serve. The server owns the entire runtime. Desktop, mobile and automation clients pair with it and share one session. The documentation states the payoff plainly: agents keep running when the client laptop sleeps or disconnects, and reconnecting the client returns you to the server-owned workspace, tab and visible-pane state without duplicating paired tabs.

If the laptop closes and the work must continue, or you want to answer an agent from your phone, you want orca serve. The rest of this guide sets that up on Ubuntu.

Install Xvfb and the Electron libraries first

Orca is an Electron app, which means the headless runtime still starts Chromium, and Chromium refuses to start without an X display even when nothing is drawn on screen. Xvfb (X virtual framebuffer) is a display server that renders into memory instead of to a monitor, and orca serve starts it for you when it is needed. You install the package; you do not manage a display.

This is the first thing that fails on a fresh VPS, and the cause is package naming rather than Orca. On Ubuntu 24.04 and Debian 13 and newer, six of these libraries carry a t64 suffix, because those releases carried out the 64-bit time_t transition and renamed the packages. On Ubuntu 24.04:

sudo apt-get update
sudo apt-get install -y \
  curl file jq xvfb zlib1g-dev ca-certificates git \
  libgtk-3-0t64 libnss3 libatk1.0-0t64 libatk-bridge2.0-0t64 libgbm1 libasound2t64 \
  libxtst6 libcups2t64 libdrm2 libxkbcommon0 libpango-1.0-0 libcairo2 libatspi2.0-0t64 \
  libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxrender1 libx11-xcb1 \
  libxcb-dri3-0 libxss1

On Ubuntu 20.04, Ubuntu 22.04 and Debian 12, run the same line with the suffix removed from exactly six names: libgtk-3-0, libatk1.0-0, libatk-bridge2.0-0, libasound2, libcups2 and libatspi2.0-0. Everything else is spelled the same on both.

Get this backwards and apt-get stops on the first bad name with E: Unable to locate package libgtk-3-0t64, or the unsuffixed version of the same message on 24.04. Nothing is broken. You used the other release's vocabulary. Check which release you are on with lsb_release -ds before you paste.

Add FUSE (filesystem in userspace) too, because the AppImage mounts itself through it: libfuse2 on 20.04 and 22.04, libfuse2t64 on 24.04 and Debian 13.

Download the AppImage, and the path for servers without FUSE

sudo mkdir -p /opt/orca
sudo curl -L https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage \
  -o /opt/orca/orca-linux.AppImage
sudo chmod +x /opt/orca/orca-linux.AppImage

Many minimal VPS images ship without FUSE, and some container-based plans cannot load it at all. Then the AppImage fails at startup with dlopen(): error loading libfuse.so.2. Install libfuse2 or libfuse2t64, or skip FUSE entirely by extracting the bundle:

cd /opt/orca
sudo ./orca-linux.AppImage --appimage-extract
sudo chmod -R a+rX /opt/orca/squashfs-root
/opt/orca/squashfs-root/AppRun serve --port 6768

The chmod line is the step the guide warns about, and it is not optional. --appimage-extract creates squashfs-root as drwx------ owned by the user who ran the extraction, which is root here, so the unprivileged service user cannot even traverse the directory. The service then fails to start with a permission error on a file you can read perfectly well as root. a+rX grants read to everyone and the execute bit only on directories, which is what traversal needs.

Run it in the foreground once

Before writing any unit file, prove the runtime starts.

LIBGL_ALWAYS_SOFTWARE=1 /opt/orca/orca-linux.AppImage serve --port 6768

LIBGL_ALWAYS_SOFTWARE=1 forces software rendering, because a VPS has no GPU and Chromium otherwise spends its startup complaining about the one it cannot find. Keep this variable in every invocation, including the systemd unit.

A healthy run prints three things and then stays in the foreground: the bound endpoint, the advertised endpoint, and a pairing URL. Press Ctrl-C to stop it. Add --json if a script needs to read that output instead of a human, since it emits a versioned single-line JSON contract.

Three failures show up here. Missing X server or $DISPLAY means the xvfb package is not installed. [serve] Xvfb failed to start means it is installed somewhere the runtime did not look, so confirm with command -v Xvfb. Chromium sandbox errors mean you are running as root or /opt/orca is not readable by the user you switched to.

A systemd service under a dedicated user

Run the runtime as its own unprivileged account. That account holds agent credentials and writes into every worktree, so it should own nothing else on the box.

sudo useradd --system --create-home --shell /usr/sbin/nologin orca
sudo chown root:root /opt/orca /opt/orca/orca-linux.AppImage
sudo chmod 755 /opt/orca /opt/orca/orca-linux.AppImage
sudo loginctl enable-linger orca

loginctl enable-linger is the line people skip. Without it, systemd tears down that user's session, and anything the runtime keeps in its own user session goes with it. Lingering keeps the user manager alive whether or not anyone is logged in, which is exactly the condition a headless server is always in.

Write /etc/systemd/system/orca-serve.service:

[Unit]
Description=Orca runtime server
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
User=orca
WorkingDirectory=/home/orca
Environment=LIBGL_ALWAYS_SOFTWARE=1
ExecStart=/opt/orca/orca-linux.AppImage serve --port 6768 --pairing-address 100.64.1.20
StandardOutput=journal
StandardError=journal
KillMode=mixed
Restart=on-failure
RestartPreventExitStatus=3
RestartSec=5

[Install]
WantedBy=multi-user.target

Type=simple is right because the process stays in the foreground and never forks, which is the same foreground run you already tested. If that distinction is new, the difference between simple, exec, forking and oneshot services decides whether systemd thinks your service ever finished starting.

RestartPreventExitStatus=3 is worth understanding rather than copying. Exit status 3 means another Orca instance is already running, so restarting can only fail again. Without that line the unit hits StartLimitBurst and gives up anyway, five minutes later, with a confusing log.

sudo systemctl daemon-reload
sudo systemctl enable --now orca-serve.service
sudo systemctl status orca-serve.service
sudo journalctl -u orca-serve.service -n 50

status should read active (running), and the journal should hold the same pairing URL the foreground run printed. If it crash-loops right after an upgrade, roll back to the pre-upgrade bundle rather than upgrading again on top. For a missing library, ldd /opt/orca/squashfs-root/orca-ide names the file that is absent.

The runtime binds on all interfaces, so firewall the port

This is the part a VPS owner must not skip. The default bind address is 0.0.0.0, which means every interface the box has, including its public one. --pairing-address only changes the address advertised to clients. It does not change what the listener binds to. Confirm with sudo ss -ltnp | grep 6768.

Close the port at the firewall and reach it over a private network instead. The docs are direct about this: do not forward the Orca port to the public internet, and prefer Tailscale, WireGuard or an authenticated tunnel.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow in on tailscale0 to any port 6768 proto tcp
sudo ufw enable
sudo ufw status verbose

The fourth rule admits traffic only on the tunnel interface, so a scan of your public IP finds a closed port. If your mesh gives you a stable range instead of a named interface, sudo ufw allow from 100.64.0.0/10 to any port 6768 proto tcp does the same job for the shared address space that tailnets use. The rest of what these rules do, and the order they are evaluated in, is covered in the ufw rules a new VPS actually needs. Check your provider's panel as well. Most host a separate network firewall in front of the machine, and a port you closed in ufw can still be open there, or the reverse.

Then advertise the tunnel address to clients, which is what --pairing-address is for:

/opt/orca/orca-linux.AppImage serve --port 6768 --pairing-address 100.64.1.20

Use the address the client can actually reach. 127.0.0.1 is never right for a remote client, because the client will try to connect to itself. Add --mobile-pairing when you want to pair the phone app. If you would rather not depend on a hosted coordination service for the tunnel, running Headscale as your own control server gives you the same 100.x.y.z addressing with the coordination plane on a box you own.

The pairing URL is a credential. Orca's own wording is that it grants access to this runtime and should be treated like a password. It carries a device token and an end-to-end encryption key. Anyone who has it can drive your agents, read your repositories and open terminals on the server. Do not paste it into a chat channel, a ticket or a screenshot. Each paired client gets its own token, and tokens are revocable, so revoke one instead of rotating everything when a device is lost.

The agents live on the server now, and so do their credentials

Install every agent CLI on the server and sign in there. The runtime uses the server's PATH, the server's home directory and the server's credentials, so an agent installed on your laptop is invisible to it.

That has a consequence worth stating plainly: your subscription tokens now sit on a machine with a public IP address, in the home directory of the orca user, readable by anything running as that user. Agents run commands from your repositories, and one poisoned dependency script that reads ~/.config can walk out with the lot. Keeping credentials out of an agent's reach covers the file permissions and the scoping that limit the damage. If the work is untrusted, put each agent in a disposable VM you can destroy after the task rather than on the box that also holds your keys.

Billing follows the credentials. The agents draw on the same plan they drew on when they ran on your laptop, and parallel worktrees mean parallel usage, which is easy to forget when the box runs overnight. What Claude Code actually costs on a subscription against the API is the number to look at before you leave five agents running unattended.

Sizing the box by how many agents run at once

Size for concurrency, not for the number of worktrees you have created. Ten worktrees with one agent working is a small load. Three agents running test suites is three of everything.

Count what a single working agent brings. The worktree is a full checkout of your tree, plus whatever the build writes into it, and installed dependencies are per worktree rather than shared. The agent CLI is its own process, usually Node. Then the real cost arrives: the compiler, the test runner, the dev server the agent started to check its change. Multiply that by agents running at the same time, then add one fixed overhead for the Orca runtime itself, which is a Chromium process and an Xvfb display whether one agent runs or six.

Disk behaves better than you would expect, because git worktrees share one object store with the main repository. What grows is working trees and build output, so the honest estimate is the size of your checked-out tree plus its dependency directory, times the number of worktrees you keep.

Memory is the limit that hurts, because Linux does not slow down gracefully when it runs out. The kernel kills the largest process and logs Out of memory: Killed process to dmesg, and the victim is often the runtime, which takes every agent with it. Watch a real run with htop and df -h on your own repository for an hour, then size from that. A published figure from someone else's codebase tells you about their build, not yours.

Compared with running agents by hand in tmux

The manual version of all this is one SSH session, one terminal multiplexer, and the agent CLI. Running Claude Code in tmux on a VPS needs no Electron, no X display and none of the library list above, and it runs on the cheapest plan on the shelf. The cost is bookkeeping. You create worktrees yourself, you keep track of which pane holds which branch, you read diffs in the terminal, and steering it from a phone means an SSH client on the phone.

orca serve buys the worktree management, the diff review, the mobile app and one shared session that survives reconnects. You pay for it in a heavier install and a service that must be firewalled correctly. Choose tmux while one agent at a time is enough, and choose orca serve at the point where you are managing several in parallel and losing track. Orca is one of several tools in this shape, and the self-hosted agent tools worth running on your own hardware is the wider comparison.

FAQ

Why does apt say it cannot find libgtk-3-0t64?

You are on Ubuntu 20.04, 22.04 or Debian 12, where that package is still called libgtk-3-0. The t64 suffix exists only on Ubuntu 24.04 and Debian 13 and newer, where the 64-bit time_t transition renamed six packages. Drop the suffix from libgtk-3-0, libatk1.0-0, libatk-bridge2.0-0, libasound2, libcups2 and libatspi2.0-0, and leave every other name alone. Run lsb_release -ds first so you know which list to paste.

Do I need to open port 6768 to the internet?

No, and you should not. orca serve binds 0.0.0.0 by default, so the listener is already on your public interface until a firewall stops it. Deny it at the firewall and reach the runtime over a private tunnel such as Tailscale, Headscale or WireGuard, then pass that tunnel address to --pairing-address. The flag changes only what is advertised to clients, never what the process binds to, which you can confirm with sudo ss -ltnp | grep 6768.

Can I run orca serve as root?

Run it as a dedicated unprivileged user. Chromium's sandbox refuses to initialise under root and the runtime reports a sandbox error rather than starting. Create the account with useradd --system --create-home --shell /usr/sbin/nologin orca, keep /opt/orca owned by root and mode 755, and run sudo chmod -R a+rX /opt/orca/squashfs-root if you extracted the AppImage, because extraction creates that directory as drwx------ owned by root and the service user cannot traverse it.

Should I use SSH worktrees or orca serve?

Use SSH worktrees when your laptop stays awake and you only want the build to happen on a stronger machine: the runtime stays local and individual worktrees execute on the remote host. Use orca serve when the server should own the whole session, so agents keep running while the client laptop sleeps or disconnects and you can reattach later from the desktop or mobile app in the same workspace state.

What does the pairing URL contain?

A device credential: a token for that client plus an end to end encryption key. Treat it exactly like a password, because anyone holding it can run agents, read your code and open terminals on the server. Each client you pair receives its own token, so a lost phone is handled by revoking that one token rather than rebuilding the server.