rsync + 하드링크로 일별 스냅샷 백업 구축하기

여러 날짜의 스냅샷 폴더가 변하지 않은 파일을 하드링크로 공유해 실제 데이터는 한 벌만 저장되는 구조

3-2-1 백업 전략에서 “로컬 백업은 자동화 + 버전 보관”이라고 했다. 이번 글은 그 구현이다. 도구는 단 두 가지 — 어디에나 있는 rsync와 파일시스템의 하드링크. 상용 백업 솔루션 없이 “어제의 파일로 돌아갈 수 있는” 30일치 스냅샷을 만든다.

원리: 안 바뀐 파일은 한 벌만 저장된다

매일 전체를 복사하면 30일치 = 원본의 30배 용량이 필요하다. 하드링크 스냅샷은 이를 피하는 고전적 기법이다.

핵심은 rsync의 --link-dest 옵션이다. 오늘 백업을 만들 때 어제 백업과 비교해서:

  • 바뀐 파일 → 새로 복사한다
  • 안 바뀐 파일 → 복사하지 않고 어제 백업의 같은 파일을 가리키는 하드링크를 만든다

하드링크는 “같은 데이터 블록을 가리키는 또 하나의 이름”이라 용량을 거의 차지하지 않는다. 결과적으로 매일의 스냅샷 폴더는 각각 완전한 전체 백업처럼 보이지만, 실제 디스크 사용량은 원본 1벌 + 날마다 바뀐 파일들뿐이다. 또 하나의 장점: 복원할 때 특별한 도구가 필요 없다. 스냅샷 폴더에서 cp로 꺼내면 끝이다.

스크립트

#!/bin/bash
# snapshot-backup.sh — rsync 하드링크 일별 스냅샷
set -euo pipefail

SRC="/home/user/data/"          # 백업 원본 (끝 슬래시 중요)
DST="/mnt/backup/snapshots"     # 백업 디스크의 스냅샷 보관 위치
KEEP=30                         # 보관 일수
TODAY=$(date +%Y-%m-%d)

# 백업 디스크가 실제로 마운트되어 있는지 먼저 확인
mountpoint -q /mnt/backup || { echo "백업 디스크 미마운트 — 중단"; exit 1; }

LATEST=$(ls -1d "$DST"/20* 2>/dev/null | sort | tail -1 || true)

if [ -n "$LATEST" ]; then
  rsync -a --delete --link-dest="$LATEST" "$SRC" "$DST/$TODAY.tmp"
else
  rsync -a --delete "$SRC" "$DST/$TODAY.tmp"   # 첫 실행: 전체 복사
fi

mv "$DST/$TODAY.tmp" "$DST/$TODAY"

# 보관 일수 초과분 삭제
ls -1d "$DST"/20* | sort | head -n -"$KEEP" | xargs -r rm -rf

설계 포인트 세 가지:

  1. 마운트 확인이 첫 줄이다. 백업 디스크가 빠져 있는데 스크립트가 돌면, 내장 디스크의 마운트 지점 폴더에 백업이 쌓여 루트 파티션이 가득 차는 사고가 난다.
  2. .tmp로 만들고 성공 시 이름 변경. 백업 도중 중단되더라도 완성된 것처럼 보이는 깨진 스냅샷이 남지 않는다.
  3. --delete는 원본 기준 동기화를 뜻한다. 원본에서 지운 파일은 오늘 스냅샷에서도 빠지지만, 어제까지의 스냅샷에는 남아 있다 — 이게 스냅샷 방식의 존재 이유다.

cron 등록과 검증

매일 새벽 실행으로 등록한다 (문법 참고: crontab(5) 매뉴얼).

30 3 * * * /home/user/scripts/snapshot-backup.sh >> /home/user/logs/backup.log 2>&1

등록했다고 끝이 아니다. 다음 날 세 가지를 확인한다: 스냅샷 폴더가 생겼는가, 로그에 에러가 없는가, 그리고 df -h로 본 백업 디스크 사용량이 예상 수준인가. 용량 효율은 du -sh 가 아니라 (하드링크 때문에 부풀려 보인다) 디스크 전체 사용량 추이로 본다.

실제 운영값을 공유하면: 내 백업은 매일 1회 cron으로 돌고 보관은 30일, 현재 4TB 백업 디스크의 사용량은 약 120GB 수준이다. 스크립트는 본문과 살짝 다른 변형을 쓰는데, rsync로 latest 폴더를 먼저 갱신한 뒤 cp -al(하드링크 복사)로 그날의 스냅샷을 뜬다 — 구현은 달라도 결과 구조는 같다. 하드링크 공유가 정말 되는지 궁금하면 stat 명령으로 파일의 링크 수를 보면 된다. 내 백업의 설정 파일 하나는 링크 수가 20으로 찍히는데, 스냅샷 20개가 디스크 위의 실체 하나를 함께 가리키고 있다는 뜻이다. 완료 보고는 텔레그램으로 받아서, 로그를 일부러 열어보지 않아도 실패를 그날 안다.

제외 패턴과 이 방식의 한계

백업 대상에서 빼야 할 것들이 있다. 캐시, 임시 파일, 썸네일처럼 재생성 가능한 데이터는 --exclude로 걸러내면 백업 시간과 용량이 크게 줄어든다.

rsync -a --delete --link-dest="$LATEST" \
  --exclude='cache/' --exclude='*.tmp' --exclude='thumbnails/' \
  "$SRC" "$DST/$TODAY.tmp"

한계도 알고 쓰자. 하드링크 방식은 파일 단위로 동작한다. 1GB짜리 파일에서 1바이트만 바뀌어도 그날 스냅샷에는 1GB가 새로 복사된다. 매일 조금씩 바뀌는 대형 파일(가상머신 이미지, 대형 DB 파일)이 많다면 블록 단위 중복 제거를 지원하는 백업 도구나 파일시스템 스냅샷이 더 효율적이다. 또 하드링크는 같은 파일시스템 안에서만 성립하므로, 스냅샷 보관 위치를 도중에 다른 디스크로 옮기면 그 시점부터 용량 공유가 끊긴다는 점도 기억해두자. 반대로 일반 문서·사진·설정 파일 위주의 홈서버 데이터라면 이 한계는 거의 체감되지 않는다 — 단순함이 주는 신뢰가 더 크다.

데이터베이스를 가진 서비스는 한 가지 더: 실행 중인 DB 파일을 그대로 복사하면 일관성이 깨진 백업이 될 수 있다. 컨테이너를 잠시 멈추고 백업하거나, DB의 덤프 명령으로 별도 파일을 만들어 그 덤프를 백업 대상에 포함하는 것이 안전하다.

복원: 이 방식의 가장 큰 장점

복원 절차라고 부르기도 민망하다.

ls /mnt/backup/snapshots/                    # 날짜 목록 확인
cp /mnt/backup/snapshots/2026-05-20/문서.odt ~/복구/   # 그날의 파일을 꺼낸다

전용 복원 도구도, 카탈로그 DB도 없다. 백업 디스크를 아무 리눅스 머신에 꽂아도 읽을 수 있다는 것 — 재해 상황에서 이 단순함은 큰 가치다. 전용 백업 도구들이 제공하는 암호화·압축·중복 제거가 필요해지는 시점이 오면 그때 갈아타면 되고, 그때조차 “스냅샷 = 그날의 폴더”라는 이 구조에서 배운 감각은 그대로 유효하다.

복원과 관련해 한 가지 주의: 스냅샷 폴더 안의 파일을 직접 수정하면 안 된다. 하드링크로 연결된 파일을 제자리에서 수정하면 그 파일을 공유하는 모든 스냅샷이 함께 바뀐다. 꺼낼 때는 반드시 복사(cp)로 꺼내고, 스냅샷 자체는 읽기 전용으로 취급하는 습관이 안전하다.

한 가지 전제를 잊지 말자. 이 백업이 향하는 디스크가 USB 외장 디스크라면, 그 연결 계층의 신뢰성 문제를 알아둬야 한다. 다음 글 USB 외장 디스크의 함정에서 이어진다.