# Codex 로그인: 브라우저 없는 SSH 서버에서 쓰는 방법 3가지와 선택 기준

> 헤드리스 서버에서는 codex login --device-auth가 먼저입니다. 막히면 ssh -L로 1455 포트를 넘기거나 auth.json을 복사하고, 자동화에는 API 키를 씁니다.

- URL: https://blog.laozhang.ai/ko/posts/codex-headless-login
- Published: 2026-10-02
- Updated: 2026-10-02
- Author: LaoZhang AI Team (https://blog.laozhang.ai/ko/about)
- Category: AI 개발 도구
- Tags: OpenAI Codex, Codex CLI, codex login, SSH, 디바이스 코드 로그인

---
SSH로 접속한 서버나 컨테이너처럼 브라우저가 없는 곳에서 Codex CLI에 ChatGPT 계정으로 로그인하려면 `codex login --device-auth`부터 실행합니다. 터미널에 나온 주소를 휴대폰이나 노트북 브라우저에서 열고 일회용 코드를 입력하면 되고, 서버 쪽에는 브라우저도 열린 포트도 필요 없습니다. OpenAI 문서가 원격·헤드리스 환경에 먼저 권하는 방법도 이것입니다.

디바이스 코드 로그인이 계정이나 워크스페이스 설정에서 막혀 있으면 두 가지가 남습니다. 내 컴퓨터에서 `ssh -L`로 서버의 1455 포트를 끌어와 일반 브라우저 로그인을 그대로 쓰거나, 브라우저가 있는 머신에서 로그인한 뒤 `~/.codex/auth.json`을 서버로 복사하는 것입니다. 사람이 터미널 앞에 없는 CI나 스케줄러는 로그인 대신 API 키나 액세스 토큰을 쓰는 쪽이 맞습니다.

아래 내용은 2026년 10월 2일 기준 최신 안정판인 Codex CLI 0.160.0에 맞춘 것이고, 어디까지가 직접 실행해 본 결과인지는 다음과 같습니다.

- **실제로 실행해 확인한 것**: Ubuntu 24.04(glibc 2.39, OpenSSH 9.6p1)와 Codex CLI 0.160.0에서 축약 표기 `127.1`이 IPv4 루프백 주소로 해석되는 것, 이 표기를 대상으로 건 실제 SSH 포워딩이 동작하는 것, 그 포워딩이 Codex의 콜백 서버까지 닿는 것(상태 값 없이 보낸 요청에 `400 Bad Request`와 `State mismatch` 응답), 1455가 사용 중일 때 1457로 넘어가는 것, 디바이스 코드 안내문과 15분 만료 표시. 여기에 더해 실제 개인 ChatGPT 계정 하나로 디바이스 코드 로그인을 시도해, 허용 설정이 꺼져 있을 때 브라우저가 멈추는 지점과 그 설정의 위치를 봤습니다.
- **하지 않은 것**: 어느 방법으로도 로그인을 끝까지 마치지 않았습니다. 디바이스 코드 허용 설정을 켜지 않았고 일회용 코드도 입력하지 않았습니다. 브라우저에서 승인한 뒤의 화면은 본 적이 없고, `auth.json`을 다른 머신에 복사해 쓰는 것과 토큰 갱신·폐기 동작도 실행해 보지 않았습니다. Alpine 같은 musl 기반 시스템은 확인하지 않았고, 터널 시험은 양쪽 끝이 같은 머신 한 대였습니다.

그래서 로그인 완료 이후의 절차는 [OpenAI의 인증 문서](https://learn.chatgpt.com/docs/auth#login-on-headless-devices)와 0.160.0 소스 코드에 적힌 내용이며, 실행 결과가 아닌 대목은 그때마다 출처를 밝힙니다.

## 기본 `codex login`이 SSH 서버에서 끝나지 않는 이유

기본 로그인은 브라우저가 CLI와 같은 머신에 있다고 가정하기 때문입니다. `codex login`을 실행하면 CLI는 그 머신의 루프백 주소에 작은 콜백 서버를 띄우고 인증 URL을 출력합니다. 브라우저에서 로그인을 마치면 OpenAI 쪽이 브라우저를 그 콜백 주소로 돌려보내고, CLI가 거기서 인증 정보를 받아 저장합니다.

SSH 서버에서 출력된 URL을 내 노트북 브라우저에 붙여 넣으면 로그인 화면까지는 정상으로 진행됩니다. 문제는 마지막 단계입니다. 브라우저가 돌아가는 곳은 노트북 자신의 1455 포트인데, 콜백 서버는 원격 서버에서 기다리고 있습니다. 노트북에는 그 포트를 듣는 프로그램이 없으니 브라우저에는 연결 거부 화면이 뜨고, 서버 터미널의 `codex login`은 끝나지 않은 채 멈춰 있습니다.

CLI도 이 상황을 알고 있습니다. 브라우저 없는 Ubuntu에서 `codex login`을 실행하면 첫 줄에 "Starting local login server on" 뒤로 루프백 호스트 이름과 포트 1455가 붙은 주소가 나오고, 이어서 "If your browser did not open, navigate to this URL to authenticate:"와 `auth.openai.com`의 인증 URL이 출력됩니다. 0.160.0 소스에는 그 뒤에 붙는 문장이 하나 더 있습니다.

```text
On a remote or headless machine? Use `codex login --device-auth` instead.
```

세 가지 방법은 이 콜백 문제를 각각 다르게 풉니다. 디바이스 코드는 콜백 자체를 없애고, 포트 포워딩은 콜백이 서버까지 닿게 길을 내고, `auth.json` 복사는 로그인을 아예 다른 머신에서 끝냅니다.

## 어떤 방법을 고를까: 네 가지 조건으로 가르기

위에서부터 차례로 확인해서 처음 맞는 줄을 쓰면 됩니다.

| 내 상황 | 쓸 방법 | 먼저 갖춰야 할 것 |
| --- | --- | --- |
| 휴대폰이든 노트북이든 브라우저가 있는 기기가 하나 있고, ChatGPT 보안 설정을 바꿀 수 있음 | 디바이스 코드 로그인 `codex login --device-auth` | ChatGPT 설정에서 디바이스 코드 로그인 허용. 워크스페이스 계정은 관리자가 켜야 함 |
| 디바이스 코드는 막혀 있지만 내 컴퓨터에서 서버로 SSH 포트 포워딩이 됨 | `ssh -L`로 콜백 포트를 넘기고 일반 `codex login` | 내 컴퓨터의 브라우저, 비어 있는 1455 포트 |
| 포워딩도 안 됨. 컨테이너이거나 중간 서버가 포워딩을 막음 | 다른 머신에서 로그인 후 `auth.json` 복사 | 인증 정보가 파일로 저장돼 있을 것. 복사본은 머신 한 대에서만 사용 |
| 사람이 없는 CI, 스케줄러, 스크립트 | API 키, `CODEX_API_KEY`, 관리형 워크스페이스의 액세스 토큰 | 과금이 ChatGPT 플랜이 아닌 API 사용량으로 바뀌는 점 확인 |

![브라우저 없는 서버에서 Codex 로그인 방법을 고르는 네 가지 상황과 각각의 방법: 디바이스 코드 로그인, SSH 포트 포워딩, auth.json 복사, API 키와 액세스 토큰](https://blog.laozhang.ai/posts/ko/codex-headless-login/img/login-method-picker.webp)

0.160.0의 `codex login --help`에 있는 로그인 플래그는 `--device-auth`, `--with-api-key`, `--with-access-token` 세 개와 `status` 하위 명령이 전부입니다. 콜백 포트를 바꾸는 플래그도, 브라우저를 열지 않게 하는 `--no-browser` 플래그도 없습니다. 예전 글에 나오는 `--api-key`는 사라졌고, 실행하면 키를 파이프로 넘기라는 안내와 함께 종료 코드 1로 끝납니다.

## 디바이스 코드 로그인: `codex login --device-auth`와 15분짜리 코드

서버에서는 명령 하나만 실행하고, 나머지는 브라우저가 있는 아무 기기에서 합니다. OpenAI 문서의 순서는 세 단계입니다.

1. ChatGPT에서 디바이스 코드 로그인을 허용합니다. 개인 계정은 보안 설정에서, 워크스페이스는 관리자가 워크스페이스 권한에서 켭니다.
2. 서버 터미널에서 `codex login --device-auth`를 실행합니다. `codex`를 처음 실행했을 때 나오는 선택 화면에서 **Sign in with Device Code**를 골라도 같습니다.
3. 터미널에 나온 링크를 브라우저에서 열어 로그인하고 일회용 코드를 입력합니다.

```bash
codex login --device-auth
```

0.160.0에서 이 명령을 실행하면 터미널에 아래 안내문이 나옵니다. 코드 자리는 가렸습니다.

```text
Follow these steps to sign in with ChatGPT using device code authorization:

1. Open this link in your browser and sign in to your account
   https://auth.openai.com/codex/device

2. Enter this one-time code (expires in 15 minutes)
   XXXX-XXXXX

Continue only if you started this login in Codex. If a website or another person gave you this code, cancel.
```

실행해서 본 것은 이 안내문까지이고, 코드를 브라우저에 입력해 로그인을 마치는 단계는 OpenAI 문서의 설명입니다. 마지막 줄의 경고는 그대로 지켜야 합니다. 웹사이트나 다른 사람이 건넨 코드를 입력하면 그 사람의 CLI에 내 계정을 넘겨주는 셈이 됩니다. 소스 코드에 따르면 15분 안에 입력하지 않을 때 CLI는 `device auth timed out after 15 minutes`로 끝나며, 명령을 다시 실행해 새 코드를 받아야 합니다.

### 디바이스 코드 로그인 허용 설정은 어디에 있나

개인 계정의 스위치는 `chatgpt.com/settings/security`, 곧 계정 보안과 로그인 설정 페이지의 맨 아래에 있습니다. 앱 보안에 해당하는 묶음의 마지막 항목이고, 이름은 Codex, Excel, PowerPoint, Word에 디바이스 코드 로그인을 허용한다는 내용입니다. 설명에는 디바이스 코드가 원격·헤드리스 환경에서 Codex에 로그인하거나 Excel, PowerPoint, Word 안에서 브라우저로 ChatGPT에 로그인할 때 쓰인다는 것, 피싱에 악용될 수 있으니 코드를 누구와도 공유하지 말라는 경고가 적혀 있습니다. 이 문구는 중국어 간체 화면에서 본 것이라 한국어 화면의 정확한 표기는 다를 수 있으니, 네 제품 이름과 디바이스 코드가 함께 들어간 항목을 찾으면 됩니다. 예전 GitHub 이슈의 사용자 보고에는 "Enable Codex Device Code Authorization"이라는 영어 이름으로 나옵니다. OpenAI 직원이 [GitHub 이슈 #2798](https://github.com/openai/codex/issues/2798)에서 안내한 `chatgpt.com/#settings/Security` 링크도 같은 페이지로 이어지고, 워크스페이스는 관리자가 `chatgpt.com/admin/permissions`에서 켭니다.

로그인을 시작하기 전에 이 스위치가 켜져 있는지부터 확인하세요. 2026년 10월 2일에 시험한 계정에서는 꺼져 있었습니다. 계정 하나에서 본 결과일 뿐이고, 기본값이나 사용할 수 있는 플랜은 OpenAI 문서에 나와 있지 않습니다.

설정이 꺼져 있어도 터미널에는 코드가 정상으로 발급됩니다. 막히는 곳은 브라우저입니다. 그 계정에서는 인증 주소를 열고 계정을 고르자, 코드를 입력하는 칸이 나오기 전에 동의 페이지에서 멈췄습니다. 계속 버튼은 비활성 상태였고, ChatGPT 보안 설정에서 Codex, Excel, PowerPoint, Word의 디바이스 코드 로그인을 켠 뒤 `codex login --device-auth`를 다시 실행하라는 안내가 떠 있었습니다. 시험은 여기서 끝났습니다. 스위치를 켜지 않았고 코드도 입력하지 않았으므로, 설정을 켠 뒤의 화면은 OpenAI 문서의 설명대로 링크를 열어 로그인하고 코드를 입력하는 순서라고만 말할 수 있습니다. GitHub 이슈에는 계정 선택과 2단계 인증을 여러 번 되풀이한 뒤에야 같은 안내를 봤다는 사용자 보고도 있으니, 로그인 화면이 맴도는 것처럼 보이면 이 설정부터 확인하세요.

워크스페이스 계정은 관리자가 꺼 두면 구성원이 직접 켤 수 없습니다. 이때 사용자들이 본 문구는 "Please contact your workspace admin to enable device code authentication"입니다. 관리자에게 요청할 수 없는 상황이면 아래 두 방법으로 넘어갑니다.

이 기능은 문서에 베타로 표시돼 있고, 안정판에는 0.44.0(2025년 10월 3일)부터 들어갔습니다. 그보다 오래된 CLI를 쓰고 있다면 먼저 업데이트해야 하며, 방법은 [Codex CLI 설치와 설정](https://blog.laozhang.ai/ko/posts/codex-cli-install)에 있습니다.

## SSH 포트 포워딩: `ssh -L`로 1455 포트를 내 컴퓨터로 가져오기

내 컴퓨터의 1455 포트를 서버의 1455 포트에 이어 두면, 브라우저가 돌아가는 콜백이 터널을 타고 서버의 CLI까지 도착합니다. 일반 브라우저 로그인을 그대로 쓰는 방식이라 ChatGPT 쪽 설정을 바꿀 필요가 없습니다.

![터널이 없을 때는 브라우저 콜백이 내 컴퓨터의 1455 포트에서 연결 거부되고, SSH 포트 포워딩을 걸면 같은 콜백이 터널을 타고 원격 서버의 Codex CLI에 도착하는 과정](https://blog.laozhang.ai/posts/ko/codex-headless-login/img/callback-port-tunnel.webp)

내 컴퓨터에서 포워딩을 걸고 서버에 접속합니다.

```bash
ssh -L 1455:127.1:1455 user@remote
```

그 SSH 세션 안에서 로그인을 시작하고, 출력된 주소를 내 컴퓨터 브라우저에서 엽니다.

```bash
codex login
```

OpenAI 문서는 같은 명령의 포워딩 대상을 루프백 호스트 이름으로 적습니다. 위 명령의 `127.1`은 IPv4 루프백 주소의 표준 축약 표기로, 가리키는 곳은 같습니다. IPv4를 명시하는 데에는 이유가 있습니다. 0.158.0(2026년 9월 28일)부터 Codex의 콜백 서버는 IPv4 루프백에만 바인딩하는데, 루프백 호스트 이름을 IPv6 주소로 먼저 해석하는 머신도 있기 때문입니다. 이 표기는 Ubuntu 24.04(glibc 2.39, OpenSSH 9.6p1)에서 실행해 확인했습니다. `getent ahosts 127.1`이 IPv4 루프백 주소를 돌려주고, IPv4 루프백에만 바인딩한 서버가 이 주소로는 응답하지만 IPv6 루프백으로는 연결이 거부됩니다. `codex login`이 1455에서 기다리는 상태에서 대상을 `127.1:1455`로 적은 `ssh -L` 포워딩을 걸고 터널 너머 `/auth/callback`에 빈 요청을 보내면 `HTTP/1.1 400 Bad Request`와 본문 `State mismatch`가 돌아옵니다. 터널 없이 직접 보냈을 때와 같은 응답이며, 상태 값이 없는 콜백을 Codex의 콜백 서버가 거절한 것이므로 요청이 터널을 지나 그 서버까지 닿았다는 뜻입니다.

이 시험이 보여 주는 것은 거기까지입니다. 실제 브라우저 로그인의 콜백을 터널로 받아 로그인을 마친 것은 아니고, 터널의 양쪽 끝도 같은 머신이었습니다. 포워딩 대상 주소는 어느 경우든 서버 쪽에서 해석되지만, 서로 다른 두 머신 사이의 네트워크 경로는 거치지 않았습니다. Alpine처럼 musl을 쓰는 서버에서 `127.1`이 어떻게 해석되는지도 이 시험 범위 밖입니다. 그런 서버에서 터널이 연결되지 않으면 [OpenAI 문서의 원래 명령](https://learn.chatgpt.com/docs/auth#login-on-headless-devices)을 그대로 쓰세요.

### 포트는 1455 고정, 사용 중이면 1457

콜백 포트는 소스 코드에 고정돼 있고 플래그나 설정으로 바꿀 수 없습니다. 설정 파일의 `mcp_oauth_callback_port`는 MCP 서버의 OAuth용이라 로그인과는 관계없습니다. 0.128.0(2026년 4월 30일)부터는 1455가 사용 중일 때 자동으로 1457로 넘어갑니다. 0.160.0에서 다른 프로그램으로 1455를 먼저 점유한 뒤 `codex login`을 실행하면 시작 안내의 포트가 1457로 바뀌어 출력됩니다. 소스 코드에 따르면 둘 다 막혀 있을 때만 `Port … is already in use` 오류로 끝납니다. OpenAI 문서에는 아직 1455만 적혀 있습니다.

여기서 따라 나오는 결론이 하나 있습니다. 서버의 Codex가 1457로 넘어갔다면 1455만 연 터널로는 콜백을 받을 수 없습니다. `codex login`은 시작할 때 실제로 연 포트를 출력하므로, 그 숫자가 1455가 아니면 터널을 그 포트로 다시 걸어야 합니다. 처음부터 두 포트를 함께 여는 방법도 있습니다. 이것은 문서에 없는 제안이고 위의 포트 동작에서 끌어낸 것으로, 아래 명령 자체는 실행해 보지 않았습니다.

```bash
ssh -L 1455:127.1:1455 -L 1457:127.1:1457 user@remote
```

내 컴퓨터 쪽 포트 번호를 다른 숫자로 바꿔서 잇는 것은 통하지 않습니다. 브라우저가 돌아갈 주소에 포트 번호가 들어 있어서, 내 컴퓨터에서도 같은 번호로 받아야 합니다. 내 컴퓨터에서 Codex가 로그인 대기 중이거나 다른 프로그램이 1455를 쓰고 있으면 먼저 정리하세요.

WSL, VS Code Remote-SSH, Codespaces에서 콜백이 실패한다는 이슈에 대해 OpenAI는 방화벽이나 VPN 같은 로컬 네트워크 구성을 원인으로 꼽고 디바이스 코드 로그인을 권했습니다. VS Code Remote-SSH나 JetBrains Gateway의 자동 포트 포워딩이 이 콜백을 대신 처리해 주는지에 대한 공식 안내는 없습니다.

## `auth.json` 복사: 다른 머신에서 로그인하고 파일을 옮기기

브라우저가 있는 머신에서 로그인을 끝낸 뒤 인증 캐시 파일을 서버의 같은 위치에 두는 방법입니다. 포워딩이 막힌 서버나 컨테이너에서 쓸 수 있고, OpenAI 문서에 명령까지 실려 있습니다.

1. 브라우저가 있는 머신에서 `codex login`을 실행합니다.
2. `~/.codex/auth.json`이 생겼는지 확인합니다.
3. 그 파일을 서버의 `~/.codex/auth.json`으로 복사합니다.

SSH로 복사할 때:

```bash
ssh user@remote 'mkdir -p ~/.codex'
scp ~/.codex/auth.json user@remote:~/.codex/auth.json
```

`scp` 없이 한 줄로:

```bash
ssh user@remote 'mkdir -p ~/.codex && cat > ~/.codex/auth.json' < ~/.codex/auth.json
```

Docker 컨테이너로 복사할 때는 `MY_CONTAINER`를 컨테이너 이름이나 ID로 바꿉니다.

```bash
CONTAINER_HOME=$(docker exec MY_CONTAINER printenv HOME)
docker exec MY_CONTAINER mkdir -p "$CONTAINER_HOME/.codex"
docker cp ~/.codex/auth.json MY_CONTAINER:"$CONTAINER_HOME/.codex/auth.json"
```

복사한 뒤 서버에서 `chmod 600 ~/.codex/auth.json`으로 권한을 좁혀 두는 것은 문서에 없는 일반적인 관행이지만 해 두는 편이 좋습니다. 이 파일에는 `tokens` 아래에 `id_token`, `access_token`, `refresh_token`이 들어 있고, OpenAI 문서도 비밀번호처럼 다루라고 적습니다. 저장소에 커밋하거나 티켓, 채팅에 붙여 넣으면 안 됩니다.

### 파일이 없을 때: 인증 정보 저장 위치 확인

`~/.codex/auth.json`이 보이지 않으면 인증 정보가 파일이 아닌 곳에 저장된 것입니다. 저장 위치는 `config.toml`의 `cli_auth_credentials_store`가 정하며, 값은 `file`, `keyring`, `auto`, `ephemeral` 네 가지입니다. 0.160.0 소스의 기본값은 모든 운영체제에서 `file`이지만, 직접 바꿨거나 관리자가 강제했다면 OS 자격 증명 저장소에 들어가 있을 수 있습니다. 이 경우 로그인하는 머신에서 아래처럼 설정한 뒤 다시 로그인해야 파일이 생깁니다.

```toml
cli_auth_credentials_store = "file"
```

`CODEX_HOME`을 직접 지정했다면 파일은 `~/.codex`가 아니라 그 디렉터리 아래에 있습니다. 서버에서 `CODEX_HOME`을 쓸 때는 그 디렉터리가 미리 존재해야 합니다.

### `auth.json` 하나는 머신 한 대에만

복사본 하나를 서버 두 대에 나눠 쓰면 안 됩니다. [OpenAI의 CI/CD 인증 가이드](https://learn.chatgpt.com/docs/auth/ci-cd-auth)는 "Do not share the same file across concurrent jobs or multiple machines."라고 적습니다. 이유는 토큰 갱신에 있습니다. Codex는 `last_refresh`가 약 8일보다 오래됐거나 요청이 401을 받으면 토큰을 갱신하고 새 값을 `auth.json`에 다시 씁니다. 한 머신이 먼저 갱신하면 다른 머신에 남은 리프레시 토큰은 이미 쓰인 옛것이 됩니다.

처음 얼마 동안은 두 대가 모두 잘 돌아가는 것처럼 보일 수 있습니다. OpenAI 직원이 [이슈 #10332](https://github.com/openai/codex/issues/10332)에서 설명한 대로, 리프레시 토큰은 한 시간 안팎의 제한된 시간 동안은 여러 번 쓸 수 있고 그 뒤에 영구히 무효가 되기 때문입니다. 그 시간이 지나면 한쪽이 "refresh token was already used"가 들어간 오류와 함께 로그아웃됩니다. 서버가 두 대면 각각 따로 로그인하거나, 디바이스 코드 로그인을 서버마다 한 번씩 하세요.

### 원래 머신에서 다시 로그인하면 서버 쪽은 어떻게 되나

서버의 복사본이 무효가 될 수 있습니다. 2026년 6월 12일에 병합된 [PR #27674](https://github.com/openai/codex/pull/27674) 이후 `codex login`은 브라우저 방식이든 디바이스 코드 방식이든 시작하기 전에 그 머신에 저장돼 있던 인증 정보를 먼저 폐기하고 지웁니다. `codex logout`도 폐기 요청을 보냅니다.

여기서부터는 문서에 적힌 내용도 실행해 본 결과도 아닌, 이 동작에서 나오는 추론입니다. 노트북에서 로그인하고 `auth.json`을 서버로 복사한 뒤 노트북에서 다시 `codex login`이나 `codex logout`을 실행하면, 폐기되는 것은 서버가 들고 있는 것과 같은 인증 정보입니다. 그러면 서버는 다음 갱신 때 아래 오류를 만나게 됩니다.

```text
Your access token could not be refreshed because your refresh token was revoked. Please log out and sign in again.
```

복사한 뒤에는 원래 머신에서 로그인 상태를 건드리지 않는 것이 안전하고, 이 오류가 나면 다시 로그인해서 새 파일을 복사합니다.

## CI와 자동화: API 키, `CODEX_API_KEY`, 액세스 토큰 중 무엇을 쓰나

사람이 코드를 입력해 줄 수 없는 환경에서는 로그인을 흉내 내지 말고 API 키를 쓰는 것이 OpenAI의 권고입니다. 브라우저 없이 한 줄로 끝납니다.

```bash
printenv OPENAI_API_KEY | codex login --with-api-key
```

다만 이것은 ChatGPT 플랜을 브라우저 없이 쓰는 방법이 아닙니다. API 키로 로그인하면 사용량이 플랜에 포함된 한도에서 빠지지 않고 OpenAI Platform 계정에 표준 API 요금으로 청구됩니다. ChatGPT 워크스페이스나 클라우드 서비스에 의존하는 기능은 제한되거나 쓸 수 없고, Codex cloud는 ChatGPT 로그인이 필수입니다. 어느 쪽이 유리한지는 [Codex API 키와 ChatGPT 구독 차이](https://blog.laozhang.ai/ko/posts/codex-api-key-vs-subscription)에서 과금 경로별로 비교할 수 있습니다.

`codex exec`로 비대화형 실행만 한다면 로그인 상태를 저장하지 않고 실행할 때만 키를 넘길 수도 있습니다.

```bash
CODEX_API_KEY=여기에_API_키 codex exec --json "triage open bug reports"
```

[비대화형 모드 문서](https://learn.chatgpt.com/docs/non-interactive-mode)에 따르면 `CODEX_API_KEY`는 `codex exec`, `codex review`, TypeScript SDK, `codex exec-server --remote`에서 쓸 수 있습니다. 대화형 `codex`는 이 변수를 읽지 않습니다. 그리고 환경 변수 문서가 인증용으로 나열하는 것은 `CODEX_API_KEY`와 `CODEX_ACCESS_TOKEN`이며, 환경에 `OPENAI_API_KEY`만 설정해 두는 것은 `codex exec`의 인증 수단으로 올라 있지 않습니다. 저장소의 코드가 함께 실행되는 작업에서는 키를 작업 전체의 환경 변수로 두지 말고, 위처럼 Codex를 실행하는 줄에만 붙이라는 것이 문서의 주의 사항입니다.

관리형 워크스페이스에서 ChatGPT 쪽 권한과 한도를 그대로 쓰면서 자동화해야 한다면 Codex 액세스 토큰이 있습니다. 워크스페이스 소유자가 권한을 켠 뒤 `chatgpt.com/admin/access-tokens`에서 만들고, 만료는 7·30·60·90일 중에서 고르며 직접 정할 때 가장 짧은 기간은 1일입니다.

```bash
printenv CODEX_ACCESS_TOKEN | codex login --with-access-token
```

대상 플랜은 OpenAI 문서끼리 표현이 다릅니다. 인증 문서는 ChatGPT Enterprise 워크스페이스라고 쓰고, [액세스 토큰 문서](https://learn.chatgpt.com/docs/enterprise/access-tokens)는 ChatGPT Business와 Enterprise 워크스페이스에서 지원한다고 씁니다. Business 워크스페이스라면 관리자 화면에 해당 항목이 실제로 보이는지로 판단하세요.

ChatGPT 계정 인증을 CI에서 유지하는 방법도 문서에 고급 절차로 실려 있습니다. `auth.json`이 없을 때만 넣어 주고, 실행 중에 Codex가 갱신한 파일을 다음 작업까지 보존하는 방식입니다. 신뢰할 수 있는 비공개 러너에서만 쓰고 공개 저장소에서는 쓰지 말라는 조건이 붙어 있으며, 같은 문서가 대부분의 CI/CD 작업에는 여전히 API 키를 권합니다.

## `codex login status`로 확인하고, 오류 문구별로 조치하기

어느 방법을 썼든 마지막에는 서버에서 상태를 확인합니다.

```bash
codex login status
```

인증 정보가 있으면 `Logged in using ChatGPT`처럼 현재 방식이 표시되고 종료 코드는 0입니다. 없으면 `Not logged in`이 출력되고 종료 코드 1로 끝나므로, 스크립트에서 로그인 여부를 가르는 조건으로 쓸 수 있습니다.

중간에 막혔다면 문구로 원인을 좁힐 수 있습니다.

| 보이는 문구나 증상 | 뜻 | 조치 |
| --- | --- | --- |
| 브라우저가 콜백 주소에서 연결 거부 화면을 띄움 | 브라우저가 있는 머신에서 그 포트를 듣는 프로그램이 없음. 터널이 없거나 포트가 다름 | `ssh -L`을 CLI가 출력한 포트로 다시 걸거나 디바이스 코드 로그인으로 전환 |
| `device auth timed out after 15 minutes` | 일회용 코드 만료 | `codex login --device-auth`를 다시 실행 |
| `device code login is not enabled for this Codex server. Use the browser login or verify the server URL.` | 디바이스 코드 발급 요청이 404를 받음 | 포트 포워딩이나 `auth.json` 복사로 전환. 사내 게이트웨이를 거친다면 서버 URL 설정 확인 |
| 브라우저가 코드 입력 칸 없이 동의 페이지에서 멈추고, 보안 설정에서 디바이스 코드 로그인을 켠 뒤 `codex login --device-auth`를 다시 실행하라고 안내함 | 계정의 디바이스 코드 로그인 허용 설정이 꺼져 있음 | `chatgpt.com/settings/security` 맨 아래의 스위치를 켜고 명령을 다시 실행 |
| "Please contact your workspace admin to enable device code authentication" (사용자 보고) | 워크스페이스에서 디바이스 코드 로그인이 꺼져 있음 | 관리자에게 요청하거나 다른 방법 사용 |
| `Port … is already in use` | 1455와 1457이 모두 사용 중 | 서버에 남아 있는 이전 `codex login` 프로세스를 종료 |
| `refresh token was revoked`가 들어간 갱신 실패 | 저장된 인증 정보가 폐기됨. 다른 곳에서 다시 로그인했거나 로그아웃한 경우 | 다시 로그인하거나 새 `auth.json`을 복사 |
| `refresh token was already used`가 들어간 갱신 실패 | 같은 `auth.json`을 여러 머신이나 동시 작업이 공유 | 머신마다 따로 로그인 |
| `ChatGPT login is disabled. Use API key login instead.` | 관리자가 `forced_login_method`로 로그인 방식을 제한 | 허용된 방식으로 로그인. 반대 문구는 `API key login is disabled. Use ChatGPT login instead.` |

문구만으로 알 수 없을 때는 로그를 봅니다. `codex login`을 직접 실행하면 전용 로그가 남고, 기본 위치는 `~/.codex/log/codex-login.log`입니다.

사내 TLS 프록시나 사설 루트 인증서를 쓰는 네트워크에서는 로그인 요청이 인증서 검증에서 막힐 수 있습니다. 이때는 로그인 전에 인증서 묶음을 지정합니다. 이 변수가 없으면 Codex는 `SSL_CERT_FILE`을 대신 읽습니다.

```bash
export CODEX_CA_CERTIFICATE=/path/to/corporate-root-ca.pem
codex login
```

브라우저에서 승인까지 했는데 터미널이 403으로 끝난다면 원인이 헤드리스 환경이 아닐 가능성이 큽니다. 이 경우는 [Codex 토큰 교환 403 실패](https://blog.laozhang.ai/ko/posts/codex-token-exchange-failed-403)에서 따로 진단합니다. 로그인 상태는 정상인데 요청마다 401이 난다면 [Codex 401 Incorrect API key provided 오류 해결](https://blog.laozhang.ai/ko/posts/codex-401-incorrect-api-key)을 보세요. 로그인 도중 전화번호 인증을 요구받는 계정은 디바이스 코드 로그인으로 바꿔도 같은 요구를 받는다는 보고가 GitHub 이슈에 열려 있고, 아직 OpenAI의 공식 결론은 없습니다.

## 헤드리스 로그인에서 자주 헷갈리는 세 가지

### 같은 ChatGPT 계정으로 서버 여러 대에서 Codex를 쓸 수 있나요?

쓸 수 있지만 서버마다 따로 로그인해야 합니다. 문서가 금지하는 것은 같은 `auth.json` 파일을 여러 머신이 공유하는 것입니다. 서버마다 `codex login --device-auth`를 한 번씩 실행하면 서버별로 별도의 인증 정보가 발급되므로 갱신이 서로 엇갈리지 않습니다.

### Docker 컨테이너 안에서는 어떤 방법이 맞나요?

터미널을 붙일 수 있는 컨테이너라면 디바이스 코드 로그인이 가장 간단합니다. 포트를 열 필요가 없기 때문입니다. 디바이스 코드가 막혀 있으면 호스트에서 로그인하고 위의 `docker cp` 명령으로 `auth.json`을 넣습니다. 컨테이너를 지우면 파일도 사라지므로 `~/.codex`를 볼륨에 두어야 다시 로그인하지 않아도 되며, 그 볼륨을 동시에 실행되는 여러 컨테이너가 함께 쓰는 것은 파일 하나를 여러 머신이 공유하는 것과 같은 문제를 만듭니다.

### 헤드리스 로그인과 `codex exec` 헤드리스 실행은 같은 건가요?

다릅니다. 앞의 것은 브라우저 없는 머신에서 인증을 마치는 일이고, `codex exec`는 화면 없이 Codex에 작업을 시키는 비대화형 실행입니다. 둘은 이렇게 이어집니다. `codex exec`는 기본적으로 저장된 CLI 인증을 그대로 쓰므로, 위 세 가지 방법 중 하나로 로그인해 두면 추가 설정 없이 실행되고, 로그인 없이 돌리려면 `CODEX_API_KEY`를 넘깁니다.
