Ghost Docker 관리자 로그인 메일의 EAUTH·ESOCKET 오류를 해결하는 유령 캐릭터

Ghost 관리자 로그인이 안 될 때: EAUTH·ESOCKET Gmail SMTP 해결

Docker 2026년 8월 19일

3줄 요약

  • Ghost 업데이트가 관리자 비밀번호를 바꾼 것이 아니라, 새 기기 확인용 6자리 코드를 보내는 SMTP 단계에서 로그인이 막힌 경우였습니다.
  • EAUTH535 Authentication failed는 인증 거부를, ESOCKET은 네트워크·소켓 계층 문제를 우선 확인해야 합니다.
  • Gmail은 16자리 앱 비밀번호와 465 + secure: "true" 조합으로 연결하고, Compose 변경 후에는 컨테이너를 재생성해야 합니다.

Ghost Docker 업데이트 뒤 관리자 페이지에 로그인을 시도했는데 비밀번호가 맞아도 다음 오류가 반복됐습니다.

Failed to send email. Please check your site configuration and try again.
Error Code: EAUTH
Invalid login: 535 Authentication failed

메일 공급자를 바꾼 뒤에는 오류 코드가 ESOCKET으로 달라지기도 했습니다. 처음에는 업데이트 과정에서 Ghost 관리자 비밀번호가 변경된 것으로 보였지만, 실제 원인은 비밀번호가 아니었습니다. 새 기기 확인 코드를 보내야 하는 SMTP 설정이 실패하면서 로그인 세션 생성까지 중단된 것이었습니다.

이 글은 2026년 8월 19일, Ghost 6.57.1을 Docker Compose로 운영하며 직접 확인한 장애와 공식 문서를 바탕으로 작성했습니다. 이메일 주소·서버 IP·오류 ID·SMTP 비밀번호는 모두 예시값으로 바꿨습니다.

결론부터: 관리자 비밀번호가 바뀐 것이 아니었다

Ghost의 장치 인증 안내에 따르면 관리자가 새 기기나 인식되지 않은 브라우저에서 로그인하면 등록 이메일로 6자리 확인 코드가 전송됩니다. 이 장치 인증은 모든 Ghost 사이트에 기본으로 적용됩니다.

로그인 흐름은 다음과 같습니다.

  1. 관리자가 이메일과 비밀번호를 입력합니다.
  2. Ghost가 새 기기인지 확인합니다.
  3. 새 기기라면 등록 이메일로 6자리 코드를 보냅니다.
  4. 코드를 확인한 뒤 관리자 세션이 완성됩니다.

따라서 3번의 메일 전송이 실패하면 비밀번호가 맞더라도 관리자 화면에 들어가지 못할 수 있습니다. 실제 로그에도 다음처럼 인증 코드 발송 함수가 표시됐습니다.

ERROR "POST /ghost/api/admin/session" 500

Failed to send email. Please check your site configuration and try again.

Error Code: EAUTH

EmailError: Failed to send email.
    at Object.sendAuthCodeToUser (.../session-service.js:...)

sendAuthCodeToUser가 보인다면 관리자 비밀번호 초기화부터 반복하기보다 SMTP 로그와 메일 공급자 상태를 먼저 확인하는 편이 빠릅니다. 이는 Ghost의 공식 장치 인증 동작과 실제 오류 스택을 함께 보고 내린 진단입니다.

EAUTH 535와 ESOCKET은 원인이 다릅니다

Ghost는 내부적으로 Nodemailer를 사용합니다. Nodemailer 오류 문서EAUTH를 인증 실패, ESOCKET을 소켓 계층 오류로 구분합니다.

EAUTH·535 Authentication failed

EAUTH와 SMTP 응답 코드 535가 함께 나온다면 SMTP 서버에는 도달했지만 인증이 거절된 상태입니다.

우선 확인할 항목은 다음과 같습니다.

  • SMTP 사용자명이 전체 이메일 주소인지
  • 일반 계정 비밀번호가 아닌 앱 비밀번호를 사용했는지
  • 앱 비밀번호 앞뒤에 공백이나 따옴표가 함께 저장되지 않았는지
  • 메일 공급자 계정이 잠기거나 비활성화되지 않았는지
  • Google 계정 비밀번호 변경 뒤 기존 앱 비밀번호가 폐기되지 않았는지
  • 컨테이너가 변경된 환경변수로 실제 재생성됐는지

비밀번호를 여러 번 재설정해도 계속 535가 나온다면 계정 상태를 꼭 확인해야 합니다.

이번 장애에서는 Mailgun 대시보드에 계정이 침해 가능성 때문에 일시 비활성화됐다는 안내가 표시됐습니다. 이 상태에서는 SMTP 사용자를 새로 만들고 IP 허용 목록을 수정해도 계정 단위의 인증 거부가 계속됐습니다. 공급자 계정이 잠겼다면 자격 증명을 반복해서 바꾸는 대신 Mailgun의 침해 계정 복구 안내처럼 공급자 지원 절차로 계정을 복구해야 합니다.

ESOCKET

ESOCKET은 비밀번호가 틀렸다는 뜻이 아닙니다. Node.js의 네트워크 또는 TLS 소켓 단계에서 연결이 깨졌을 때 나타날 수 있습니다.

다음 순서로 확인합니다.

  • SMTP 호스트 오타
  • 포트와 secure 조합
  • 서버 또는 클라우드 방화벽의 외부 SMTP 연결 제한
  • 연결 중단이나 일시적인 네트워크 문제
  • 컨테이너 DNS 해석과 외부 접속 상태

ESOCKET만으로 포트 오류라고 단정할 수는 없습니다. 오류 메시지에 ECONNRESET, EPIPE 같은 하위 원인이 있는지도 함께 봐야 합니다. DNS, TLS, 시간 초과는 각각 EDNS, ETLS, ETIMEDOUT처럼 다른 코드로 나타날 수도 있습니다.

Missing mail.from은 별도 경고

다음 경고가 함께 보일 수 있습니다.

Missing mail.from config, falling back to a generated email address.

유효한 발신 주소를 설정하라는 뜻이므로 수정하는 것이 맞습니다. 다만 이 경고 자체가 535 Authentication failed를 만든 직접 원인은 아닙니다. 인증 오류와 발신 주소 경고를 분리해서 처리해야 합니다.

Gmail SMTP를 사용하기 전에 준비할 것

Ghost의 관리자 장치 인증과 비밀번호 재설정 같은 소량 트랜잭션 메일은 일반 SMTP로 보낼 수 있습니다. Mailgun 계정 복구를 기다리는 동안 Gmail SMTP로 전환해 관리자 로그인을 복구했습니다.

1. 전용 Google 계정 사용

개인적으로 계속 사용하는 주 계정보다 블로그의 운영 메일만 담당할 별도 계정을 권장합니다. 문제가 생겼을 때 영향 범위를 줄이고 발송 내역도 분리할 수 있기 때문입니다.

Gmail 주소와 앱 비밀번호를 공개 저장소, 캡처, 메신저에 올리면 안 됩니다. 이 글의 your-account@gmail.com은 실제 주소가 아닌 예시입니다.

2. 2단계 인증 활성화

Google 앱 비밀번호를 만들려면 먼저 Google 계정의 2단계 인증을 켜야 합니다.

3. 16자리 앱 비밀번호 생성

Google 앱 비밀번호 안내에 따라 16자리 앱 비밀번호를 생성합니다. Ghost에는 일반 Google 계정 비밀번호가 아니라 이 앱 비밀번호를 입력해야 합니다.

다음 환경에서는 앱 비밀번호 메뉴가 나타나지 않을 수 있습니다.

  • 보안 키만으로 2단계 인증을 설정한 계정
  • 조직 정책이 적용된 직장·학교 계정
  • Google 고급 보호 프로그램이 적용된 계정

Google 계정 비밀번호를 변경하면 기존 앱 비밀번호도 폐기됩니다. 이전에 잘 되다가 갑자기 EAUTH가 발생했다면 이 부분도 확인하세요.

Docker Compose Gmail SMTP 설정

Compose 파일의 Ghost 서비스 environment에 다음 값을 설정합니다.

services:
  ghost:
    environment:
      mail__transport: "SMTP"
      mail__from: "mLog <your-account@gmail.com>"
      mail__options__host: "smtp.gmail.com"
      mail__options__port: "465"
      mail__options__secure: "true"
      mail__options__auth__user: "your-account@gmail.com"
      mail__options__auth__pass: "${GMAIL_APP_PASSWORD}"

앱 비밀번호는 Compose 파일에 직접 쓰지 않고 같은 디렉터리의 .env에 둡니다.

GMAIL_APP_PASSWORD=여기에_16자리_앱_비밀번호

.env는 버전 관리에서 제외하고 소유자만 읽을 수 있도록 제한합니다.

printf '\n.env\n' >> .gitignore
chmod 600 .env

이미 .gitignore.env가 있다면 중복으로 추가할 필요는 없습니다. Git 저장소가 아니라도 파일 권한 제한은 적용하는 것이 좋습니다.

단, .env가 이미 Git에 커밋된 적이 있다면 .gitignore만 추가해도 기존 이력에서 사라지지 않습니다. 즉시 노출된 앱 비밀번호를 폐기·재발급하고 저장소 이력에서도 제거해야 합니다. 이력 수정은 공동 작업자에게 영향을 줄 수 있으므로 저장소 상황에 맞는 절차를 별도로 확인하세요.

465와 587 중 어떤 포트를 써야 하나요?

Google의 Gmail SMTP 문서smtp.gmail.com에서 465와 587을 안내합니다.

  • 포트 465: 연결 시작부터 TLS를 사용하므로 secure: "true"
  • 포트 587: 연결 뒤 STARTTLS로 전환하므로 secure: "false"

587을 쓴다면 두 줄을 함께 바꿔야 합니다.

mail__options__port: "587"
mail__options__secure: "false"

secure: "false"는 메일을 끝까지 암호화하지 않는다는 뜻이 아닙니다. Nodemailer는 서버가 지원하면 STARTTLS로 연결을 승격합니다.

service 항목은 제거합니다

기존 Mailgun 설정에 다음 항목이 남아 있다면 삭제합니다.

mail__options__service: "Mailgun"

Nodemailer에서 service는 미리 정의된 연결 설정을 사용하며 host, port, secure 값을 덮어쓸 수 있습니다. 이 글처럼 연결 정보를 직접 지정할 때는 service를 함께 쓰지 않는 편이 명확합니다. Gmail로 바꿨는데도 여전히 이전 호스트로 연결되는 것처럼 보인다면 이 항목을 확인하세요.

true는 반드시 문자열로 작성합니다

Compose의 environment 아래 값은 컨테이너에 전달할 환경변수입니다. 다음처럼 따옴표 없는 YAML 불리언을 사용하면 일부 Compose 환경에서 자료형 오류가 발생할 수 있습니다.

mail__options__secure: true

실제로 예전 docker-compose 명령에서 다음 오류가 발생했습니다.

services.ghost.environment.mail__options__secure contains true,
which is an invalid type, it should be a string, number, or a null

Docker Compose environment 문서true, false, yes, no를 문자열로 전달하려면 따옴표로 감싸라고 안내합니다.

mail__options__secure: "true"

이 글의 명령은 현재 Docker Compose CLI 플러그인 문법인 docker compose로 통일했습니다. 유지보수가 종료된 docker-compose v1과 혼용하지 않는 편이 좋습니다.

변경한 SMTP 설정을 안전하게 적용하기

1. 내용 출력 없이 Compose 검증

sudo docker compose config -q

오류가 없다면 아무 내용 없이 종료됩니다. -q를 빼고 docker compose config를 실행하면 환경변수가 치환된 결과에 앱 비밀번호가 나타날 수 있습니다. 화면을 공유하거나 터미널 기록이 남는 환경에서는 출력형 명령을 피하세요.

2. Ghost 컨테이너 재생성

sudo docker compose up -d --force-recreate ghost

Docker의 restart 문서에 따르면 docker compose restart만 실행해서는 Compose 파일에서 바꾼 환경변수가 반영되지 않습니다. docker compose up 문서처럼 컨테이너를 재생성해야 새 설정이 들어갑니다.

up -d --force-recreate ghost는 Ghost 서비스 컨테이너를 재생성하지만 마운트된 콘텐츠 볼륨과 데이터베이스 볼륨을 삭제하는 명령은 아닙니다. 이 작업에 down -v는 필요하지 않습니다.

3. 부팅과 메일 오류 확인

sudo docker compose ps
sudo docker compose logs --tail=100 ghost

로그에 실제 이메일 주소, 오류 ID, 토큰이 포함될 수 있으므로 외부에 공유하기 전 반드시 가립니다.

비밀번호를 출력하지 않고 적용값 확인하기

다음 명령은 호스트·포트·보안 방식과 자격 증명 설정 여부만 보여 줍니다. 사용자명과 비밀번호 자체는 출력하지 않습니다.

sudo docker compose exec -T ghost node - <<'NODE'
const e = process.env;

console.log({
  transport: e.mail__transport,
  host: e.mail__options__host,
  port: e.mail__options__port,
  secure: e.mail__options__secure,
  userSet: Boolean(e.mail__options__auth__user),
  passSet: Boolean(e.mail__options__auth__pass),
  fromSet: Boolean(e.mail__from)
});
NODE

정상적인 465 설정이라면 다음과 비슷하게 표시됩니다.

{
  transport: 'SMTP',
  host: 'smtp.gmail.com',
  port: '465',
  secure: 'true',
  userSet: true,
  passSet: true,
  fromSet: true
}

비밀번호 길이나 해시를 비교하는 것보다 컨테이너 재생성 여부와 SMTP 서버의 실제 인증 결과를 확인하는 편이 정확합니다. 비밀번호는 로그에 출력하지 않는 것이 최선입니다.

고급 진단: 메일을 보내지 않고 SMTP 인증 확인

Nodemailer의 verify()는 DNS 해석, TCP 연결, TLS 전환, SMTP 인증을 검사하지만 실제 메일은 보내지 않습니다. Ghost 공식 이미지의 내부 경로는 버전에 따라 달라질 수 있으므로 일반 절차로 해결되지 않을 때만 사용하세요.

sudo docker compose exec -T ghost sh -lc \
  'cd /var/lib/ghost/current && node' <<'NODE'
const nodemailer = require('nodemailer');
const e = process.env;

const transport = nodemailer.createTransport({
  host: e.mail__options__host,
  port: Number(e.mail__options__port),
  secure: e.mail__options__secure === 'true',
  auth: {
    user: e.mail__options__auth__user,
    pass: e.mail__options__auth__pass
  }
});

transport.verify()
  .then(() => console.log('OK: SMTP 연결과 인증 성공'))
  .catch((error) => {
    console.error('FAIL:', error.code, error.responseCode || '');
    process.exitCode = 1;
  });
NODE

이 테스트가 EAUTH 535를 반환하면 Ghost 관리자 화면이나 리버스 프록시보다 SMTP 자격 증명·계정 상태를 우선 확인합니다. ESOCKET이면 호스트·포트·TLS 조합과 네트워크 경로부터 봅니다.

verify() 성공은 SMTP 서버가 특정 발신 주소의 메일을 최종 수락한다는 보장까지는 아닙니다. 마지막 확인은 Ghost 관리자 로그인을 다시 시도해 6자리 코드가 실제로 도착하는지 보는 것입니다.

실제 해결 순서

이번 장애는 다음 순서로 분리해 해결했습니다.

  1. Ghost 로그에서 sendAuthCodeToUserEAUTH 535를 확인했습니다.
  2. Mailgun SMTP 사용자와 앱 설정만의 문제가 아닌지 컨테이너 안에서 인증을 재검사했습니다.
  3. Mailgun 대시보드에서 계정 단위 비활성화 안내를 확인했습니다.
  4. 트랜잭션 메일을 Gmail SMTP로 임시 전환했습니다.
  5. 앱 비밀번호, 465 포트, secure: "true"를 설정했습니다.
  6. 남아 있던 mail__options__service를 제거했습니다.
  7. docker compose up -d --force-recreate ghost로 환경변수를 반영했습니다.
  8. 관리자 로그인을 다시 시도해 6자리 코드 수신과 로그인을 확인했습니다.

여기서 중요한 점은 메일 공급자를 무조건 Gmail로 바꾸는 것이 아니라 오류 계층을 먼저 분리하는 것입니다. 계정 잠금이 원인이라면 비밀번호 해시 비교나 서버 재부팅을 반복해도 해결되지 않습니다.

Gmail SMTP로 Ghost 뉴스레터도 보낼 수 있나요?

아닙니다. 이 글의 Gmail SMTP 설정은 관리자 장치 확인, 비밀번호 재설정, 초대 같은 소량 트랜잭션 메일용입니다.

Ghost 뉴스레터 문서Mailgun FAQ에 따르면 셀프 호스팅 Ghost의 내장 대량 뉴스레터는 기본 SMTP로 발송할 수 없으며, 2026년 8월 19일 현재 지원되는 대량 메일 API는 Mailgun입니다. 뉴스레터를 사용하지 않는다면 Mailgun 설정은 필요하지 않습니다.

개인 Gmail은 발송 한도와 스팸 방지 정책에 따라 일시적으로 발송이 제한될 수 있으므로 대량 발송 수단으로 사용하지 마세요. 이 블로그처럼 회원·뉴스레터 기능을 사용하지 않는 구성에서는 관리자 인증과 운영 알림 정도로 범위를 제한하는 편이 안전합니다.

그래도 로그인이 안 될 때 체크리스트

  • Ghost 로그에 EAUTH, ESOCKET, ETLS, EDNS 중 무엇이 표시되는가?
  • SMTP 호스트가 smtp.gmail.com으로 적용됐는가?
  • 465 + "true" 또는 587 + "false" 조합이 맞는가?
  • 일반 비밀번호가 아니라 현재 유효한 16자리 앱 비밀번호인가?
  • Google 계정 비밀번호를 변경한 뒤 앱 비밀번호를 새로 만들었는가?
  • 이전 mail__options__service가 남아 있지 않은가?
  • docker compose restart가 아니라 컨테이너를 재생성했는가?
  • 서버에서 외부 SMTP 포트로 연결할 수 있는가?
  • 메일 공급자 계정이 잠기거나 비활성화되지 않았는가?
  • 스팸함을 포함해 6자리 코드 메일이 도착했는가?

TLS 인증서 검사를 끄는 rejectUnauthorized: false는 운영 환경의 일반 해결책이 아닙니다. 인증서 오류의 원인을 숨기고 중간자 공격 위험을 키울 수 있으므로 사용하지 않았습니다.

자주 묻는 질문

Ghost 업데이트가 관리자 비밀번호를 바꾸나요?

이번 사례에서는 바뀌지 않았습니다. 업데이트 후 새 기기 확인 코드 발송 과정이 추가로 실행됐고, SMTP가 실패해 로그인이 막힌 것처럼 보였습니다. 비밀번호 초기화 전에 Ghost 로그의 sendAuthCodeToUser와 메일 오류 코드를 먼저 확인하세요.

EAUTH 535가 나오면 비밀번호만 바꾸면 되나요?

아닙니다. 사용자명·앱 비밀번호가 틀린 경우도 있지만 SMTP 계정이 잠기거나 비활성화된 경우에도 인증이 거절될 수 있습니다. 새 자격 증명으로 직접 인증을 검사하고 공급자 대시보드의 계정 상태를 함께 확인해야 합니다.

ESOCKET은 비밀번호 오류인가요?

일반적으로 인증 이전 또는 통신 중의 소켓 계층 문제입니다. 호스트·포트·TLS 조합, 방화벽, 네트워크 연결을 먼저 확인하세요. 상세 오류 없이 ESOCKET만 보고 한 가지 원인으로 단정해서는 안 됩니다.

Gmail 일반 비밀번호를 사용해도 되나요?

이 구성에서는 2단계 인증을 켠 뒤 생성한 16자리 앱 비밀번호를 사용합니다. 일반 계정 비밀번호를 Compose에 넣지 마세요.

465와 587 중 어느 포트를 써야 하나요?

둘 다 사용할 수 있습니다. 465는 secure: "true", 587은 secure: "false"로 STARTTLS를 사용합니다. 두 설정을 섞지 않는 것이 중요합니다.

docker compose restart만 하면 되나요?

아닙니다. Docker 공식 문서에 따르면 Compose 파일에서 바꾼 환경변수는 단순 재시작으로 반영되지 않습니다. docker compose up -d --force-recreate ghost로 Ghost 컨테이너를 재생성하세요.

Gmail SMTP로 Ghost 내장 뉴스레터를 보낼 수 있나요?

현재는 할 수 없습니다. Ghost의 표준 SMTP는 트랜잭션 메일용이고, 셀프 호스팅 내장 뉴스레터 대량 발송은 Mailgun API를 사용합니다. 외부 뉴스레터 서비스를 별도로 연동하는 것은 다른 방식입니다.

함께 보면 좋은 글

참고한 공식 문서

이 글은 실제 운영 경험과 공식 문서를 바탕으로 작성한 개인 기술 기록입니다. Ghost, Google, Mailgun, Docker와 제휴하거나 후원받지 않았으며 각 이름과 상표는 해당 권리자에게 있습니다.

태그