# Run Codex CLI Headless: Servers, SSH, and No-Browser Auth

Run Codex CLI on a headless server: install, device-code auth with no browser, codex exec for scripts, auth.json handling, and systemd patterns that survive reboots.

*Published 2026-06-12 · https://proxyllm.ai/blog/codex-cli-headless-server-setup*

Codex CLI runs fine on a headless server once you respect two facts: sign-in works without a browser through `codex login --device-auth`, and unattended runs must use `codex exec`, because the bare `codex` command launches a terminal UI that panics without an interactive TTY. Install with npm or Homebrew, authenticate with the device flow, point your scripts at `codex exec`, and the rest is ordinary Linux service hygiene. This guide covers exactly that, through to systemd.

## Install on the server

```bash
npm i -g @openai/codex
# or, where Homebrew runs on Linux
brew install codex

codex --version
```

Same binary as your laptop, from github.com/openai/codex. Install it for the user that will run the jobs, not root, so auth and config land in a predictable home directory.

## The TTY problem, named

If you have seen Codex panic about a missing TTY in CI logs or a cron mail, this is why: `codex` with no subcommand starts a full-screen interactive UI, and there is no terminal to draw it in. Nothing is broken on your server. The CLI simply has two modes, and unattended machines get the second one:

```bash
codex exec "summarize the failing tests in this repo and suggest the smallest fix"
```

`codex exec` reads a prompt, runs it to completion inside the sandbox, prints the result to stdout, and exits with a status code, which is everything a script wants and nothing a TUI needs. The full surface, including `codex exec resume --last` for multi-stage pipelines, is in [the codex exec guide](/blog/codex-exec-non-interactive-mode-guide).

## Sign in without a browser

OpenAI documents device-code auth for exactly this situation (developers.openai.com/codex/auth):

```bash
codex login --device-auth
# CLI prints a short code
# open chatgpt.com on any device, enter the code, approve
```

The approval happens between you and OpenAI on whatever device has a browser; the server only receives the resulting session. ChatGPT sign-in is the route OpenAI recommends, and it is what makes server-side Codex bill to your flat plan instead of metered tokens. An API key via `codex login --with-api-key` is the alternative where a personal plan does not belong, such as shared runners.

There is also an SSH port-forward trick for using the normal browser login through a tunnel, plus token-refresh details, in [codex login without a browser](/blog/codex-login-without-browser).

## Treat auth.json like a password

The session lands in `~/.codex/auth.json`, and that file is a live credential for your account. Treat it exactly like a password file, because that is what it is.

```bash
chmod 600 ~/.codex/auth.json
```

Copying it from your laptop to a server works, but you are transporting a credential; device auth on each machine is the cleaner habit. Keep it out of git, out of Docker images, and off shared accounts. One person, one account, per OpenAI's terms; a shared server login wrapped around one ChatGPT account is the pattern to avoid.

## Running under systemd

Cron works, but systemd gives you logs, retries, and timers in one place. A scheduled digest, as a oneshot service plus timer:

```ini
# /etc/systemd/system/codex-digest.service
[Unit]
Description=Codex nightly repo digest
After=network-online.target

[Service]
Type=oneshot
User=deploy
Environment=HOME=/home/deploy
WorkingDirectory=/srv/app
ExecStart=/usr/bin/env codex exec "summarize yesterday's commits and flag anything risky"
```

```ini
# /etc/systemd/system/codex-digest.timer
[Unit]
Description=Run codex-digest nightly

[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true

[Install]
WantedBy=timers.target
```

Enable with `systemctl enable --now codex-digest.timer`, read output with `journalctl -u codex-digest`. The `Environment=HOME=` line is the fix for the most common failure: the CLI resolving `~/.codex` against the wrong home and reporting you as logged out. For containerized variants of the same setup, sandbox flags included, see [Codex CLI in Docker](/blog/codex-cli-docker-guide).

## What the server now owes you

A headless Codex box is real infrastructure, and it accrues the usual obligations: keeping the machine up, noticing when the session needs a refresh before the jobs fail silently, queueing concurrent work, handling plan windows that exhaust mid-run, and keeping logs you can read later. None of it is hard; all of it is recurring. The complete walkthrough of that life, provider choice through tmux, is in [running Codex CLI on a VPS](/blog/codex-cli-on-vps).

We run the other end of that trade. [Codex Hosted](/) keeps the official, unmodified CLI signed in with your own account in a managed container, exposed as an OpenAI-compatible endpoint with queueing, request logs, and limit failover handled. Build the server when the server is the point; use the endpoint when the output is.
