Codex CLI Remote Connections: Running Agents on Remote Hosts with SSH, WebSocket, and Secure Tunnels

Codex CLI Remote Connections: Running Agents on Remote Hosts with SSH, WebSocket, and Secure Tunnels
Your code lives on a beefy cloud devbox. Your credentials sit in a vault accessible only from a private subnet. Your CI runners spin up ephemeral containers with no GUI. Meanwhile, you’re on a laptop in a coffee shop. Codex CLI’s remote connections feature — alpha since v0.121.0 and improved in v0.122.01 — bridges this gap by splitting the agent into two halves: an app-server executing tool calls on the remote host, and a TUI rendering the conversation locally. This article covers every moving part: SSH bootstrapping, WebSocket transport, authentication modes, secure tunnelling patterns, and the operational pitfalls that will bite you in production.
Architecture: The Split-Process Model
Remote Codex works by decoupling the agent runtime from the terminal interface. The app-server process handles model inference, sandbox execution, file I/O, and MCP tool calls on the machine where the code lives. The TUI process handles rendering, keyboard input, and approval prompts on the machine where the developer sits2.
sequenceDiagram
participant Dev as Developer Laptop (TUI)
participant SSH as SSH Tunnel / Tailscale
participant Server as Remote Host (app-server)
participant API as OpenAI API
Dev->>SSH: WebSocket JSON-RPC
SSH->>Server: ws://127.0.0.1:4500
Server->>API: Model requests
API-->>Server: Completions + tool calls
Server->>Server: Execute tools in sandbox
Server-->>SSH: Notifications (turn/*, item/*)
SSH-->>Dev: Stream to TUI
Dev->>SSH: Approval decisions
SSH->>Server: Accept / Decline
Communication uses bidirectional JSON-RPC 2.0 over either stdio (for local use) or WebSocket frames (for remote use)3. Every interaction follows the Thread → Turn → Item hierarchy: a Thread is the conversation, a Turn is one user request plus agent work, and Items are the individual messages, commands, and file changes within a turn3.
Enabling Remote Connections
Remote connections are gated behind a feature flag. Add the following to ~/.codex/config.toml on both machines4:
[features]
remote_control = true
If you’re using the Codex desktop app rather than the CLI, the same flag enables the Settings → Connections panel. Alternatively, switch to the beta appcast for early access5.
Method 1: SSH-Bootstrapped Connections (Codex App)
The simplest path — suitable when you already have SSH access to a devbox and use the Codex desktop app.
Prerequisites
- A working SSH config entry:
Host devbox
HostName devbox.example.com
User you
IdentityFile ~/.ssh/id_ed25519
Codex reads concrete host aliases from ~/.ssh/config and resolves them with OpenSSH. Pattern-only hosts (e.g. Host *) are ignored4.
- Codex CLI installed and authenticated on the remote host. The app bootstraps the remote server through your login shell, so
codexmust be on the remote PATH4.
Authentication on Headless Remotes
Standard codex login spawns a local HTTP server on port 1455, waiting for an OAuth callback — impossible on a headless machine without port forwarding. Use Device Code flow instead6:
ssh devbox
codex login --device-auth
This generates a one-time code valid for 15 minutes and an authorisation URL. Complete sign-in from any browser — the CLI and browser communicate through OpenAI’s servers, requiring no direct network path between them6.
⚠️ Important: You must enable Device Code Authorisation in your ChatGPT Security Settings before this will work.
Connecting
In the Codex app: Settings → Connections → Add SSH Host → Select remote project folder. The app starts the app-server over SSH and connects automatically4.
Method 2: Manual App-Server + Remote TUI (CLI-to-CLI)
For terminal-only workflows, you control both halves yourself. This gives maximum flexibility — you choose the transport, authentication, and tunnelling strategy.
Step 1: Start the App-Server on the Remote Host
# On the remote machine
codex app-server --listen ws://127.0.0.1:4500
The --listen flag accepts stdio:// (default, for local piping) or ws://IP:PORT for WebSocket transport2. Binding to 127.0.0.1 is critical — never bind to 0.0.0.0 without authentication7.
Step 2: Create an SSH Tunnel
# On your laptop
ssh -N -L 4500:127.0.0.1:4500 devbox
This forwards local port 4500 to the remote listener. The -N flag suppresses a shell session since you only need the tunnel.
Step 3: Connect the TUI
# On your laptop, in a separate terminal
codex --remote ws://127.0.0.1:4500
The --remote flag is supported for codex, codex resume, and codex fork2. You can also pass --cd /path/to/project to set the remote working directory before the agent starts2.
graph LR
A[codex TUI<br/>laptop:4500] -->|SSH tunnel| B[sshd<br/>devbox]
B --> C[codex app-server<br/>ws://127.0.0.1:4500]
C --> D[sandbox<br/>project files]
C --> E[OpenAI API]
WebSocket Authentication
For SSH-tunnelled connections to localhost, no authentication is required — the tunnel itself provides the security boundary7. For direct network connections (e.g., over a VPN or Tailscale), Codex supports two authentication modes.
Capability Token
A shared secret stored in a file on the server, transmitted as a bearer token by the client:
# Generate a token
openssl rand -base64 32 > /etc/codex/ws-token
# Start the server
codex app-server \
--listen ws://0.0.0.0:4500 \
--ws-auth capability-token \
--ws-token-file /etc/codex/ws-token
On the client side, set the token in an environment variable and reference it:
export CODEX_WS_TOKEN="$(cat /path/to/ws-token)"
codex --remote ws://devbox.tailnet:4500 \
--remote-auth-token-env CODEX_WS_TOKEN
Tokens are only transmitted over wss:// URLs or ws:// connections to localhost addresses2. Attempting to send a token over plain ws:// to a non-localhost address will fail — this is a deliberate safety rail.
Signed Bearer Token (JWT)
For enterprise deployments where multiple clients connect to shared servers, use HMAC-signed JWTs:
codex app-server \
--listen wss://0.0.0.0:4500 \
--ws-auth signed-bearer-token \
--ws-shared-secret-file /etc/codex/hmac-secret \
--ws-issuer "codex-infra" \
--ws-audience "codex-devbox-pool" \
--ws-max-clock-skew-seconds 30
The server validates iss and aud claims and rejects tokens outside the skew window2. This mode integrates naturally with corporate identity providers that can mint JWTs.
Secure Tunnelling Patterns
Pattern 1: SSH Port Forwarding (Simplest)
Best for individual developers with existing SSH access. No additional infrastructure required.
# Forward local 4500 → remote 4500
ssh -N -L 4500:127.0.0.1:4500 devbox
Trade-off: One tunnel per connection. If the SSH session drops, the TUI disconnects. Consider autossh for automatic reconnection:
autossh -M 0 -N -L 4500:127.0.0.1:4500 \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
devbox
Pattern 2: Tailscale / WireGuard Mesh (Team Scale)
For teams sharing devbox pools, a mesh VPN eliminates per-connection SSH tunnels. Codex’s own documentation recommends Tailscale or similar mesh networking tools over direct internet exposure4.
# Remote: bind to Tailscale IP
codex app-server \
--listen ws://100.64.1.50:4500 \
--ws-auth capability-token \
--ws-token-file /etc/codex/ws-token
# Local: connect directly over the tailnet
codex --remote ws://devbox.tailnet:4500 \
--remote-auth-token-env CODEX_WS_TOKEN
The Tailscale connection is already encrypted with WireGuard, so ws:// (rather than wss://) is acceptable within the tailnet — though the token still provides authentication8.
Pattern 3: Reverse Proxy with TLS Termination (Enterprise)
For shared infrastructure behind a load balancer:
# nginx.conf snippet
upstream codex_servers {
server 10.0.1.10:4500;
server 10.0.1.11:4500;
}
server {
listen 443 ssl;
server_name codex.internal.example.com;
location /ws {
proxy_pass http://codex_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
}
}
Use signed bearer tokens here — the reverse proxy terminates TLS, and the JWT validates client identity at the app-server layer.
Session Management Across Reconnections
Remote sessions survive TUI disconnections. The app-server keeps the thread alive, and you can reconnect using codex resume:
# List all sessions (including from other directories)
codex resume --all
# Jump straight to the last session
codex resume --last --remote ws://127.0.0.1:4500
# Target a specific session
codex resume thr_abc123 --remote ws://127.0.0.1:4500
Resumed sessions preserve the original transcript, plan history, and prior approval decisions9. If you’re running multiple agents on the same remote host, each app-server instance needs its own port.
In v0.122.0, resumed and forked app-server threads replay token usage immediately, so the context and status UI starts with the correct state rather than showing zeroes until the next turn1.
The exec-server: Headless Remote Execution
For CI/CD and automation pipelines, the experimental codex exec-server subcommand combines headless execution with remote connectivity1. Rather than running a one-shot codex exec command, exec-server keeps a persistent process accepting tasks over the same JSON-RPC protocol:
# On the CI runner
codex exec-server --listen ws://127.0.0.1:4501
This is particularly useful for:
- Shared CI agents that handle multiple fix requests without cold-starting each time
- Orchestration layers that dispatch tasks to a pool of Codex workers
- Long-running watchers that respond to events (webhook, file change, cron) by executing agent tasks
⚠️ The exec-server subcommand is experimental as of v0.122.0 and its API may change.
Troubleshooting
Shell Initialisation Failures
The most common connection failure: the app-server starts through the remote user’s login shell, and something in .bashrc or .zshrc breaks non-interactive execution. Guard shell transitions with an environment check5:
# In ~/.bashrc — prevent exec zsh from breaking Codex
if [ -x "$(command -v zsh)" ] && [ -z "${BASH_EXECUTION_STRING:-}" ]; then
exec zsh -l
fi
WebSocket 1006 Errors
ECONNRESET or WebSocket close code 1006 typically means the remote process crashed during startup. Check:
codexis on the remote PATH (test withssh devbox 'codex --version')- The remote Codex version matches or exceeds v0.121.05
- No port conflict on the listener address
Overload Rejection
The app-server uses bounded request queues. Under heavy load, it returns JSON-RPC error code -32001 (“Server overloaded; retry later”)3. Clients should implement exponential backoff with jitter. If you’re hitting this regularly, you likely need multiple app-server instances behind a load balancer.
Clipboard Over SSH
The TUI’s Ctrl+O (copy last agent response) works over SSH but relies on OSC 52 terminal escape sequences. Ensure your terminal emulator supports OSC 52 — most modern terminals (iTerm2, WezTerm, Ghostty, Windows Terminal) do, but tmux requires set -g set-clipboard on1.
Security Checklist
Remote connections expand the attack surface. Before deploying:
- Never expose unauthenticated listeners on shared or public networks4
- Bind app-server to
127.0.0.1and use SSH tunnels, or use capability/JWT auth with VPN - Use least-privilege SSH accounts — the agent executes with whatever permissions the SSH user has
- Rotate WebSocket tokens regularly; store them outside version control
- In v0.122.0, security-sensitive flows were tightened:
logoutrevokes managed ChatGPT tokens, and project hooks plus exec policies require trusted workspaces1 - Review sandbox policies — remote app-servers respect the same
workspaceWrite,readOnly, andexternalSandboxmodes as local execution3 - For multi-tenant server pools, use signed bearer tokens with per-team
iss/audclaims
Putting It All Together: A Complete Remote Workflow
Here’s a practical end-to-end setup for a developer using a cloud devbox:
# === ONE-TIME SETUP (on the devbox) ===
# Install Codex CLI
npm install -g @openai/codex
# Authenticate via device code (no port forwarding needed)
codex login --device-auth
# Enable remote connections
cat >> ~/.codex/config.toml << 'EOF'
[features]
remote_control = true
EOF
# Generate a WebSocket token
mkdir -p /etc/codex
openssl rand -base64 32 > /etc/codex/ws-token
# === START THE SERVER ===
codex app-server \
--listen ws://127.0.0.1:4500 &
# === ON YOUR LAPTOP ===
# Option A: SSH tunnel (simplest)
ssh -N -L 4500:127.0.0.1:4500 devbox &
# Connect the TUI
codex --remote ws://127.0.0.1:4500 --cd /home/you/project
# Option B: Tailscale (team-friendly)
codex --remote ws://devbox.tailnet:4500 \
--remote-auth-token-env CODEX_WS_TOKEN \
--cd /home/you/project
The agent now executes on the devbox — reading files, running tests, applying patches — while you interact through your local terminal. Disconnect your laptop, reconnect later with codex resume --last --remote ws://127.0.0.1:4500, and pick up exactly where you left off.
Citations
-
OpenAI, “Changelog — Codex,” April 2026. https://developers.openai.com/codex/changelog ↩ ↩2 ↩3 ↩4 ↩5
-
OpenAI, “Command line options — Codex CLI,” April 2026. https://developers.openai.com/codex/cli/reference ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
OpenAI, “App Server — Codex,” April 2026. https://developers.openai.com/codex/app-server ↩ ↩2 ↩3 ↩4
-
OpenAI, “Remote connections — Codex,” April 2026. https://developers.openai.com/codex/remote-connections ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Yiğit Konur, “Using remote SSH with Codex app,” April 2026. https://yigitkonur.com/using-remote-ssh-with-codex-app ↩ ↩2 ↩3
-
Vibe Sparking AI, “Remote Setup: OpenAI Codex CLI + OpenClaw Integration via SSH,” April 2026. https://www.vibesparking.com/en/blog/tools/openclaw/2026-04-05-openclaw-codex-remote-setup/ ↩ ↩2
-
OpenAI, “Unlocking the Codex harness: how we built the App Server,” April 2026. https://openai.com/index/unlocking-the-codex-harness/ ↩ ↩2
-
Tailscale, “Tailscale SSH Documentation,” 2026. https://tailscale.com/docs/features/tailscale-ssh ↩
-
OpenAI, “Features — Codex CLI,” April 2026. https://developers.openai.com/codex/cli/features ↩