---
title: "백업 실패 알림: 복원이 필요해지기 전에 알아채기"
description: "백업은 몇 주씩 조용히 실패합니다. restic과 borg 오류, 오래된 스냅샷, 실행 누락을 잡아 백업이 깨지면 전화로 알려주세요. 복사해 쓰는 스크립트 포함."
date: 2026-10-09
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - 백업 모니터링
  - restic
  - borg
  - 실패 알림
  - 전화 알림
  - 웹훅
---

# 백업 실패 알림: 복원이 필요해지기 전에 알아채기

대부분은 백업이 깨져 있었다는 사실을 정작 그 백업이 필요한 날 알게 됩니다. 작업은 3주 전에 오류로 종료됐고, 아무도 로그를 보지 않았으며, 가장 최근 스냅샷은 되돌리려는 실수보다 오래된 것입니다.

해결책은 더 좋은 백업 도구가 아닙니다. 백업 문제가 생긴 바로 그 시간 안에 사람에게 전달되게 만드는 것입니다. 이 가이드는 restic과 borg(그리고 셸에서 실행되는 모든 것)를 대상으로, 백업이 실제로 실패하는 세 가지 방식을 복사해 쓸 수 있는 스크립트로 다룹니다. 마지막 단계에서는 [Echobell](/ko)로 알림을 휴대폰에 보내므로, 깨진 백업이 받은편지함에 묻히지 않고 전화로 울립니다.

## 백업이 실패하는 세 가지 방식

첫 번째만 모니터링하는 것이 가장 흔한 실수입니다.

1. **실행되고 실패한다.** 저장소 잠김, 디스크 가득 참, 비밀번호 오류, 네트워크 끊김. 도구가 0이 아닌 값으로 종료합니다.
2. **실행되고 절반만 성공한다.** restic은 스냅샷은 만들어졌지만 일부 파일을 읽지 못했을 때 종료 코드 `3`을 반환합니다. borg는 경고일 때 `1`을 반환합니다. 둘 다 대수롭지 않게 넘기기 쉽지만, 가장 중요한 디렉터리를 조용히 건너뛰는 권한 문제를 숨기고 있을 수 있습니다.
3. **아예 실행되지 않는다.** 서버가 꺼져 있었거나, 타이머가 비활성화됐거나, crontab이 덮어써졌습니다. 프로세스가 없으니 오류도 없습니다. 사람들에게 가장 치명적인 것이 이 실패입니다.

각각 다른 감지 방법이 필요하므로 세 가지를 모두 만들어 보겠습니다.

## 1단계: 주의를 끌 수 있는 채널 만들기

Echobell에서 `backups`라는 채널을 만들고, 중요한 작업용으로 알림 유형을 **전화(Calling)** 로 설정합니다. 웹훅 URL을 복사합니다.

```
https://hook.echobell.one/t/<channel-token>
```

잠이 덜 깬 상태에서도 이해되는 템플릿을, 아래에서 보낼 필드로 만듭니다.

```
제목: {{host}} 백업 {{status}}
본문: {{job}}: {{detail}}
```

문법은 [템플릿 레퍼런스](/ko/docs/template)를 참고하세요.

## 2단계: 래퍼 스크립트로 실패 잡기

작은 래퍼가 종료 코드를 알림으로 바꿔 줍니다. `/usr/local/bin/backup-run`으로 저장하세요.

```bash
#!/usr/bin/env bash
set -uo pipefail

HOOK="https://hook.echobell.one/t/<channel-token>"
JOB="nightly-restic"

alert() {
  curl -fsS -m 10 -X POST "$HOOK" \
    -H "Content-Type: application/json" \
    -d "{\"job\":\"$JOB\",\"host\":\"$(hostname)\",\"status\":\"$1\",\"detail\":\"$2\"}" || true
}

restic backup /srv /etc --exclude-caches
code=$?

case $code in
  0)  ;;                                   # 성공: 조용히 둔다
  3)  alert "incomplete" "Snapshot saved, but some files were unreadable" ;;
  11) alert "locked" "Repository is locked by another process" ;;
  12) alert "auth-failed" "Wrong repository password" ;;
  *)  alert "failed" "restic exited with code $code" ;;
esac

exit $code
```

세부 사항이 중요한 이유는 다음과 같습니다.

- `set -e`는 의도적으로 쓰지 않았습니다. restic이 실패해도 스크립트가 알림 단계까지 도달하게 하기 위해서입니다.
- `curl`의 `|| true` 덕분에 웹훅에 닿지 못해도 실제 종료 코드가 가려지지 않습니다.
- `-m 10`은 멈춰 버린 네트워크 호출이 다음 예약 실행을 막는 것을 방지합니다.

위에서 쓴 restic 종료 코드는 restic 매뉴얼에 문서화되어 있습니다. `0` 성공, `1` 치명적 오류, `3` 스냅샷은 생성됐지만 일부 소스 파일을 읽지 못함, `10` 저장소가 존재하지 않음, `11` 잠금 실패, `12` 비밀번호 오류. 코드는 시간이 지나며 추가되었으니, 사용 중인 머신의 `restic version`과 대조해 확인하세요.

### borg와 borgmatic

borg는 성공이면 `0`, 경고면 `1`, 오류면 `2` 이상을 반환합니다. 백업 도중 파일이 바뀌면 `1`이 나오는데 로그 디렉터리에서는 일상적인 일이므로, `2` 이상일 때만 전화로 호출하고 `1`은 조용한 알림으로 보내는 방식이 일반적입니다.

```bash
borg create --stats ::'{hostname}-{now}' /srv /etc
code=$?
if [ "$code" -ge 2 ]; then
  alert "failed" "borg exited with code $code"
elif [ "$code" -eq 1 ]; then
  alert "warning" "borg finished with warnings"
fi
```

borgmatic을 쓴다면, borgmatic 자체에서 오류가 발생했을 때 명령을 실행하는 `on_error` 훅이 있으며 같은 `curl`을 넣기에 자연스러운 자리입니다. 다만 borgmatic 문서가 스스로 밝히는 한계에 유의하세요. borgmatic이 아예 실행되지 않으면 `on_error`도 발동하지 않습니다. 그것이 세 번째 실패이며, 3단계가 필요합니다.

## 3단계: 실행되지 않은 백업 잡기

래퍼는 실행된 회차만 보고할 수 있습니다. 침묵을 잡으려면 논리를 뒤집습니다. 백업이 **성공했을 때** 모니터에 핑을 보내고, 핑이 **늦어지면** 모니터가 여러분에게 알립니다. 이것을 데드맨 스위치라고 합니다.

[Healthchecks.io](https://healthchecks.io)(호스팅 또는 셀프 호스팅)가 바로 이 일을 합니다.

1. 스케줄 `0 3 * * *`과 한두 시간의 유예 시간으로 체크를 만듭니다.
2. 작업 끝에 그 핑을 추가해 성공했을 때만 발송되게 합니다.
3. 체크의 인테그레이션에서 Echobell 채널 URL을 가리키는 **Webhook**을 추가합니다.

핑은 래퍼의 맨 끝에 둡니다.

```bash
# backup-run의 마지막 `exit $code` 바로 앞에 둡니다. 성공했을 때만 ping
if [ $code -eq 0 ]; then
  curl -fsS -m 10 --retry 3 -o /dev/null https://hc-ping.com/<your-uuid>
fi
```

Healthchecks는 `/start`와 `/fail` 핑 변형도 지원하고 URL에 종료 코드를 담을 수도 있어서, 하나의 체크로 멈춰 버린 작업까지 잡을 수 있습니다. 그 결과 서버 다운, 비활성화된 타이머, 삭제된 crontab 줄이 모두 같은 결말, 즉 울리는 휴대폰으로 이어집니다. 일반적인 패턴과 다른 모니터는 [크론 작업 실패 알림](/ko/blog/cron-job-failure-alerts)을 참고하세요.

## 4단계: 백업이 최신이고 복원 가능한지 확인하기

백업이 일정대로 돌고 종료 코드 `0`을 반환해도 쓸모없을 수 있습니다. 예를 들어 마운트가 실패해서 빈 디렉터리를 백업하는 경우입니다. 별도의 일일 작업에 값싼 점검 두 가지를 추가하세요.

**스냅샷 나이.** 가장 최근 스냅샷이 예상보다 오래됐다면 알립니다.

```bash
latest=$(restic snapshots --latest 1 --json | jq -r '.[0].time')
age_h=$(( ( $(date +%s) - $(date -d "$latest" +%s) ) / 3600 ))
if [ "$age_h" -gt 30 ]; then
  alert "stale" "Newest snapshot is ${age_h}h old"
fi
```

**저장소 무결성.** `restic check`는 저장소 구조를 검증합니다. 주 1회 실행하고, 큰 저장소라면 `restic check --read-data-subset=5%`로 전부 읽지 않고 데이터를 샘플링할 수 있습니다. 0이 아닌 종료 시 알립니다.

둘 다 실제 복원 테스트를 대체하지는 못합니다. 분기에 한 번, 검증할 수 있는 파일을 임시 디렉터리로 복원해 보세요. 백업이 작동한다는 것을 증명하는 유일한 테스트입니다.

## 5단계: 정말 중요할 때만 울리기

경고마다 전화가 오면 채널을 음소거하게 되고, 결국 정작 중요한 한 건에는 울리지 않습니다. Echobell [조건](/ko/docs/conditions)으로 한 채널 안에서 단계를 나누세요.

```
status == "failed" || status == "stale" || status == "auth-failed"
```

`incomplete`와 `warning`은 **시간 민감(Time Sensitive)** 또는 일반 유형의 두 번째 채널로 보내, 울리지 않고 잠금 화면에만 표시되게 하세요. 낮 시간의 실패는 조용히 두고 싶다면 조건 레퍼런스에 설명된 UTC 변수로 시간대를 추가하면 됩니다.

## 자주 묻는 질문

### 백업이 실패하면 어떻게 알림을 받나요?

백업 명령을 감싸서 종료 코드를 읽고, 0이 아닌 코드가 나오면 웹훅을 보냅니다. Healthchecks.io 같은 데드맨 스위치를 더하면 시작조차 하지 않은 실행도 알 수 있습니다.

### restic의 어떤 종료 코드에서 알려야 하나요?

실제 실패를 뜻하는 `1`, `10`, `11`, `12`는 강하게 알리세요. `3`(스냅샷은 생성됐지만 일부 파일을 읽지 못함)은 권한 문제를 숨길 수 있으므로 조사해야 할 경고로 다룹니다.

### 백업이 조용히 멈췄는지 어떻게 알 수 있나요?

하트비트를 쓰세요. 작업이 성공하면 모니터에 핑을 보내고, 핑이 늦어지면 모니터가 알립니다. 오류를 일으킬 프로세스 자체가 없으므로 오류 훅으로는 잡을 수 없습니다.

### 종료 코드가 성공이면 백업을 믿어도 되나요?

아니요. 도구가 끝까지 실행됐다는 뜻일 뿐, 데이터가 완전하거나 복원 가능하다는 뜻이 아닙니다. 스냅샷 나이를 확인하고, 주기적으로 무결성 검사를 하고, 복원 테스트를 정기적으로 하세요.

### 메일 대신 전화로 받을 수 있나요?

네. 메일은 놓치기 쉽습니다. Echobell의 전화 채널은 수신 전화처럼 동작하고 iOS 집중 모드를 뚫으므로, 새벽 3시의 백업 실패도 알아챌 수 있습니다.

## 정리

세 가지 감지기가 세 가지 실패 유형을 덮습니다. 실패한 실행에는 종료 코드 래퍼, 쓸모없이 성공한 실행에는 최신성 점검, 아예 일어나지 않은 실행에는 하트비트. 이들을 전화 채널로 보내고 경고는 조용한 채널에 두면, 깨진 백업은 주말을 날리는 사고가 아니라 5분이면 고칠 일이 됩니다.

[iPhone용 Echobell 다운로드](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-backup-failure-alerts-ko&mt=8) 또는 [Google Play에서 받기](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid) 후 `backups` 채널을 만들고, 일부러 비밀번호를 틀리게 하여 래퍼를 한 번 실행해 실제로 울리는지 확인해 보세요.

## 관련 글

- [크론 작업 실패 알림](/ko/blog/cron-job-failure-alerts)
- [개발자를 위한 알림 피로 해소 가이드](/ko/blog/fix-alert-fatigue-developer-guide)
- [채널 조건 레퍼런스](/ko/docs/conditions)
- [웹훅 연동 가이드](/ko/docs/webhook)
- [SSL 인증서 만료 알림](/ko/blog/ssl-certificate-expiry-alerts)
