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.
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
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:
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.
Sign in without a browser
OpenAI documents device-code auth for exactly this situation (developers.openai.com/codex/auth):
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.
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.
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:
# /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"
# /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.
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.
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.
Frequently asked questions
How do I log in to Codex on a server with no browser?
Run codex login --device-auth. The CLI prints a short code; open chatgpt.com on any device, enter the code, and approve. OpenAI documents this flow for headless machines at developers.openai.com/codex/auth.
Why does Codex crash with a TTY error on my server?
The bare codex command launches a full-screen terminal UI, which needs an interactive TTY. Cron jobs, CI runners, and piped shells do not have one. Use codex exec for non-interactive runs; it reads a prompt, prints the result to stdout, and exits.
Can I copy ~/.codex/auth.json to another machine?
It works, but you are moving a live credential for your account. Prefer running device auth on each machine, restrict the file to chmod 600, and never commit it or bake it into images. Treat auth.json exactly like a password file.
Does Codex work in cron and systemd?
Yes, through codex exec. Make sure HOME is set so the CLI can resolve ~/.codex for auth and config, use absolute paths, and capture stdout. A systemd oneshot service plus a timer is the cleanest pattern for scheduled runs.