AWS CLI Unable to locate credentials: 키 출처 우선순위
AWS CLI v2 설치가 끝난 것과 S3 목록 조회가 되는 것 사이에는, CLI가 자격 증명을 어디서 읽느냐는 단계가 하나 더 있습니다. 그 단계가 비어 있으면 Unable to locate credentials가 나오고, 이때는 실행 파일보다 CLI가 어느 설정에서 키를 읽는지를 먼저 쫓아야 합니다. 운영 서버에서 돌릴 AWS 작업 스크립트의 점검 절차를 정하려고, Debian 13에서 이 자격 증명 누락 오류를 일으키고 프로필 설정으로 되살리는 과정을 로컬 moto_server를 상대로 실행했습니다. 빈 설정으로 오류를 재현하고, 이름 있는 프로필로 로컬 요청을 성공시킨 다음, 환경 변수가 키의 출처를 바꾸는 지점까지 비교했습니다. 점검 절차를 동료에게 넘길 때 설치 여부와 자격 증명 선택 결과를 따로 적어 둘 수 있도록, 출력마다 무엇을 읽어야 하는지 이어서 설명합니다.
설치가 끝난 것과 키를 찾은 것은 별개
Debian 서버에서 명령을 하나씩 실행하면서 오류 문구와 설정 출처를 같이 비교했습니다. 표는 실행 환경과 요청 대상을 고정해 두는 기준입니다. 뒤에 나오는 성공과 실패는 모두 이 환경에서 한 로컬 실습의 결과입니다.
| 항목 | 테스트 환경 |
|---|---|
| OS | Debian GNU/Linux 13 (trixie) |
| 주요 도구 버전 | aws-cli/2.38.1, moto 5.2.4 |
| 로컬 엔드포인트 | http://127.0.0.1:5005 |
| 확인일 | 2026-10-15 |
AWS CLI는 공개 배포 파일 awscli-exe-linux-x86_64.zip을 사용해 사용자 폴더에 설치합니다. 설치 경로와 실행 파일 경로를 지정하는 방식으로 루트 권한 없이 구성합니다. 설치 절차는 AWS CLI 설치 안내(확인일 2026-10-15)를 참고합니다. 이 글에서 비교하는 실행 버전은 출력에 나타나는 aws-cli/2.38.1로 한정합니다.
요청 대상은 moto_server라서 실제 AWS 계정도, 과금되는 호출도 쓰지 않습니다. 프로필에 넣는 testing은 로컬 테스트용 더미 키입니다. 로컬 엔드포인트는 moto 서버 모드 안내(확인일 2026-10-15)를 보고 지정했습니다. 그러니 여기서 말하는 성공은 자격 증명을 찾아가는 과정과 로컬 요청 흐름이 제대로 도는지를 본 결과입니다.
빈 설정에서 오류를 만들고 프로필로 되살리기
설정 경로와 요청 대상부터 고정
AWS_CONFIG_FILE과 AWS_SHARED_CREDENTIALS_FILE은 awshome 아래를 가리키게 했습니다. 기존 ~/.aws는 건드리지 않은 채, 아무것도 없는 설정과 프로필을 저장한 설정을 비교하기 위해서입니다. 아래는 common.sh의 핵심 설정이고, R은 실행 스크립트가 놓인 디렉터리를 뜻합니다. 실제 실행에서는 run 디렉터리를 기준으로 설정 파일 경로가 정해집니다.
export AWS_CONFIG_FILE=$R/awshome/config
export AWS_SHARED_CREDENTIALS_FILE=$R/awshome/credentials
export AWS_EC2_METADATA_DISABLED=true
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_PROFILE AWS_DEFAULT_REGION
EP=http://127.0.0.1:5005
AWS_EC2_METADATA_DISABLED=true는 EC2 인스턴스 메타데이터 조회를 끄는 설정입니다. EC2 밖의 서버에서 자격 증명을 탐색할 때 메타데이터 단계의 대기를 피하려고 적용합니다. 이 설정과 키 등록은 역할이 다르므로, 메타데이터 조회를 끈 뒤에도 자격 증명이 비어 있으면 프로필이나 환경 변수의 값을 따로 점검합니다.
자격 증명이 비어 있을 때 나오는 실제 오류
재현 스크립트는 빈 설정에서 버전과 aws configure list를 먼저 보고, 리전과 로컬 엔드포인트를 지정해 S3 목록을 요청합니다. 출력에서는 명령의 버전 정보와 자격 증명 탐색 결과를 함께 읽어야 합니다. aws-cli/2.38.1이 멀쩡히 찍혀도 키 항목이 비어 있으면 요청은 aws_exit=253으로 끝납니다. 버전이 나온다고 호출할 준비가 된 것은 아닙니다.
$ aws --version
aws-cli/2.38.1 Python/3.14.6 Linux/6.12.94+ exe/x86_64.debian.13
$ aws configure list
NAME : VALUE : TYPE : LOCATION
profile : <not set> : None : None
access_key : <not set> : None : None
secret_key : <not set> : None : None
region : <not set> : None : None
$ aws s3 ls --endpoint-url $EP --region ap-northeast-2; echo "aws_exit=$?"
aws: [ERROR]: An error occurred (NoCredentials): Unable to locate credentials. You can configure credentials by running "aws login".
aws_exit=253

profile, access_key, secret_key, region이 모두 <not set>입니다. S3 요청에 –region은 넣었지만 자격 증명이 없으니 NoCredentials와 aws_exit=253이 나옵니다. 캡처에서 눈여겨볼 것은 두 가지입니다. 리전을 지정하는 것만으로는 이 오류가 풀리지 않는다는 점, 그리고 이 버전의 안내 문구가 aws login을 언급한다는 점입니다. 안내는 안내대로 읽되, 이번 로컬 실습에서는 이름 있는 프로필에 더미 키를 저장하는 쪽으로 풀었습니다.
이름 있는 프로필을 만들고 --profile로 고르기
moto 프로필에 액세스 키와 시크릿 키를 넣고 리전을 설정한 다음, 같은 프로필을 지정해 설정 출처를 조회하고 S3 버킷 생성과 목록 조회를 이어서 실행했습니다. 해결 스크립트의 설정 단계에서는 저장할 때도, 부를 때도 –profile moto를 붙여 고르는 대상을 맞춥니다. 키를 저장해 놓고 다른 프로필을 고르면 의도한 설정을 읽지 못하니, 값을 쓰는 단계와 쓰는 쪽을 한 흐름으로 확인합니다.
aws configure set aws_access_key_id testing --profile moto
aws configure set aws_secret_access_key testing --profile moto
aws configure set region ap-northeast-2 --profile moto
aws configure list --profile moto
aws s3 mb s3://demo-bucket --profile moto --endpoint-url $EP
aws s3 ls --profile moto --endpoint-url $EP
설정 파일을 열면 구역 이름도 같이 봅니다. config에는 [profile moto], credentials에는 [moto]로 적혀 있습니다. 출력은 키를 가린 파일 내용과 CLI가 실제로 고른 출처를 함께 보여 주니, 파일이 있다는 것을 넘어 무엇이 선택됐는지까지 확인됩니다. 두 파일의 구역 표기를 억지로 같은 모양으로 맞추기보다, 실행 결과를 설정 및 자격 증명 파일 안내(확인일 2026-10-15)와 맞대어 보는 편이 정확합니다.
$ aws configure set aws_access_key_id testing --profile moto && aws configure set aws_secret_access_key testing --profile moto && aws configure set region ap-northeast-2 --profile moto
$ cat $AWS_CONFIG_FILE; sed "s/= .*/= ****/" $AWS_SHARED_CREDENTIALS_FILE
[profile moto]
region = ap-northeast-2
[moto]
aws_access_key_id = ****
aws_secret_access_key = ****
$ aws configure list --profile moto
NAME : VALUE : TYPE : LOCATION
profile : moto : manual : --profile
access_key : ****************ting : shared-credentials-file :
secret_key : ****************ting : shared-credentials-file :
region : ap-northeast-2 : config-file : /home/user/aws-creds/awshome/config
$ aws s3 mb s3://demo-bucket --profile moto --endpoint-url $EP && aws s3 ls --profile moto --endpoint-url $EP; echo "aws_exit=$?"
make_bucket: demo-bucket
2026-10-15 23:26:01 demo-bucket
aws_exit=0

캡처의 config-file 값은 실험용 폴더의 실제 경로이고, 본문은 같은 값을 /home/user/aws-creds 아래로 줄여 적었습니다.
profile 행에는 manual과 –profile이, 키의 TYPE에는 shared-credentials-file이 찍힙니다. region 행은 config-file과 읽어 온 경로를 보여 줍니다. 그 뒤로 make_bucket, demo-bucket 목록, aws_exit=0이 이어집니다. 설정 파일에 값을 적은 것, CLI가 그 값을 고른 것, 요청이 성공한 것을 한 단계씩 차례로 확인한 셈입니다.
명령에 –profile moto를 붙이면 어떤 프로필을 쓰는지가 드러나고, configure list에서 키와 리전의 출처를 한꺼번에 볼 수 있습니다. 대신 호출할 때마다 프로필 이름을 맞춰 줘야 하고, 이름을 잘못 치면 원하는 프로필을 못 찾는 오류를 따로 처리해야 합니다.
AWS_PROFILE로 고를 때 키의 출처가 어디인지 보기
같은 프로필을 계속 쓴다면 AWS_PROFILE=moto를 설정하는 방법이 편합니다. 다만 프로필을 고르는 변수와 키 자체를 넘기는 변수는 하는 일이 다릅니다. 점검 스크립트의 핵심 명령에서 첫 줄은 기본으로 쓸 프로필을 고르고, 마지막 줄은 환경 변수로 키를 줬을 때 출처가 어떻게 찍히는지 봅니다.
export AWS_PROFILE=moto
aws configure list
AWS_ACCESS_KEY_ID=envkey AWS_SECRET_ACCESS_KEY=envsecret aws configure list
점검 스크립트 출력을 보면 AWS_PROFILE로 프로필을 골랐을 때 profile의 TYPE은 env인데 access_key는 여전히 shared-credentials-file입니다. 여기에 AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY를 붙인 명령에서는 access_key의 TYPE이 env로 바뀝니다. 이 실습 조건에서는 환경 변수의 키가 공유 자격 증명 파일의 키보다 먼저 쓰인다는 뜻입니다. 변수마다의 의미는 AWS CLI 환경 변수 안내(확인일 2026-10-15)에 있습니다.
AWS_PROFILE은 반복해서 쓸 프로필을 한 번에 골라 두고, configure list로 선택 상태를 확인하며, 환경 변수 키를 줬을 때의 출처 변화도 같은 명령으로 비교하게 해 줍니다. 함정은 프로필 이름만 보고 안심하는 것입니다. 환경 변수 키가 선택된 상태를 놓치기 쉬우니 access_key의 TYPE까지 같이 봐야 합니다.
프로필 이름 오류와 우선순위 함정
설정을 마친 뒤에도 문제가 남는다면 먼저 오류 메시지부터 가립니다. 점검 스크립트는 없는 motox 프로필로 부른 결과, AWS_PROFILE을 고른 결과, 환경 변수 키를 붙인 결과, 호출자 조회 결과를 차례로 보여 줍니다. 프로필 조회가 실패한 aws_exit=255와 호출자 조회가 성공한 aws_exit=0을 나눠 읽어야 합니다. 중간의 configure list는 키를 어디서 골랐는지 보여 주는 단계일 뿐이라, 그 결과를 곧바로 다음 요청의 신원으로 단정하지 않습니다.
$ aws s3 ls --profile motox --endpoint-url $EP; echo "aws_exit=$?"
aws: [ERROR]: The config profile (motox) could not be found
aws_exit=255
$ export AWS_PROFILE=moto; aws configure list | grep -E "profile|access_key"
profile : moto : env : ['AWS_PROFILE', 'AWS_DEFAULT_PROFILE']
access_key : ****************ting : shared-credentials-file :
$ AWS_ACCESS_KEY_ID=envkey AWS_SECRET_ACCESS_KEY=envsecret aws configure list | grep -E "access_key"
access_key : ****************vkey : env :
$ aws sts get-caller-identity --endpoint-url $EP --output text; echo "aws_exit=$?"
123456789012 arn:aws:sts::123456789012:user/moto AKIAIOSFODNN7EXAMPLE
aws_exit=0

캡처에는 motox 프로필 조회 실패와 환경 변수를 붙인 뒤의 키 출처 변화가 같이 잡혀 있습니다. 없는 이름을 지정한 호출은 255로 끝나고, AWS_PROFILE로 moto를 고른 뒤에는 파일에서 키를 읽습니다. 이어서 키 변수를 명령 앞에 붙이면 access_key의 TYPE이 env로 바뀌니, 프로필 이름이 같아도 키가 어디서 왔는지는 달라질 수 있습니다. 장애 기록에 프로필 행과 키 행을 함께 남겨 두면 다음 담당자도 같은 조건에서 원인을 따라갈 수 있습니다.
프로필 이름 오류를 자격 증명 누락과 구분합니다
motox를 지정한 호출은 The config profile (motox) could not be found와 aws_exit=255를 남깁니다. 앞에서 본 NoCredentials, aws_exit=253과는 메시지부터 다릅니다. 이때는 키를 다시 적을 것이 아니라, 명령에 쓴 이름과 저장한 moto 프로필 이름을 맞춰 봐야 합니다. 프로필을 찾는 단계와 그 프로필에서 키를 읽는 단계를 나눠 두면 어느 설정을 고칠지 금방 보입니다.
프로필의 출처와 액세스 키의 출처를 따로 읽습니다
profile 행의 env는 프로필 이름이 환경 변수에서 선택되었다는 뜻으로 읽습니다. 그 행만 보고 액세스 키도 환경 변수에서 읽었다고 판단하지 않습니다. 실제로 같은 출력에서 access_key는 shared-credentials-file을 가리킵니다. 다음 명령에서 access_key가 env로 바뀌는 지점까지 비교해야 프로필 선택과 키 선택의 차이를 확인합니다.
우선순위를 점검할 때는 실행 조건도 같이 적어 둡니다. 환경 변수 키를 비교한 명령에는 –profile이 없고, 프로필은 AWS_PROFILE로 골랐습니다. 그래서 이 결과를 모든 옵션 조합에 그대로 들이밀기보다는, 지금 쓰는 명령의 프로필 지정 방식과 configure list의 TYPE을 함께 확인하는 절차로 가져가시기를 권합니다.
호출자 조회 결과를 실습 범위에 맞게 해석합니다
마지막 sts get-caller-identity는 로컬 엔드포인트에서 호출자 정보를 확인하는 단계입니다. 바로 앞의 envkey와 envsecret은 configure list 명령 앞에 붙여 사용한 값이므로, 마지막 STS 결과를 해당 환경 변수 키의 호출 결과로 연결해 해석하지 않도록 명령 구성을 함께 읽습니다. 키 출처 비교와 호출자 조회를 별개의 점검 항목으로 다룹니다.
출력의 계정 123456789012와 AKIAIOSFODNN7EXAMPLE은 moto가 돌려주는 고정 예시 값입니다. 이 결과를 실제 AWS 계정의 신원 증거로 사용하지 않습니다. 여기서는 로컬 호출이 aws_exit=0으로 끝났다는 점과, 호출자 조회 명령까지 이어지는 흐름을 확인합니다. 모든 요청 명령에서 –endpoint-url이 로컬 주소를 가리키는지도 함께 점검합니다.
AWS 자격 증명은 어느 설정에서 오는가
버전이나 실행 환경을 비교할 때 기준은 재현 스크립트의 aws –version 출력과 해결 스크립트의 설정 파일 경로입니다. 기억하고 있는 설치 버전이나 파일 위치로 판단하지 말고, 지금 실행한 CLI가 보여 주는 버전과 LOCATION을 맞대어 봅니다. 실습처럼 설정 경로를 따로 떼어 놓았다면 기존 ~/.aws가 아니라 지정한 awshome을 열어 봐야 합니다.
이번 aws-cli/2.38.1은 자격 증명이 없을 때 aws login을 안내합니다. 이는 지금 실행 파일이 내놓은 해결 안내일 뿐, 로컬 테스트의 프로필 설정을 대신해 주지는 않습니다. 버전을 바꿀 때 안내 문구가 그대로일 거라 가정하지 말고, 버전 출력과 설정 출처를 다시 모아 운영 문서를 고칩니다. 업스트림의 API 폐기나 옵션 변경 여부를 짐작으로 단정하기보다는, 이번 출력에서 직접 읽히는 종료 값과 키의 TYPE을 점검 기준으로 씁니다.
헬스체크에 CLI 호출을 넣는다면 실행 파일이 있는지 보는 점검과 자격 증명을 써서 요청하는 점검을 나눠 두기를 권합니다. 오류 문장이 일치하는지만으로 상태를 판정하면 안내 문구가 바뀌는 순간 점검 결과가 흔들리니, 종료 값과 설정 출처를 같이 남기도록 절차를 고쳐 둡니다. 설정 경로를 옮겼다면 AWS_CONFIG_FILE과 AWS_SHARED_CREDENTIALS_FILE이 새 위치를 가리키는지, LOCATION에 찍힌 경로와 같은지 봅니다. 컨테이너의 점검 프로세스에도 같은 경로와 프로필 선택이 넘어가는지 확인해 두지 않으면, 작업 프로세스는 실패하는데 점검만 통과하는 일이 생깁니다.
어떤 출처로 키를 주고 무엇을 확인할 것인가
| 상황 | 확인 기준 | 대응 및 주의점 |
|---|---|---|
| 자격 증명 누락 | <not set>, NoCredentials, aws_exit=253 | moto 프로필에 더미 키와 리전을 설정하고 다시 조회합니다. |
| 프로필 명시 선택 | manual, –profile, shared-credentials-file | 명령에 –profile moto를 지정하고 저장한 이름과 대조합니다. |
| 환경 변수로 프로필 선택 | profile은 env, 키는 shared-credentials-file | AWS_PROFILE을 설정한 뒤 키의 출처까지 함께 확인합니다. |
| 환경 변수 키 지정 | access_key의 TYPE이 env | 공유 파일보다 환경 변수 키가 먼저 선택된 상태를 점검합니다. |
| 없는 프로필 지정 | motox 오류, aws_exit=255 | 프로필 이름을 저장된 moto와 맞춰 확인합니다. |
자격 증명이 빈 상태를 재현하고 해결 과정을 남겨야 한다면, 이름 있는 프로필을 만들고 –profile로 명시하는 방식을 추천합니다. 같은 프로필을 이어서 쓰는 작업에는 AWS_PROFILE이 편하지만, 환경 변수 키를 함께 쓰는 경우라면 access_key의 TYPE을 한 번 더 확인합니다. 프로필 이름 오류가 나오면 이름부터 맞춰 보고, 키 출처가 예상과 다르면 환경 변수와 공유 파일을 비교합니다.
이번 오류를 풀면서 분명해진 것은, 키를 몇 번 다시 넣느냐보다 CLI가 고른 출처를 눈으로 확인하는 일이 먼저라는 점입니다. 재현용으로는 별도 설정 경로, moto 프로필, 로컬 엔드포인트를 함께 고정하는 구성을 추천합니다. 운영 작업에서는 명령의 버전, 고른 프로필, 키의 출처, 요청 대상을 기록하되 실제 키 값은 남기지 않습니다. 실시간 서비스에 붙일 때도 서버 사양을 늘리기 전에 서비스 실행 환경과 점검 환경이 같은 자격 증명 설정을 읽는지부터 맞춰야 합니다. 서비스 상태 확인과 AWS 요청의 인증 점검을 갈라 두면, 프로필 누락으로 생긴 실패를 서비스 전체 장애로 오인하는 일이 사라집니다. 로컬 검증을 마친 뒤에는 실제 서비스가 쓸 자격 증명과 요청 대상으로 호출자 확인 절차를 이어 가는 것이 맞다고 판단합니다.






