> ## Content Index
> Fetch the complete content index at: https://taektech.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Ghost Admin 로그인 안 될 때 — 기기 인증(2FA) 이메일 설정으로 해결하기
- URL: https://taektech.com/ghost-admin-login-device-verification-smtp-fix/
- Published: 2026-06-19T03:56:11.000Z
- Updated: 2026-06-19T06:57:13.000Z
- Author: Taektech
- Tags: 서버·셀프호스팅, Self-hosting, Ghost CMS, Docker, Troubleshooting, Email/SMTP

Docker로 셀프호스팅 중인 [Ghost](https://ghost.org/?ref=taektech.com) 블로그(`ghost:6-alpine`)에서 관리자(admin) 로그인이 막혀 원인을 추적하고 해결한 과정을 정리합니다.

## 증상

- `/ghost/` 관리자 페이지 접속 시 아래와 같은 에러가 노출됨

```
{
  "errors": [
    {
      "message": "Authorization failed",
      "context": "Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication.",
      "type": "NoPermissionError"
    }
  ]
}

```

- 시크릿 모드로 접속해도 동일한 메시지가 반복됨

## 원인 파악

1. **이 에러 자체는 로그인 실패의 직접적인 원인이 아니었습니다.** 컨테이너 로그(`docker compose logs ghost`)를 실시간으로 확인해보니, 이 메시지는 `/ghost/` 페이지가 로드될 때 "현재 로그인된 사용자가 있는지" 확인하는 백그라운드 API(`GET /ghost/api/admin/users/me/`)가 세션 쿠키 없이 호출되며 발생하는 **정상적인 사전 체크**였습니다.
2. **진짜 원인은 두 가지였습니다.**
  - Ghost 5.118+ 에 추가된 **Staff Device Verification(기기 인증)** 보안 기능 — 새 기기/IP에서 로그인 시 등록된 이메일로 인증 코드를 보내는데, **SMTP(메일 발송) 설정이 없어서** 코드가 발송되지 않아 로그인이 끝까지 진행되지 못함
  - 실제 로그 확인 결과 별도로 `PASSWORD_INCORRECT` (비밀번호 오류)도 함께 있었음 — 즉 기기 인증 이슈와 비밀번호 분실 이슈가 겹쳐 있던 상황

## 해결 단계

### 1\. Gmail SMTP 연동

Gmail로 메일을 발송하려면 일반 비밀번호가 아닌 **앱 비밀번호(App Password)**가 필요합니다 (2단계 인증 활성화 후 발급).

민감 정보는 `docker-compose.yml`에 직접 적지 않고, 같은 디렉토리의 `.env` 파일로 분리해 Compose의 변수 치환(`${VAR}`) 기능을 사용했습니다. `.env`는 `chmod 600`으로 권한을 제한합니다.

`.env` (예시 — 실제 값은 비공개):

```
GHOST_MAIL_USER=your-account@gmail.com
GHOST_MAIL_PASS=xxxxxxxxxxxxxxxx   # Gmail 앱 비밀번호, 공백 제거

```

`docker-compose.yml` 일부:

```
services:
  ghost:
    image: ghost:6-alpine
    restart: unless-stopped
    ports:
      - "2368:2368"
    environment:
      url: https://blog.example.com
      database__client: sqlite3
      database__connection__filename: /var/lib/ghost/content/data/ghost.db
      mail__transport: SMTP
      mail__from: '"My Blog" <your-account@gmail.com>'
      mail__options__service: Gmail
      mail__options__auth__user: ${GHOST_MAIL_USER}
      mail__options__auth__pass: ${GHOST_MAIL_PASS}
    volumes:
      - ./content:/var/lib/ghost/content

```

### 2\. 기기 인증 기능 임시 비활성화로 응급 처치 (선택)

SMTP 설정 전 급하게 로그인이 필요하다면 아래 옵션으로 기능을 잠시 끌 수 있습니다. 다만 **프로덕션/외부 노출 사이트에는 비권장**(보안 계층 약화).

```
environment:
  security__staffDeviceVerification: "false"

```

SMTP 설정이 끝나면 이 줄을 제거해 기본값(`true`, 기능 활성화)으로 되돌립니다.

### 3\. 트러블슈팅: `535-5.7.8 Username and Password not accepted`

Gmail 앱 비밀번호 인증이 거부된 경우 점검 목록:

- 해당 Gmail 계정에 **2단계 인증이 켜져 있는지** (꺼져 있으면 앱 비밀번호 자체가 무효)
- 앱 비밀번호를 **다른 계정용으로 발급받아 잘못 전달**하지 않았는지 (메일 발신 계정과 앱 비밀번호 발급 계정이 일치해야 함)
- 복사 시 공백/오타 여부

### 4\. 비밀번호 재설정

SMTP가 정상화된 후 로그인 화면의 **"Forgot password?"** 로 재설정 메일을 받아 admin 비밀번호를 복구했습니다.

## 정리

| 항목    | 내용                                                  |
| ----- | --------------------------------------------------- |
| 증상    | Admin 로그인 실패, 쿠키/세션 에러 노출                           |
| 1차 원인 | Staff Device Verification 기능에 필요한 SMTP 미설정          |
| 2차 원인 | Admin 비밀번호 분실                                       |
| 해결    | Gmail SMTP(앱 비밀번호) 연동 → 기기 인증 정상화 → 비밀번호 재설정 메일로 복구 |
| 보안 메모 | 자격증명은 docker-compose.yml이 아닌 .env(권한 600)로 분리 보관    |