# Codex self signed certificate in certificate chain 오류, 프로세스별 해결

> Codex의 self signed certificate in certificate chain 오류는 사내 프록시 루트 CA를 믿지 못해 납니다. 실패한 프로세스 설정에 루트 인증서 PEM을 넣으세요.

- Source: https://www.aifreeapi.com/ko/posts/codex-self-signed-certificate-in-certificate-chain
- Language: ko
- Published: 2026-10-02
- Updated: 2026-10-02
- Publisher: AI Free API (https://www.aifreeapi.com)

Codex에서 `self signed certificate in certificate chain`이 나오면, 서버가 보낸 인증서 체인의 맨 끝에 있는 루트 CA가 그 프로세스의 신뢰 저장소에 없다는 뜻입니다. 회사 노트북이나 사내망에서는 HTTPS 검사를 하는 사내 프록시나 보안 에이전트가 연결을 회사 루트 CA로 다시 서명한 경우가 대부분입니다. 직접 만든 인증서가 있어야 나는 오류가 아닙니다.

해결은 검증을 끄는 것이 아니라 **회사 루트 인증서를 PEM 파일로 받아, 오류를 낸 프로세스가 읽는 설정에 지정하는 것**입니다. Codex CLI라면 다음 두 줄이 출발점입니다.

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

이 두 줄로 끝나지 않는 경우가 있습니다. Codex 안에는 신뢰 설정을 따로 읽는 프로세스가 여럿이어서, CLI에 넣은 값이 데스크톱 앱이나 MCP 서버, 샌드박스 안의 `curl`까지 닿지 않을 수 있기 때문입니다.

## 오류가 난 위치별 설정: CLI·데스크톱 앱·MCP 서버·샌드박스

오류 문구가 어디에 찍혔는지 보면 바꿀 설정이 정해집니다. 아래 표에서 자신의 증상에 해당하는 줄부터 보면 됩니다.

| 증상이 보이는 곳 | 실패한 프로세스 | 바꿀 설정 | 근거 수준 |
| --- | --- | --- | --- |
| `codex login`, CLI 응답 요청, HTTP 방식 MCP 서버 연결 | Codex 자체 클라이언트 | `CODEX_CA_CERTIFICATE` (없으면 `SSL_CERT_FILE`) | OpenAI 공식 문서 |
| 데스크톱 앱 Remote Control, 로그에 `SELF_SIGNED_CERT_IN_CHAIN` | 앱의 Node 계층 | `NODE_USE_SYSTEM_CA=1`로 앱 실행 | 사용자 보고 1건 |
| `npx`·`node`로 띄운 MCP 서버의 로그 | 별도 Node 프로세스 | `NODE_EXTRA_CA_CERTS` 또는 `NODE_USE_SYSTEM_CA=1` | Node.js 공식 문서 |
| Codex가 실행한 명령에서 `curl: (60) SSL certificate problem` | 샌드박스 안의 명령 (macOS) | 도구에 CA 파일을 직접 지정 (`CURL_CA_BUNDLE`, `--cacert`) | 원인은 사용자 보고, 지정 방법은 curl 표준 동작 |

네 줄 모두 같은 PEM 파일을 씁니다. 그래서 순서는 파일을 먼저 확보하고, 그다음에 해당 프로세스의 설정에 넣는 것입니다.

![검증을 끄지 않고 푸는 네 단계: openssl s_client로 서명자 확인, 루트 CA를 PEM으로 확보, 실패한 프로세스의 설정에 지정, return code 0으로 결과 확인](https://www.aifreeapi.com/posts/ko/codex-self-signed-certificate-in-certificate-chain/img/fix-order-four-steps.webp)

## OpenSSL 오류 19번의 뜻: 신뢰 저장소에 없는 루트 CA

`self signed certificate in certificate chain`은 OpenSSL 검증 오류 19번입니다. 체인을 끝까지 따라갔더니 자기 자신이 서명한 루트 인증서가 나왔는데, 클라이언트가 가진 신뢰 목록에 그 루트가 없다는 의미입니다. 루트 인증서는 원래 모두 자기 서명이므로, 문제는 "self signed"가 아니라 "내 목록에 없다"는 쪽에 있습니다.

![Codex 프로세스의 HTTPS 연결을 사내 프록시가 회사 루트 CA로 다시 서명하고, 그 루트가 신뢰 저장소에 없으면 오류 19번, PEM으로 추가하면 Verify return code 0이 되는 흐름](https://www.aifreeapi.com/posts/ko/codex-self-signed-certificate-in-certificate-chain/img/proxy-resigned-chain.webp)

비슷한 문구인 `self-signed certificate`(오류 18번)는 다른 경우입니다. 이쪽은 서버 인증서 자체가 자기 서명일 때 나옵니다. 개발용 서버에 직접 만든 인증서를 붙였을 때 흔히 보는 것은 18번이고, 회사 네트워크에서 Codex를 쓰다 만나는 것은 대개 19번입니다.

사내 프록시가 원인이 아닌 경우도 있습니다. 백신의 HTTPS 검사 기능, 사설 CA로 서명된 사내 게이트웨이나 자체 호스팅 MCP 서버, 기본 주소를 바꿔 둔 base URL이 같은 문구를 만듭니다. 어느 쪽인지는 체인을 직접 보면 바로 구분됩니다.

## openssl s_client로 서명자를 확인하고 루트 CA를 PEM으로 확보

누가 연결을 다시 서명했는지는 명령 한 줄로 확인합니다. 호스트는 오류 메시지나 로그에 찍힌 것을 쓰고, 없으면 `chatgpt.com`으로 시작합니다.

```bash
openssl s_client -connect chatgpt.com:443 -showcerts </dev/null
```

출력에서 볼 곳은 두 군데입니다.

1. 마지막 인증서의 `issuer`(`i:` 줄). 회사 이름이나 보안 제품 이름이 보이면 HTTPS 검사가 중간에 있다는 뜻입니다. 공인 인증 기관 이름이 보이면 네트워크가 아니라 해당 프로세스의 신뢰 저장소 쪽을 봐야 합니다.
2. 맨 끝의 `Verify return code`. `19 (self signed certificate in certificate chain)`이면 Codex가 낸 오류와 같은 상태를 터미널에서 재현한 것입니다.

서명자를 확인했으면 그 루트 인증서를 PEM 파일로 준비합니다. 방법은 [OpenSSL s_client 문서](https://docs.openssl.org/master/man1/openssl-s_client/)의 출력 형식과 운영체제의 인증서 관리 도구를 그대로 씁니다.

- **macOS**: 키체인 접근 앱의 시스템 키체인에서 회사 루트 인증서를 찾아 `.pem`으로 내보냅니다. 터미널에서는 `security find-certificate -a -c "인증서 이름" -p /Library/Keychains/System.keychain > ~/certs/corp-root.pem`으로 같은 결과를 얻습니다.
- **Windows**: `certmgr.msc`의 "신뢰할 수 있는 루트 인증 기관"에서 해당 인증서를 "Base-64로 인코딩된 X.509(.CER)" 형식으로 내보냅니다. 이 형식이 PEM입니다.
- **목록에 없을 때**: 사내 IT 담당에게 "HTTPS 검사용 루트 CA 인증서를 PEM으로 받고 싶다"고 요청합니다.

파일이 맞는지는 Codex를 건드리기 전에 확인할 수 있습니다.

```bash
openssl s_client -connect chatgpt.com:443 -CAfile ~/certs/corp-root.pem </dev/null
```

마지막 줄이 `Verify return code: 0 (ok)`로 바뀌면 올바른 루트 인증서입니다. 여전히 19가 나오면 중간 인증서를 받았거나 다른 CA의 파일입니다.

## Codex CLI와 로그인: CODEX_CA_CERTIFICATE에 PEM 경로 지정

Codex 자체 클라이언트가 낸 오류는 `CODEX_CA_CERTIFICATE` 하나로 해결됩니다. [Codex 인증 문서](https://learn.chatgpt.com/docs/auth)는 사내 TLS 프록시나 사설 루트 CA를 쓰는 네트워크에서 로그인하기 전에 이 변수에 PEM 번들 경로를 넣으라고 안내합니다.

```bash
export CODEX_CA_CERTIFICATE="$HOME/certs/corp-root.pem"
codex login
```

동작 규칙은 다음과 같습니다.

- 이 변수가 없으면 Codex는 `SSL_CERT_FILE`을 읽습니다. 둘 다 없으면 시스템 루트만 사용합니다.
- 같은 설정이 로그인, 일반 HTTPS 요청, 보안 WebSocket 연결에 모두 적용됩니다. [openai/codex PR #14239](https://github.com/openai/codex/pull/14239)의 설명에 따르면 클라우드 작업과 HTTP 방식 MCP 클라이언트, 응답·실시간 WebSocket도 여기에 포함됩니다.
- 지정한 번들은 시스템 루트를 대체하지 않고 함께 로드됩니다. 회사 루트 하나만 든 파일이어도 공인 인증서로 서명된 다른 호스트 연결이 깨지지 않습니다.
- 파일을 읽지 못하면 오류 메시지에 환경 변수 이름과 경로가 나옵니다. 경로 오타는 여기서 드러납니다.

매번 입력하지 않으려면 `~/.zshrc`나 `~/.bashrc`에 `export` 줄을 넣습니다. IDE 확장은 터미널이 아니라 Dock이나 시작 메뉴에서 IDE를 띄우면 셸 프로파일의 변수를 물려받지 못할 수 있으므로, 변수를 설정한 터미널에서 IDE를 실행해 차이가 나는지 확인합니다.

성공 여부는 `codex login`이 끝까지 진행되고, 이어서 보낸 첫 요청에 응답이 오는지로 판단합니다. 변수를 넣었는데 문구가 그대로라면 Codex가 오래된 빌드일 수 있습니다. 예전 빌드는 시스템 루트만 신뢰했고, 그때는 같은 원인이 `Token exchange failed: error sending request for url (https://auth.openai.com/oauth/token)`이라는 다른 문구로 나타났습니다([issue #6849](https://github.com/openai/codex/issues/6849), CLI 0.58.0). Codex를 최신 버전으로 올린 뒤 다시 시도하고, 로그인 단계 자체를 더 좁혀야 하면 [Codex Token Exchange Failed 로그인 실패 단계 진단](/ko/posts/codex-access-token-could-not-be-refreshed)을 참고합니다.

## 데스크톱 앱 Remote Control: NODE_USE_SYSTEM_CA=1 사용자 보고

데스크톱 앱에서 Remote Control만 실패하고 로그에 `[remote-control-transport] remote_control_websocket.connect_failed_before_open`과 `SELF_SIGNED_CERT_IN_CHAIN`이 남는다면, 앱 안의 Node 계층이 키체인의 루트를 보지 못하는 상황일 수 있습니다.

2026년 10월 2일 기준으로 이 증상에 대한 공식 안내는 없고, [openai/codex issue #43489](https://github.com/openai/codex/issues/43489)에 사용자 보고 1건이 열려 있습니다. 보고 환경은 앱 26.901.51231, macOS 26.6.1(Apple Silicon)이며, 회사 루트 CA가 시스템 키체인에서 신뢰된 상태이고 `curl`, OpenSSL, 따로 설치한 Node는 모두 검증에 성공하는데 Remote Control만 실패했습니다. 보고자는 앱을 다음과 같이 실행해 해결했다고 적었습니다.

```bash
NODE_USE_SYSTEM_CA=1 /Applications/ChatGPT.app/Contents/MacOS/ChatGPT
```

`NODE_USE_SYSTEM_CA=1`은 [Node.js 문서](https://nodejs.org/api/cli.html)에 있는 표준 옵션으로, Node가 운영체제의 인증서 저장소(macOS 키체인, Windows 인증서 저장소, Linux의 OpenSSL 기본 위치)를 함께 읽게 합니다. 검증을 끄는 옵션이 아니므로 시도해도 안전합니다. 다만 메인테이너의 답변이 달리지 않은 보고이고, 앱 버전이 바뀌면 결과가 달라질 수 있습니다.

앱의 Node 계층이 `CODEX_CA_CERTIFICATE`를 읽는지, Windows와 Linux의 앱에서도 같은 방식이 통하는지는 공개 문서에 기재가 없습니다. 위 명령으로 해결되지 않으면 같은 issue에 앱 버전과 로그를 덧붙이는 것이 다음 단계입니다.

## Node로 띄운 MCP 서버: NODE_EXTRA_CA_CERTS를 env로 전달

`npx`나 `node`로 실행하는 stdio 방식 MCP 서버는 Codex와 별개의 Node 프로세스입니다. 이 프로세스는 `CODEX_CA_CERTIFICATE`를 읽지 않으므로, MCP 서버 로그에만 오류가 찍힌다면 Node의 설정을 써야 합니다.

`NODE_EXTRA_CA_CERTS`는 지정한 PEM 파일의 인증서를 Node 내장 루트 목록에 더합니다. [Codex MCP 문서](https://learn.chatgpt.com/docs/extend/mcp)의 `env` 테이블로 서버별로 넘길 수 있습니다. 아래에서 `internal-docs`와 패키지 이름은 자신의 서버 설정으로 바꿉니다.

```toml
[mcp_servers.internal-docs]
command = "npx"
args = ["-y", "your-mcp-server"]

[mcp_servers.internal-docs.env]
NODE_EXTRA_CA_CERTS = "/Users/me/certs/corp-root.pem"
```

주의할 점이 두 가지 있습니다. 이 변수는 프로세스가 시작될 때 한 번만 읽히므로 값을 바꾼 뒤에는 Codex를 다시 시작해야 합니다. 그리고 파일 형식이 잘못되어도 Node는 경고만 내고 계속 실행되기 때문에, 설정했는데도 오류가 그대로라면 파일 내용이 `-----BEGIN CERTIFICATE-----`로 시작하는 PEM인지부터 확인합니다.

루트 인증서가 이미 운영체제 저장소에 설치되어 있다면 `NODE_EXTRA_CA_CERTS` 대신 `NODE_USE_SYSTEM_CA = "1"`을 같은 `env` 테이블에 넣어도 됩니다. 서버가 오래된 Node로 실행되면 이 옵션을 지원하지 않을 수 있으며, 그때는 파일 경로를 지정하는 쪽이 확실합니다.

HTTP 방식 MCP 서버는 Codex 자체 클라이언트가 연결하므로 앞 절의 `CODEX_CA_CERTIFICATE`가 적용됩니다.

## 샌드박스 안 curl: (60) 오류: 키체인 대신 CA 파일을 지정

터미널에서는 되는 `curl`이 Codex가 실행했을 때만 `curl: (60) SSL certificate problem: self signed certificate in certificate chain`으로 실패한다면 샌드박스가 원인일 수 있습니다.

[openai/codex issue #11182](https://github.com/openai/codex/issues/11182)(codex-cli 0.98.0, macOS)의 분석은 이렇습니다. macOS의 `curl`은 `/etc/ssl/cert.pem`에 없는 루트를 키체인에서 찾는데, 샌드박스가 그 조회(`com.apple.TrustEvaluationAgent`)를 막습니다. 그래서 키체인에만 설치된 회사 루트는 샌드박스 안에서 보이지 않습니다. 보고자는 `--log-denials` 옵션으로 차단 기록을 확인했습니다. 이 issue는 열려 있고 메인테이너가 제시한 해결책은 없습니다.

키체인 조회가 필요 없도록 도구에 CA 파일을 직접 알려 주면 조회 자체가 일어나지 않습니다. 이것은 issue에서 검증된 방법이 아니라 `curl`의 표준 동작입니다.

```bash
curl --cacert "$HOME/certs/corp-root.pem" https://example.internal/health
# 또는
export CURL_CA_BUNDLE="$HOME/certs/corp-root.pem"
```

`CURL_CA_BUNDLE`로 지정한 파일은 기본 번들을 대체합니다. 공인 인증서를 쓰는 호스트도 같이 호출해야 하면 두 파일을 합친 번들을 만듭니다.

```bash
cat /etc/ssl/cert.pem "$HOME/certs/corp-root.pem" > "$HOME/certs/corp-bundle.pem"
```

여기서 한 가지가 더 걸립니다. 셸에서 `export`한 변수가 Codex가 실행한 명령까지 전달되지 않을 수 있습니다. `config.toml`의 `shell_environment_policy`가 어떤 환경 변수를 넘길지 정하기 때문입니다. [고급 설정 문서](https://learn.chatgpt.com/docs/config-file/config-advanced)에 있는 `set`으로 값을 명시하면 상속 여부와 무관하게 들어갑니다.

```toml
[shell_environment_policy]
set = { CURL_CA_BUNDLE = "/Users/me/certs/corp-bundle.pem" }
```

확인은 실패했던 `curl` 명령을 Codex에게 다시 실행하게 해서 `(60)` 오류 없이 응답이 오는지 보는 것으로 충분합니다. `git`, `pip`처럼 각자 CA 설정을 가진 다른 도구는 그 도구의 CA 파일 옵션을 같은 방식으로 지정합니다. 샌드박스가 무엇을 허용하고 막는지는 [Codex 샌드박스와 config.toml의 승인·쓰기·네트워크 경계](/ko/posts/codex-config-toml)에서 확인할 수 있습니다.

## NODE_TLS_REJECT_UNAUTHORIZED=0과 strict-ssl false를 쓰면 안 되는 이유

검증을 끄는 설정은 오류 문구를 없앨 뿐 원인을 해결하지 않고, 연결을 누구나 가로챌 수 있는 상태로 바꿉니다. `NODE_TLS_REJECT_UNAUTHORIZED=0`에 대해 [Node.js 문서](https://nodejs.org/api/cli.html)는 인증서 검증을 비활성화하며 TLS 연결이 중간자 공격에 취약해진다고 경고합니다. `npm config set strict-ssl false`, `curl -k`, `--insecure`도 같은 종류입니다.

Codex에서는 위험이 더 큽니다. 이 연결로 오가는 것은 로그인 토큰과 소스 코드, 프롬프트입니다. 문서가 비밀번호처럼 다루라고 하는 `~/.codex/auth.json`의 자격 증명도 같은 경로로 발급됩니다. 검증을 끄면 회사 프록시뿐 아니라 그 사이에 끼어든 어떤 장비의 인증서도 그대로 통과합니다.

루트 인증서를 신뢰 목록에 더하는 방법은 다릅니다. 회사가 이미 정책으로 신뢰하게 한 CA 하나만 추가하고, 나머지 검증은 그대로 유지합니다. PEM 파일을 한 번 받아 두면 이후 작업은 환경 변수 한 줄이므로, 검증을 끄는 쪽이 더 빠르지도 않습니다.

## 사내 IT 담당에게 넘겨야 하는 경우

다음 중 하나에 해당하면 직접 설정을 더 바꾸지 말고 IT 담당에게 문의합니다.

- `openssl s_client` 출력의 서명자가 회사 이름도, 알려진 보안 제품도, 공인 인증 기관도 아닌 경우. 정책에 따른 HTTPS 검사가 아닐 수 있습니다.
- 루트 인증서가 운영체제 저장소에 없고 내보낼 권한도 없는 경우.
- 올바른 PEM으로 `Verify return code: 0 (ok)`까지 확인했는데 Codex 연결이 차단되는 경우. 인증서가 아니라 프록시의 허용 목록 문제일 가능성이 큽니다.

문의할 때는 실패한 호스트 이름, `openssl s_client` 출력의 `issuer` 줄, 오류가 난 프로세스(CLI, 앱, MCP 서버, 샌드박스 명령)를 함께 전달하면 왕복이 줄어듭니다.

## 자주 헷갈리는 세 가지

### codex login --device-auth로 인증서 오류를 피할 수 있나요?

피할 수 없습니다. 디바이스 코드 로그인(`codex login --device-auth`, 베타이며 ChatGPT 보안 설정에서 먼저 켜야 함)은 브라우저 리디렉션을 없앨 뿐이고, 코드 발급과 토큰 수신은 여전히 HTTPS 요청입니다. 루트 CA를 신뢰하지 못하면 같은 지점에서 실패하므로 `CODEX_CA_CERTIFICATE`를 먼저 설정합니다.

### 키체인에 루트 CA를 설치했는데 왜 Codex에서 오류가 나나요?

키체인을 읽지 않는 프로세스가 섞여 있기 때문입니다. Node는 기본적으로 내장 루트 목록만 쓰고 `NODE_USE_SYSTEM_CA=1`을 줘야 운영체제 저장소를 읽습니다. macOS 샌드박스 안의 명령은 키체인 조회가 막혀 있다는 사용자 보고가 있습니다. 두 경우 모두 운영체제에 설치한 것과 별개로 해당 프로세스에 CA를 알려 줘야 합니다.

### 인증서 오류 대신 Reconnecting이나 시간 초과가 뜨면 같은 원인인가요?

같을 수도 있지만 문구만으로는 알 수 없습니다. 프록시가 연결을 끊거나 지연시키는 경우에도 비슷하게 보입니다. 먼저 `openssl s_client`로 `Verify return code`를 확인하고, 0이라면 인증서가 아닌 다른 원인을 찾습니다. 작업 중 재연결이 반복되면 [Codex가 Reconnecting을 반복할 때의 해결 순서](/ko/posts/codex-stuck-reconnecting-fix)를, 시작 단계에서 15초 뒤 멈추면 [Codex cloud config bundle 시간 초과 진단 순서](/ko/posts/codex-timeout)를 봅니다.
