Running Codex and pi on a VPS: a setup checklist for always-on coding agents
A coding agent on a laptop stops when the lid closes. Move it to an always-on Linux server and a long refactor or test run keeps going overnight, next to the repository and build tools, and you can check on it from any device.
The server does not have to be a VPS. A home machine or a spare mini PC works the same way. This checklist covers the parts that usually go wrong on a headless host: which account runs the agent, keeping processes alive after logout, signing in without a browser, and reaching sessions from a phone. The examples use Codex CLI and pi.
The short answer
- Create a dedicated non-root user for the agents and sign in to each agent as that user.
- Install Pontia, the tool we build, and run
pontia init. - Open app.pontia.dev on your phone.
Pontia keeps sessions running after you log out and connects the server to your phone over an end-to-end encrypted relay, with no open inbound port. OpenAI’s ChatGPT mobile remote control cannot run on a Linux server directly. It needs the ChatGPT desktop app on a Mac or Windows host that stays awake and reaches the server over SSH.
The rest of this article explains each step, and how to build the same pieces yourself if you prefer.
1. Run agents as a dedicated non-root user
An agent acts with the permissions of the account that runs it. Codex has an OS-enforced sandbox and an approval policy. pi has neither by default: its security guide states that it acts with its user’s permissions and does not ask before each tool call. Either way, the account is your outer boundary.
sudo adduser agent Add your SSH public key to the new account’s ~/.ssh/authorized_keys. Give the account only the repositories and credentials it needs. Prefer a deploy key or a fine-grained token scoped to specific repositories over your personal SSH key, so a mistaken or prompt-injected command cannot push to everything you own.
Run the later steps over SSH as this user (ssh agent@your-server), not through su or sudo -u. Those commands do not set up the session environment that systemctl --user needs.
2. Sign in to agents without a browser
The server has no browser, so use device-code flows where they exist:
codex login --device-auth Codex prints a URL and a code that you open on your phone or laptop. For pi, set your provider’s API key as an environment variable, or run /login inside pi. The pi quickstart lists the providers.
3. Keep services running after logout
On most distributions, systemd stops a user’s services when their last session ends. Enable lingering so the user manager keeps running:
sudo loginctl enable-linger agent # from an admin account
systemctl --user is-system-running # as agent, over SSH is-system-running should print running, or degraded if an unrelated unit failed. When you select Codex, pontia init enables lingering for you. For a pi-only setup, run the command above yourself.
4. Install Pontia
As the agent user:
curl -fsSL https://get.pontia.dev/install.sh | sh
pontia init Select pi, Codex, or both. The initializer:
- installs Pontia as a systemd user service;
- for Codex, installs a service that starts the Codex app-server daemon;
- for pi, installs the Pontia pi plugin;
- signs you in with Google or GitHub through a URL and one-time code, which you approve from your phone;
- registers the server with an official relay.
When it finishes, open app.pontia.dev on your phone and select the server. From there you start, follow, interrupt, and continue Codex or pi sessions, and answer Codex approval requests with Allow or Deny.
The server connects out to the relay, and the relay forwards only ciphertext between your browser and the server. You do not open ports, configure a VPN, or put a reverse proxy in front of anything. Advanced users can run their own relay.
pi sessions started from the Dashboard run in tmux, outside the Pontia service, so restarting or updating Pontia does not stop them. You can still SSH in and attach to one with tmux attach -t SESSION_NAME. The names start with pontia_.
To check and maintain the setup:
pontia status
pontia doctor # checks service, config, login, and remote registration
pontia update # installs the latest signed release and restarts the service
journalctl --user -u pontia.service --no-pager -n 200 Build it yourself instead
Without Pontia, you assemble the same pieces from standard tools.
Session persistence. An agent started directly in an SSH shell dies with the connection. Run it in tmux:
tmux new -s work
pi # or codex
# detach with Ctrl-b d, reattach later with: tmux attach -t work Network access. Keep agent servers on localhost. OpenAI’s Remote connections guide says not to expose Codex app-server transports on a shared or public network and to use a VPN or mesh network instead. A typical setup is a private network such as Tailscale on both the server and the phone, key-only SSH, and Mosh so the connection survives network changes.
Phone client. Install an SSH terminal app, add a key, and attach to tmux.
You maintain each of these yourself, and on the phone you operate a full-screen TUI on a small keyboard, with no signal when an agent is waiting for approval.
Common problems
| Symptom | Likely cause |
|---|---|
| Agent or service stops when you log out | Lingering is not enabled for the user |
systemctl --user fails with “Failed to connect to bus” | You switched users with su. Log in over SSH as that user |
| Agent works in your shell but not from a service | The service’s PATH lacks the directory that contains the agent or Node.js |
| Codex in a service uses different credentials than your shell | The service and your shell use different CODEX_HOME values |
Shift+Enter submits instead of adding a new line in pi | tmux extended keys are off. See pi’s tmux guide |