SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Self-host Paperclip on a VPS

Paperclip is the control plane over your AI coding agents. Install it on a VPS, then reach the UI over SSH or a tailnet instead of the public internet.

What Paperclip is

Paperclip is not an agent. It is the control plane over the agents you already run, and it does not perform model inference itself. It wakes each agent on a heartbeat, a short scheduled execution window, then records the run, its cost and its output against the work that run was meant to advance. Around that loop it keeps the structure a group of agents needs: companies, an org chart, goals and issues, budgets, and approvals that a person signs. An agent reaches Paperclip through an adapter, and the adapter is the boundary that decides how the agent executes: a local coding CLI session such as Claude Code or Codex, a shell command, an HTTP webhook, or a plugin package. The project's own doc/PRODUCT.md puts it plainly: agents "run wherever they run and phone home".

That single fact drives every decision below. Paperclip schedules and remembers. Your adapters do the work.

What you get when you self-host Paperclip on a VPS

Everything in this guide was read from the paperclipai/paperclip repository README and its doc/ directory at release v2026.916.1, published 2026-09-21, and checked again on 2026-09-30. This project ships often, so confirm anything version-shaped against paperclipai --version on your own box before you trust it.

What lands on the server is a Node.js API server and a React user interface. It needs Node.js 24.11.0 or newer on PATH, and nothing older will do. pnpm 9.15 or newer is needed only if you build from a source checkout instead of installing the published package. An embedded PostgreSQL database is created for you on first start, so there is no database server to install and no connection string to write. The server listens on port 3100 by default.

One detail from doc/CLI.md saves an hour of confusion later. Port 3100 is the preferred port, not a guaranteed one: the server falls back to the first available port at or above 3100. So if something else on the box already holds 3100, Paperclip quietly takes 3101, and an SSH tunnel pointed at 3100 connects to nothing. paperclipai doctor reports the port it actually bound, along with managed-install status, service health, storage and database checks.

Sizing the box: your agents, not Paperclip

This is reasoning from the architecture, not a benchmark. Paperclip's own process is a Node server, a static user interface and an embedded PostgreSQL holding rows about companies, issues, runs and costs. That workload is small and mostly idle between heartbeats, because between heartbeats there is nothing to do.

The box is sized by the adapters you put on it. A local CLI adapter starts or resumes a real coding session on that machine, which means a Node or Python process, a git checkout of the repository it works on, whatever the project's test suite loads, and a language server if the tool starts one. Two Claude Code sessions and a Codex session woken by the same heartbeat window are three of those at once. That is the number to plan around.

So there are two shapes of deployment, and they want very different servers. If every adapter is an HTTP webhook pointing at agents running elsewhere, the VPS carries Paperclip and Postgres and little else, and a small instance is honest. If the coding agents live on the same VPS, budget for the concurrent sessions first and treat Paperclip's own footprint as a rounding error on top. Stagger heartbeats across the day rather than firing every agent on the hour, because concurrent peak is what runs you out of memory, not daily total. If you are already running coding sessions this way, the layout in running Claude Code on a VPS under tmux is the same box Paperclip will be driving.

Install Paperclip with the checksum step

The documented managed install downloads a script and verifies it before running it. Run all four steps. Skipping to the last one is how people end up piping an unverified script into a shell.

curl -fsSLO https://paperclip.ing/install.sh
curl -fsSLO https://paperclip.ing/install.sh.sha256
if command -v sha256sum >/dev/null 2>&1; then
  sha256sum -c install.sh.sha256
else
  shasum -a 256 -c install.sh.sha256
fi
bash install.sh

sha256sum -c prints install.sh: OK on success. Any other output means stop.

Now the part worth understanding, because it changes what that check is worth. The checksum file is served from the same origin as the script. Both come from paperclip.ing. Anyone able to replace install.sh on that origin can replace install.sh.sha256 in the same motion, and your verification passes against the attacker's own hash. What this check genuinely catches is a truncated download, a corrupted cache object or a mangled proxy response. It is not a defence against a compromised origin, and treating it as one is the mistake.

The independent check is a copy that lives somewhere else, under a tag nobody can silently move. The installer is committed to the repository at scripts/install.sh, so fetch the release-tagged copy and compare:

curl -fsSL https://raw.githubusercontent.com/paperclipai/paperclip/v2026.916.1/scripts/install.sh \
  -o install.sh.tagged
diff install.sh install.sh.tagged

No output from diff means the two files are identical and you are running the script that is in the tagged source. If they differ, read the difference before you run anything. A hosted copy that has drifted from the tagged source is not automatically an attack (the site may serve a newer build than the tag you picked), but it is a reason to read the script rather than trust it.

Read it anyway once. The installer detects your operating system and architecture, installs Node.js 24.11.0 or newer if it is missing, installs the paperclipai npm package, and can run onboarding and install a per-user service. It fetches helper scripts of its own, and it verifies each one against a hard-coded SHA-256 value, confirms the file starts with #!, and runs a Bash syntax check before executing it. On a VPS where you already manage Node yourself, install Node first so the script has nothing to decide.

If you would rather not have an installer touch PATH at all, npx paperclipai install performs the same managed install: it downloads the latest published release, verifies it, and places a paperclipai command in ~/.local/bin.

Then set up your instance and check the result:

paperclipai onboard --yes
paperclipai doctor

onboard walks through the database, the deployment mode and your first company. doctor should come back with the managed install found, the database reachable and a bound port reported. A failure here is far cheaper to fix than the same failure discovered through a browser that will not load.

Run Paperclip as a service so it survives your logout

Paperclip installs a systemd user service on Linux, and a LaunchAgent on macOS.

paperclipai service install
paperclipai service status
paperclipai service logs -f

status should show the service active and name the port. logs -f follows the live log, which is where heartbeat runs and adapter errors appear.

A systemd user service is not a system service, and on a VPS that difference bites. By default the user manager shuts down when your last session ends, so the moment you close SSH, systemd tears down your user instance and Paperclip goes with it. Nothing errors. The service was simply stopped on purpose. Enable lingering so the user manager starts at boot and stays up with nobody logged in:

sudo loginctl enable-linger $USER
loginctl show-user $USER --property=Linger

That second command must print Linger=yes. Reboot the box, log back in and run paperclipai service status before you trust the setup. A control plane that misses every heartbeat overnight is worse than no control plane, because the dashboard still looks calm. The rest of the unit-file vocabulary, including OnFailure= handlers and restart policy, works the same way it does for any systemd service and timer you run on a VPS.

The remaining service verbs are start, stop, restart and uninstall.

The bind modes, and why the default is the right one on a VPS

This is the section the post exists for. Paperclip separates two settings, and confusing them is how an unauthenticated control plane ends up on a public IP address.

Runtime mode decides whether a human logs in. local_trusted requires no login and binds loopback only. authenticated requires a login, and comes in a private flavour for private networks and a public flavour for internet-facing instances.

Bind mode decides which address the server listens on. loopback listens on localhost only and is the default. lan listens on all interfaces, meaning 0.0.0.0. tailnet listens on the detected Tailscale address. custom takes an explicit host or address.

On a VPS, keep local_trusted with loopback, which is what a fresh install gives you. Never expose local_trusted to the internet, because it has no human login flow at all, so anything that can reach the port is treated as the operator and inherits your agents, your provider keys and your approval queue.

Note what lan means on a server. A VPS has no home network behind a router. Its primary interface carries a public address, so --bind lan publishes the port to the internet, whatever the word suggests.

Reaching the interface over an SSH tunnel

With the default loopback bind, forward the port from your laptop. Nothing on the server changes and nothing new listens publicly.

ssh -N -L 3100:127.0.0.1:3100 you@your-vps

Open http://localhost:3100 while that stays running. If the page does not load, check the port Paperclip actually bound with paperclipai doctor and point the tunnel at that number instead.

Reaching it over a tailnet

For access from several of your own devices without a tunnel each time, bind to the tailnet address. Note the constraint: local_trusted is loopback-only, so a tailnet bind means moving to the authenticated runtime mode with private exposure. You get a login page, and the port is visible only to devices on your mesh network.

paperclipai configure --section server
paperclipai run --bind tailnet

Rate limiting on the login form is off by default in private mode. Turn it on with PAPERCLIP_AUTH_RATE_LIMIT_ENABLED=true. If you would rather not depend on a hosted coordination server for this, running Headscale as your own Tailscale control server gives you the same mesh with the coordination plane on your own box.

Publishing it for a team, properly

When people who cannot hold an SSH key need access, use authenticated with public exposure. Set PAPERCLIP_PUBLIC_URL to the address users will actually type, because the mode requires an explicit public URL rather than guessing one. Request rate limiting on the authentication endpoints is on by default here. The documented recommendation is still loopback for the bind, with a reverse proxy in front terminating TLS (transport layer security) and forwarding to 127.0.0.1:3100. Paperclip never listens publicly itself, and the proxy owns the certificate. Picking that proxy is its own decision, covered in the comparison of Nginx, Caddy and Traefik as a reverse proxy.

In public mode, local stdio MCP (model context protocol) runtime slots fail closed by default. That is deliberate. An internet-facing control plane should not be spawning local processes on your behalf unless you have explicitly said so with PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST.

Whichever mode you choose, do not open 3100 in the firewall. A default-deny inbound policy with SSH allowed is the whole requirement, and it is step one of what to do in the first ten minutes on a new VPS.

Provider keys and agent secrets

Adapters authenticate with ordinary provider environment variables: ANTHROPIC_API_KEY for the Claude adapter, OPENAI_API_KEY for the Codex adapter, OPENROUTER_API_KEY for the OpenCode adapter. Agent-level credentials belong in Paperclip's secrets store instead, where values are held by reference and redacted from command output and telemetry.

npx paperclipai secrets create --company-id "$COMPANY_ID" --name deploy-token --value-env DEPLOY_TOKEN
npx paperclipai secrets list
npx paperclipai secrets doctor

--value-env names an environment variable to read the value from, so the secret never appears as an argument. That matters because arguments are visible in ps output to any user on the box, and they land in your shell history file.

There is a second rule in doc/CLI.md worth copying onto a sticky note. Use npx paperclipai rather than pnpm paperclipai for any command whose argument can hold untrusted or semi-trusted content: issue text, comments, or model output. The pnpm form evaluates shell substitutions before the CLI ever receives the value, so text written by an agent can execute on your server. Paperclip's whole purpose is to carry text produced by agents, which makes this the sharpest edge in the tool.

The local encryption key lives at ~/.paperclip/instances/<instance-id>/secrets/master.key. Treat that file as the crown jewels of the install. The wider habits for this, including which keys an agent should be able to see at all, are in keeping secrets out of the reach of your AI agents. Budgets are the other half of that control surface, and setting real ones is the cheapest form of cost control for agents running on a VPS.

Back up the database, and the four things next to it

The documented layout for an instance is one directory:

~/.paperclip/instances/<instance-id>/
  config.json
  .env
  db/
  data/storage/
  logs/
  secrets/master.key

PAPERCLIP_HOME and PAPERCLIP_INSTANCE_ID override the location and the instance identifier. The embedded PostgreSQL keeps its data under db/, created on first start and persisted across restarts.

Read the limitation in doc/DATABASE.md before you design a backup. A logical database dump covers the public schema and the Drizzle migration journal. It does not include local-disk uploads, workspace files, or the local encrypted secrets master key. Restore a database-only backup onto a fresh box and you get an instance that holds every secret record and can decrypt none of them, because the key it needs was never in the dump.

So back up the instance directory as a whole, with the service stopped so Postgres is not written to mid-copy:

paperclipai service stop
tar czf ~/paperclip-backup.tgz -C ~/.paperclip instances
chmod 600 ~/paperclip-backup.tgz
paperclipai service start

Move that archive off the server. It contains master.key and .env, so anywhere you put it is now as sensitive as the VPS itself. Test the restore once, on a throwaway box, while you still have the original. From a source checkout there is also pnpm db:backup for logical dumps, with retention settings documented in doc/DEVELOPING.md.

Updates and rollback

Managed installs update with one command, and every managed update starts by taking a database snapshot on its own. It stops if the database is not reachable rather than updating blind. --no-backup exists and should stay unused on anything you care about.

paperclipai update --check
paperclipai update --dry-run
paperclipai update

--check reports what is available without changing anything. --dry-run shows what would happen. Pin a version with paperclipai update --version 2026.916.1, follow the stable channel with --latest, or take pre-release builds with --canary. The update restarts your background service if one is active for the instance.

Rollback is one command:

paperclipai update --rollback

Managed installs keep two previous payloads under ~/.paperclip/cli/installs, with ~/.paperclip/cli/current as a symlink switched atomically between them. So rollback is a safety net for the update you just ran, not a full version history. If you need to go back further than that, it is a restore from your own backup. Confirm where you landed with paperclipai --version, or with paperclipai service restart --expected-version <version> when you want the service to assert the version it came back on.

Uninstalling is paperclipai service uninstall followed by paperclipai uninstall. It deliberately leaves ~/.paperclip/instances/ in place, so removing the software does not remove your data.

Who should not run Paperclip

Paperclip earns its keep when there are several agents, real money moving through them, and a person who has to approve things. The org chart, the budgets and the approval queue are all overhead until there is something to organise.

If you run one agent for yourself, a control plane is a second service to patch, back up and keep off the internet, wrapped around a problem you do not have. Run the agent directly. The comparison between Hermes and OpenClaw covers that choice, and self-hosting a Hermes agent on a VPS is a much shorter evening. If you are still deciding what belongs on the box at all, start from the survey of self-hosted AI agents worth running and add the control plane when the agent count makes the spreadsheet painful.

FAQ

Can I put the Paperclip UI on the public internet?

Only in authenticated runtime mode with public exposure, with PAPERCLIP_PUBLIC_URL set and a reverse proxy terminating TLS in front of a loopback bind. Never expose the default local_trusted mode, because it has no login flow, so anyone who reaches the port is treated as the operator and inherits your agents, provider keys and approvals. Be careful with --bind lan on a VPS too: it listens on 0.0.0.0, and a VPS primary interface carries a public address, so lan means the internet there.

Why does Paperclip stop when I close my SSH session?

Participating in the shutdown is expected behaviour, not a crash. paperclipai service install creates a systemd user service on Linux, and by default the systemd user manager exits when your last login session ends, taking the service with it. Run sudo loginctl enable-linger $USER, then confirm with loginctl show-user $USER --property=Linger printing Linger=yes. Reboot and check paperclipai service status before relying on it.

Does Paperclip run the models, or do I still need the agent CLIs?

You still need them. Paperclip does not perform model inference. It wakes agents on heartbeats through adapters and records what happened, and each agent runs wherever its adapter runs. A local CLI adapter starts a real coding session on the same machine, so that machine needs the tool installed, its credentials, and enough memory for every session woken in the same window. An HTTP adapter pointing at an agent on another host leaves the Paperclip box carrying only the Node server and its embedded PostgreSQL.

What exactly do I need to back up to rebuild on a new VPS?

The whole instance directory, ~/.paperclip/instances/<instance-id>/, not just the database. A logical database dump covers the public schema and the Drizzle migration journal, and explicitly excludes local-disk uploads, workspace files and the secrets master key at secrets/master.key. Without that key the restored instance cannot decrypt any secret it holds. Stop the service, archive the directory, set mode 600 on the archive, and keep it off the server, because it contains both the master key and .env.

How do I verify the installer if the checksum comes from the same site?

Run the published sha256sum -c install.sh.sha256 step, and understand what it proves. Both files are served from paperclip.ing, so the check catches a truncated or corrupted download, not a compromised origin, because whoever could swap the script could swap its hash. For an independent check, download scripts/install.sh from the repository at a release tag on raw.githubusercontent.com and diff it against the copy you fetched. No output means they match. Any difference is a reason to read the script before running it.