MongoServerError bad auth Authentication failed (code 18) 30초 진단표로 즉시 해결
"비밀번호 맞게 쳤는데 왜 bad auth가 뜨죠?"
mongosh나 백엔드 앱에서 이 메시지를 만나고 이 글에 도착하셨을 겁니다.
MongoServerError: bad auth : Authentication failed.또는 드라이버 로그에서:
MongoServerError: Authentication failed. (code 18)99%의 개발자가 여기서 비밀번호를 세 번쯤 다시 칩니다. 하지만 MongoDB 인증의 핵심은 비밀번호가 아니라 "이 유저가 어느 DB에 주민등록(authSource)되어 있는가" 입니다. MySQL이나 PostgreSQL에서 넘어온 분들이 특히 여기서 막힙니다. 유저는 admin에 살고 있는데, 여러분은 mydb에 대고 로그인을 시도하니 "그런 사람 없는데요"라며 인증이 튕기는 겁니다.
이 글은 처음부터 끝까지 읽는 글이 아닙니다. 30초 안에 5가지 원인 중 하나를 특정하고, 복붙 명령으로 접속을 복구하는 진단 도구입니다.
30초 진단표: 내 에러는 5가지 중 무엇인가
가장 먼저 감별할 것은 인증 실패인지 권한 부족인지입니다. 이 둘은 완전히 다른 문제입니다.
| 증상(에러 메시지) | code | 확인 명령 | 원인 | 바로가기 |
|---|---|---|---|---|
bad auth : Authentication failed | 18 | 접속 URI에 ?authSource=admin 있는지 확인 | 유저가 소속된 DB(authSource) 불일치 | Case 1 |
bad auth (비번에 @ # : / 포함) | 18 | connection string 원문 확인 | 비밀번호 특수문자 URL 인코딩 누락 | Case 2 |
Authentication failed (신규 클라이언트) | 18 | db.getUser(...,{showCredentials:true}) | SCRAM-SHA-1 vs 256 mismatch | Case 3 |
not authorized on X to execute command | 13 | db.getUser("user") roles 확인 | 인증은 성공, 권한 부족 | Case 4 |
bad auth (막 --auth 켠 신규 서버) | 18 | system.users 비어있는지 확인 | admin 유저 미생성 | Case 5 |
핵심 감별점 두 가지만 기억하세요.
code 18(bad auth) = 인증 실패입니다. 유저·비번·authSource 중 하나가 틀렸습니다.code 13(not authorized) = 인증은 성공했으나 명령을 실행할 권한이 없습니다. 비밀번호는 맞았다는 뜻이니 절대 비번을 다시 치지 마세요.
유저가 어디에 등록됐는지 한 방에 보는 원라이너:
// 관리자로 접속한 상태에서 실행
db.getSiblingDB("admin").system.users.find({}, {user:1, db:1, "credentials":1})여기서 나오는 db 필드가 바로 그 유저의 authSource입니다. 대부분 admin으로 찍힙니다. 그럼 접속할 때도 authSource=admin을 붙여야 합니다.
케이스별 원인 진단 + 복붙 복구 명령
Case 1. authSource 미지정 (가장 흔함)
admin이 아닌 DB에 유저를 만들었거나(또는 Docker 루트 유저처럼 admin에 있는데), 접속 시 authSource를 빼먹은 경우입니다.
진단:
db.getSiblingDB("admin").system.users.find({user:"myuser"}, {user:1, db:1})
// db: "admin" 으로 나오면 → 접속 시 authSource=admin 필요복구 — mongosh:
mongosh "mongodb://myuser:mypass@localhost:27017/mydb?authSource=admin"Node.js (공식 mongodb 드라이버):
const { MongoClient } = require("mongodb");
const uri = "mongodb://myuser:mypass@localhost:27017/mydb?authSource=admin";
const client = new MongoClient(uri);
await client.connect();Python (pymongo):
from pymongo import MongoClient
client = MongoClient(
"localhost", 27017,
username="myuser", password="mypass",
authSource="admin"
)
# 또는 URI 형태
client = MongoClient("mongodb://myuser:mypass@localhost:27017/mydb?authSource=admin")Case 2. 비밀번호 특수문자 URL 인코딩
비밀번호에 @ : / ? # % 같은 문자가 있으면 connection string 파서가 URI 구조로 오인해 깨집니다. 예를 들어 비번이 p@ss:w0rd라면 @를 호스트 구분자로 읽어버립니다.
URL 인코딩 대응표:
| 문자 | 인코딩 | 문자 | 인코딩 |
|---|---|---|---|
@ | %40 | # | %23 |
: | %3A | % | %25 |
/ | %2F | ? | %3F |
Node.js 자동 인코딩:
const user = encodeURIComponent("myuser");
const pass = encodeURIComponent("p@ss:w0rd");
const uri = `mongodb://${user}:${pass}@localhost:27017/mydb?authSource=admin`;Python 자동 인코딩:
from urllib.parse import quote_plus
from pymongo import MongoClient
uri = "mongodb://%s:%s@localhost:27017/mydb?authSource=admin" % (
quote_plus("myuser"), quote_plus("p@ss:w0rd"))
client = MongoClient(uri)실무 팁: 저는 아예 팀 컨벤션으로 "비밀번호는 URI에 직접 넣지 말고 드라이버의 username/password 파라미터로 분리한다"를 못 박아뒀습니다. pymongo나 Node 드라이버 모두 파라미터로 넘기면 인코딩 함정을 원천 차단할 수 있어서, 이 방식으로 바꾼 뒤 Case 2 문의가 사라졌습니다.
Case 3. SCRAM-SHA-1 vs SCRAM-SHA-256 mismatch
MongoDB 6.x/7.x의 기본 메커니즘은 SCRAM-SHA-256입니다. 오래된 스크립트로 만든 유저가 SHA-1만 갖고 있거나, 클라이언트가 협상에 실패하면 인증이 튕깁니다.
진단 — 저장된 메커니즘 확인:
db.getUser("myuser", { showCredentials: true })
// credentials 객체에 SCRAM-SHA-1 / SCRAM-SHA-256 키가 보인다복구 — 메커니즘 재설정(양쪽 다 부여):
db.updateUser("myuser", {
mechanisms: ["SCRAM-SHA-256", "SCRAM-SHA-1"],
pwd: "mypass" // 메커니즘 갱신 시 비번 재설정 필요
})클라이언트에서 명시적으로 지정:
// Node.js
const uri = "mongodb://myuser:mypass@localhost:27017/mydb"
+ "?authSource=admin&authMechanism=SCRAM-SHA-256";# pymongo
client = MongoClient(
"localhost", 27017,
username="myuser", password="mypass",
authSource="admin", authMechanism="SCRAM-SHA-256"
)Case 4. 유저는 있으나 roles 부족 (bad auth 아님!)
not authorized on mydb to execute command(code 13)가 떴다면 로그인은 성공한 겁니다. 역할만 붙여주면 됩니다.
진단:
db.getUser("myuser") // roles 배열이 비었거나 부족한지 확인복구:
db.getSiblingDB("admin").grantRolesToUser("myuser", [
{ role: "readWrite", db: "mydb" }
])Case 5. --auth 활성화 후 최초 admin 유저 미생성
--auth를 켰는데 admin 유저를 안 만들었다면, 아무도 로그인할 수 없는 잠긴 상태가 됩니다. localhost exception으로 부트스트랩합니다.
안전한 순서 (self-hosted):
# 1) auth 없이 재기동 (또는 localhost exception 이용)
mongod --dbpath /data/db --bind_ip localhost
# 2) localhost에서 접속 후 admin 유저 생성
mongoshuse admin
db.createUser({
user: "root",
pwd: "strongPass123!",
roles: [{ role: "root", db: "admin" }]
})# 3) --auth 켜고 재기동
mongod --dbpath /data/db --authDocker라면 환경변수 한 방:
docker run -d --name mongo \
-e MONGO_INITDB_ROOT_USERNAME=root \
-e MONGO_INITDB_ROOT_PASSWORD=strongPass123! \
-p 27017:27017 mongo:7이렇게 뜬 루트 유저는 admin DB에 생성됩니다. 따라서 접속 시 반드시 authSource=admin이 필요합니다(Case 1 함정과 직결).
연결 문자열 레퍼런스 & Docker 체크리스트
정상 접속 문자열 완성형을 한자리에 모았습니다.
# mongosh
mongosh "mongodb://root:strongPass123!@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-256"// Node.js
const uri = "mongodb://root:strongPass123!@localhost:27017/mydb"
+ "?authSource=admin&authMechanism=SCRAM-SHA-256";# pymongo (URI)
client = MongoClient(
"mongodb://root:strongPass123!@localhost:27017/mydb?authSource=admin&authMechanism=SCRAM-SHA-256")Docker 체크리스트
-
MONGO_INITDB_ROOT_*유저는 항상 admin에 생김 →authSource=admin필수 - 최초 기동 시점에만 초기화 스크립트 실행됨 (볼륨이 이미 있으면 무시)
- 앱 전용 유저는
mydb에 만들되, 접속 URI에는 그 유저의 authSource를 정확히 명시
결론: 다음에 또 막히지 않으려면
한 줄 요약: code 18이면 authSource·비번·SCRAM을 의심하고, code 13이면 roles를 부여하세요.
재발 방지 원칙 두 가지만 지키면 됩니다.
- 항상 authSource를 명시한다 (특히 Docker 루트 유저는 무조건 admin).
- 비밀번호는 반드시 인코딩 함수를 통과시키거나 드라이버 파라미터로 분리한다.
MySQL의 ERROR 1698(auth_socket/mysql_native_password)이나 PostgreSQL의 password authentication failed(pg_hba.conf) 런북을 찾아온 분이라면 헷갈릴 수 있는데, MongoDB에는 그들에게 없는 두 가지 고유 개념이 있습니다. 바로 **유저가 소속된 인증 DB(authSource)**와 SCRAM 메커니즘 협상입니다. MySQL은 user@host 기준, PostgreSQL은 pg_hba.conf의 접속 규칙 기준으로 인증하지만, MongoDB는 "유저가 어느 DB에 등록됐는가"가 인증의 전제 조건이라는 점이 결정적으로 다릅니다.
자주 묻는 질문 (FAQ)
Q. authSource를 안 쓰고 접속할 수는 없나요?
A. 유저가 접속 대상 DB와 동일한 DB에 생성돼 있다면 생략 가능합니다. 하지만 Docker 루트 유저나 공용 관리 유저는 admin에 있으므로 사실상 authSource=admin이 필수입니다. 헷갈리지 않게 항상 명시하는 걸 권장합니다.
Q. 비밀번호는 맞는데 계속 bad auth가 떠요.
A. 십중팔구 authSource 불일치(Case 1) 또는 비밀번호 특수문자 인코딩(Case 2)입니다. db.getSiblingDB("admin").system.users.find()로 유저의 실제 db 필드를 먼저 확인하세요.
Q. not authorized도 비밀번호 문제인가요?
A. 아닙니다. code 13은 인증에 성공한 뒤 권한이 부족한 상태입니다. grantRolesToUser로 역할만 부여하면 됩니다(Case 4).
Q. mongo 셸로 접속하던 스크립트가 안 돼요.
A. 구형 mongo 셸은 폐기됐고 현재 표준은 mongosh입니다. 명령 문법과 기본 SCRAM-SHA-256 협상 방식이 달라졌으니 mongosh 기준 예제로 교체하세요.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.