BlueByte
InvalidClientTokenIdFixed

AWS CLI: InvalidClientTokenId — 요청의 security token이 유효하지 않음

작성 Haneul Seo2026년 9월 7일 업데이트7 min

안녕하세요, BlueByte입니다. 분명 되어야 할 명령에 AWS CLI가 An error occurred (InvalidClientTokenId) ... The security token included in the request is invalid로 답한다면, 계정이 망가진 게 아닙니다 — AWS가 요청에 실린 자격 증명을 보고 거부한 것입니다. 오늘은 이 메시지가 뜻하는 것, AWS가 토큰을 거부하는 몇 가지 원인, CLI가 실제로 집어 든 자격 증명을 확인하는 법, 원인별 해결, 그리고 예방까지 하나씩 짚어보겠습니다.

"The security token included in the request is invalid"가 뜻하는 것

InvalidClientTokenId는 자격 증명 인증 실패로, AWS가 권한을 확인하기도 전에 돌려주는 오류입니다. 어떤 서비스 호출에서도 나타날 수 있어서 — AWS 문서는 aws s3 ls(ListBuckets 작업)에서의 예를 보여줍니다 — 메시지에 적힌 작업 이름이 원인은 아닙니다:

$ aws s3 ls
An error occurred (InvalidClientTokenId) when calling the ListBuckets operation:
The security token included in the request is invalid.

"security token"은 access key ID와, 임시 자격 증명이라면 함께 다니는 session token을 아우릅니다. AWS는 보낸 값을 살아 있는 자격 증명으로 매핑할 수 없다고 말하는 것입니다. 권한 확인 단계에는 도달조차 못 했으니 이건 access-denied가 아닙니다.

AWS가 토큰을 거부하는 몇 가지 원인

대개 다음 중 하나입니다.

  • 설정 이후 access key가 IAM에서 삭제·비활성화됨.
  • 만료된 임시 자격 증명 — assumed role, aws sso login, 또는 직접 붙여 넣은 session token에서 온 STS 세션은 수명이 짧아, 만료되면 토큰이 무효가 됨.
  • 오래된 환경 변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)가 셸에 남아 있고, 환경 변수가 자격 증명 파일보다 우선순위가 높기 때문에 쓰려던 프로필을 덮어씀.
  • key가 엔드포인트와 다른 파티션 소속임 — 표준 파티션 key로 GovCloud(aws-us-gov)나 중국(aws-cn) 엔드포인트를 부르면 인식되지 않음.
  • access key ID를 잘못 복사함 — 잘리거나 자리가 뒤바뀜.

먼저, CLI가 실제로 쓰는 자격 증명을 확인하기

어떤 자격 증명이 나갔는지 추측하지 말고 CLI에 물어보면 됩니다. aws configure list는 해석된 값과, 무엇보다 각 값이 어디서 왔는지를 보여줍니다:

aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************ABCD              env    AWS_ACCESS_KEY_ID
secret_key     ****************wXyZ              env    AWS_SECRET_ACCESS_KEY
    region                us-east-1              env    AWS_DEFAULT_REGION

Location 열이 이런 실패의 대부분을 설명해 줍니다. ~/.aws/credentials를 기대했는데 env라고 나오면 환경 변수가 이기고 있는 것입니다. 신원을 끝까지 확인하려면:

aws sts get-caller-identity

여기서도 같은 InvalidClientTokenId가 나오면 문제는 한 명령이 아니라 자격 증명 자체입니다. 무엇이 로드됐는지 전체 흐름을 보려면 --debug로 다시 실행해 Looking for credentials via: 줄을 읽어 보세요.

원인별 해결: 오래된 env 변수·만료된 세션·삭제된 key

  1. 오래된 환경 변수 — 지우고 프로필이 나서게 합니다:
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
aws sts get-caller-identity
  1. 만료된 임시 자격 증명 — 다시 인증합니다. IAM Identity Center / SSO는 aws sso login --profile my-sso. assumed role은 aws sts assume-role를 다시 실행해 내보낸 값을 교체합니다.

  2. 삭제·비활성화된 key — 상태를 확인하고 교체합니다:

aws iam list-access-keys --user-name my-user

key가 Inactive거나 없으면 새로 만들고 aws configure로 저장합니다.

  1. 잘못된 파티션 — 맞는 파티션을 대상으로 하고(예: --region us-gov-west-1), 그 파티션에서 발급한 key를 씁니다.

실제 사례: 남아 있던 AWS_SESSION_TOKEN

어제까지 잘 돌던 배포 스크립트가 aws s3 cp에서 InvalidClientTokenId로 실패하기 시작했습니다. aws configure list를 보니 access_key ... Location env AWS_ACCESS_KEY_ID였는데, 이 프로젝트는 named profile을 씁니다. 누군가 관련 없는 테스트를 하려고 그 셸에 임시 자격 증명을 export해 뒀고, 그중 AWS_SESSION_TOKEN이 그새 만료됐던 것입니다. env 변수가 프로필보다 우선이라 모든 명령이 죽은 토큰을 실어 나른 것이죠. unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN 뒤 aws sts get-caller-identity가 프로필의 기대 ARN을 돌려줬고, 배포가 통과했습니다.

고친 뒤 확인하고 재발을 막기

aws sts get-caller-identity로 확인합니다 — Account·UserId·Arn이 담긴 JSON이 나오면 자격 증명이 살아 있는 것입니다. 재발을 막으려면: 오래된 key가 남지 않도록 장기 key보다 단기 자격 증명(SSO나 assumed role)을 쓰고, 일회성 작업에 자격 증명을 셸에 export하지 말고 --profile로 넘기며, CI의 첫 단계로 aws sts get-caller-identity를 넣어 잘못된 자격 증명이 배포 깊숙이 아니라 작업 맨 앞에서 요란하게 실패하게 하세요.

InvalidAccessKeyId·SignatureDoesNotMatch와 어떻게 다른가

InvalidClientTokenId는 key ID나 session token이 AWS가 아는 살아 있는 자격 증명이 아니라는 뜻입니다. InvalidAccessKeyId는 비슷하지만 다릅니다 — "The AWS Access Key Id you provided does not exist in our records" — AWS에 그 key 기록이 아예 없는 경우로, 대개 오타이거나 다른 계정의 key입니다. SignatureDoesNotMatch는 또 다릅니다: key는 인식하지만 요청 서명이 검증되지 않은 경우로, 시계가 몇 분 이상 어긋났거나(date로 확인) secret key가 특수문자로 깨진 것이 흔한 원인입니다. 그리고 이 셋 중 무엇도 AccessDenied는 아닙니다 — 그건 자격 증명은 유효하나 해당 작업 권한이 없는 경우입니다.

관련 질문

한 시간 전에는 되던 key가 왜 지금은 무효인가요?

임시 자격 증명(SSO, assumed role, 붙여 넣은 session token)을 쓰고 있다면 정해진 수명 뒤에 만료됩니다. aws sso login으로 다시 인증하거나 role을 다시 assume하세요. 장기 key는 만료되지 않으므로, 그런 key가 갑자기 실패하면 IAM에서 비활성화됐는지 확인하세요.

aws configure list에 key가 'env'로 뜨는데 저는 설정한 적이 없습니다 — 어디서 온 건가요?

환경 변수는 AWS 우선순위에서 자격 증명 파일보다 위입니다. 셸 프로필이나 .env, 또는 이전 export가 AWS_ACCESS_KEY_ID를 설정한 것입니다. unset하면 프로필이 나섭니다.

InvalidClientTokenId는 제 IAM 정책이 잘못됐다는 뜻인가요?

아니요. 이 오류는 어떤 권한 확인보다 앞선 인증 단계에서 실패합니다. 정책 문제는 작업과 리소스를 이름으로 지목하는 AccessDenied로 나타납니다.

임시 자격 증명을 붙여 넣으면서 session token을 빠뜨렸는데, 그게 원인인가요?

네. 임시 자격 증명은 짝이 되는 AWS_SESSION_TOKEN이 있어야만 유효합니다. session token 없이 STS 세션의 access key와 secret만 있으면 무효로 거부됩니다.

시계 문제로도 이 오류가 나나요?

시계가 틀리면 보통 InvalidClientTokenId가 아니라 SignatureDoesNotMatch가 납니다. 그래도 date로 확인해, 몇 분 이상 어긋났다면 배제하기 전에 먼저 동기화하세요.

참고 자료

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

같은 카테고리 다른 글

detected dubious ownershipFixed

Git: fatal: detected dubious ownership in repository

Git 2.35.2의 CVE-2022-24765 수정 이후, Git은 작업 트리나 .git 디렉터리의 소유자가 명령을 실행한 사용자와 다르면 저장소를 읽지 않습니다. 컨테이너, CI 작업, sudo 세션, 공유 드라이브에서 주로 나타납니다. 저장소가 내 것이어야 한다면 소유권을 바로잡고, 아니라면 global 설정의 safe.directory에 정확한 경로를 추가하세요 — Git은 이 설정을 저장소 자신의 설정에서는 무시합니다.

Git
failed calling webhookFixed

Kubernetes: Internal error occurred: failed calling webhook

admission webhook이 쓰기 요청 앞에 서 있는데 API server가 응답을 받지 못했고, 기본값인 failurePolicy: Fail이 그 침묵을 거부로 바꾼 것입니다. 메시지 끝부분이 곧 진단입니다 — context deadline exceeded는 호출이 도달하지 못한 것, no endpoints available은 떠 있는 게 없는 것, x509 줄은 API server가 webhook 인증서를 신뢰하지 않는 것입니다. 원인마다 해결이 다르고, 어느 것도 매니페스트 문제가 아닙니다.

Kubernetes
1205Fixed

MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

다른 트랜잭션이 쥐고 있는 행 잠금을 innodb_lock_wait_timeout 만큼 기다리다 포기한 것입니다. sys.innodb_lock_waits 가 막고 있는 세션을 지목하고 KILL 문까지 만들어 주며, blocking_query 가 NULL 이면 블로커가 열린 트랜잭션 위에서 놀고 있다는 뜻입니다. 재시도 로직이 가장 자주 틀리는 지점은 따로 있습니다. 기본값에서는 타임아웃된 문장 하나만 롤백되므로, 트랜잭션은 그대로 열린 채 앞서 잡은 잠금을 전부 쥐고 있습니다.

MySQL
MISCONFFixed

Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk

읽기는 되는데 모든 쓰기가 거부됩니다. 마지막 백그라운드 저장이 실패했고 stop-writes-on-bgsave-error 기본값이 yes 이기 때문입니다. 진짜 원인은 로그에 적혀 있습니다 — 디스크 공간 부족, redis 사용자가 쓸 수 없는 dir, rename 시점의 read-only 마운트, 또는 fork 가 Cannot allocate memory 로 실패하는 경우입니다. 원인을 고치고 BGSAVE 한 번만 돌리면 재시작 없이 쓰기가 돌아옵니다. rdb_last_bgsave_status 가 err 에서 ok 로 바뀝니다. stop-writes-on-bgsave-error no 는 쓰기를 즉시 되살리지만 스냅샷은 여전히 실패한 상태로 두므로, 해결이 아니라 의도한 맞교환으로 다뤄야 합니다.

Redis
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryFixed

Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (종료 코드 134)

V8 힙에는 시스템 메모리와 Node 릴리스에서 유도되는 자기만의 천장이 있고, 가진 RAM 보다 훨씬 낮은 경우가 흔합니다. 빌드나 서버가 거기 닿으면 V8 은 FATAL ERROR: Reached heap limit 과 종료 코드 134 로 스스로 중단합니다. v8.getHeapStatistics().heap_size_limit 으로 실제 한도를 읽은 뒤, 큰 작업은 --max-old-space-size(MiB)나 NODE_OPTIONS 로 올리고, 컨테이너 안에서는 cgroup 한도보다 낮게 잡고, 오래 도는 프로세스의 누수는 --heapsnapshot-near-heap-limit 으로 잡으세요. FATAL ERROR 줄 없이 137 로 끝나면 컨테이너 kill 이지 이 오류가 아닙니다.

Node.js
exec /docker-entrypoint.sh: exec format errorFixed

Docker: 컨테이너 시작 직후 "exec format error" — 플랫폼이 다른 이미지, 에뮬레이터 없음, 셔뱅 없는 스크립트

컨테이너가 첫 명령에서 exec format error 로 끝납니다. 커널의 ENOEXEC, 즉 파일은 있지만 여기서는 실행할 수 없다는 뜻입니다. 실제로는 한 CPU 아키텍처에서 빌드한 이미지(Apple 실리콘 Mac은 linux/arm64 를 만듭니다)를 binfmt_misc 에 QEMU 핸들러가 없는 다른 아키텍처(x86_64 서버)에서 돌리거나, 엔트리포인트 스크립트의 첫 줄이 셔뱅이 아닌 경우입니다. uname -m, docker image inspect, ls /proc/sys/fs/binfmt_misc 로 원인을 가르고, docker buildx build --platform 을 명시(또는 두 플랫폼 매니페스트 목록 발행)하거나, 에뮬레이션이 목적이면 QEMU 등록과 --platform, 스크립트라면 #!/bin/sh 한 줄로 해결합니다.

Docker