infra
Platform

모듈 맵

[Linux] tar/rsync를 이용한 완벽한 서버 데이터 백업과 증분 백업 가이드

0 / 37 완료

펼치기
0 / 37 완료0%

리눅스 서버 운영 · 30 / 37

[Linux] tar/rsync를 이용한 완벽한 서버 데이터 백업과 증분 백업 가이드

rsync, tar, 증분 백업 — 랜섬웨어와 디스크 장애에서 데이터를 지키는 체계

🚨INCIDENT ALERT
HIGH

새벽에 개발자가 rm -rf /var/www/html 을 프로덕션 서버에서 잘못 실행했습니다. 백업이 있었지만 마지막 백업이 2주 전이었고, 그 사이 추가된 사용자 데이터는 복구 불가능했습니다. 다른 팀에서는 랜섬웨어에 감염되어 백업까지 같은 서버에 있었기 때문에 모두 암호화됐습니다. 체계적인 3-2-1 백업 전략과 자동화된 rsync 스크립트가 있었다면, 재앙이 아닌 단순한 복구 작업으로 끝났을 겁니다.

서버 백업 & 데이터 보호

데이터 손실은 "만약에"가 아니라 "언제"의 문제입니다. 디스크는 반드시 죽고, 랜섬웨어는 예고 없이 찾아오며, 실수로 rm -rf를 잘못 입력하는 순간은 누구에게나 옵니다. 체계적인 백업은 그 순간을 재앙이 아닌 단순한 복구 작업으로 바꾸어 줍니다.

이 챕터에서는 tarrsync를 중심으로 실전에서 쓰이는 백업 전략을 처음부터 끝까지 구현합니다. 개념만 배우는 것이 아니라, 실제 운영 서버에 바로 배포할 수 있는 자동화 스크립트를 만드는 것이 목표입니다.


1. 백업 전략: 어떤 방식을 선택할 것인가

이번 챕터에서 배울 것
  • 1풀·증분·차등 백업 전략을 비교하고 상황별 선택 기준을 적용할 수 있다
  • 23-2-1 백업 원칙(3개 사본, 2가지 미디어, 1개 오프사이트)을 설명할 수 있다
  • 3tar로 아카이브를 생성·압축·해제하고 --exclude 고급 옵션을 활용할 수 있다
  • 4rsync로 로컬·원격을 동기화하고 SSH로 암호화 전송할 수 있다
  • 5하드링크 기반 증분 백업으로 저장 공간을 절약할 수 있다
  • 6cron 자동화 스크립트를 작성하고 오래된 백업 보존 정책을 구현할 수 있다
실습 환경 준비
rsync 설치 확인
rsync --version
백업 대상 디렉토리 용량 확인
du -sh /var/www /home /etc /opt 2>/dev/null
백업 저장 경로 생성
mkdir -p /backup/{daily,weekly,monthly}
cron 데몬 상태 확인
systemctl status crond 2>/dev/null || systemctl status cron
💡개념

풀 백업 vs 증분 백업 vs 차등 백업

백업 전략 비교와 3-2-1 원칙 — 풀(매번 전체, 복구 단순·저장 큼)·증분(직전 대비 변경분, 저장 효율·복구 시 풀+모든 증분 필요)·차등(마지막 풀 대비 변경분, 절충). 3-2-1은 사본 3개·매체 2종·오프사이트 1개로 단일 장애점에도 복구 보장 — 복구 속도와 저장 비용의 트레이드오프로 조합확대

매일 밤 서버 전체를 풀 백업하면 200GB씩 쌓입니다. 일주일이면 1.4TB, 한 달이면 6TB가 됩니다. 비용과 시간 때문에 현실적으로 이렇게 할 수 없습니다. 그렇다고 매일 변경분만 백업하면 복구할 때 여러 날치를 순서대로 합쳐야 해서 복구 시간이 길어집니다. 백업 전략은 "저장 비용, 백업 소요 시간, 복구 소요 시간" 세 가지가 트레이드오프 관계에 있고, 서비스의 RPO/RTO 요구사항에 맞춰 선택해야 합니다.

백업에는 세 가지 기본 전략이 있으며, 각각 장단점이 다릅니다.

풀 백업 (Full Backup)

전체 데이터를 매번 새로 복사하는 방식입니다.

  • 장점: 복구가 단순하고 빠름. 백업 파일 하나면 복구 완료
  • 단점: 저장 공간을 가장 많이 차지함. 백업 시간도 가장 오래 걸림
  • 적합한 상황: 데이터 양이 적거나, 주간 기준 백업
일요일: [전체 100GB 백업]
월요일: [전체 100GB 백업]  ← 중복 데이터가 대부분
화요일: [전체 100GB 백업]  ← 비효율적

증분 백업 (Incremental Backup)

이전 백업(풀 또는 증분) 이후 변경된 파일만 백업합니다.

  • 장점: 저장 공간 최소화, 백업 시간 단축
  • 단점: 복구 시 풀 백업 + 모든 증분 백업을 순서대로 적용해야 함. 복구 과정이 복잡
  • 적합한 상황: 매일 또는 매시간 단위 백업, 대용량 데이터
일요일: [풀 백업 100GB]
월요일: [증분: 변경된 5GB만]
화요일: [증분: 변경된 3GB만]
복구 시: 일요일 풀 + 월요일 증분 + 화요일 증분 순서 적용

차등 백업 (Differential Backup)

마지막 풀 백업 이후 변경된 모든 파일을 백업합니다.

  • 장점: 복구 시 풀 백업 + 최신 차등 백업 두 개만 있으면 됨
  • 단점: 증분 백업보다 저장 공간을 더 사용함 (누적됨)
  • 적합한 상황: 복구 속도와 저장 효율 사이의 균형이 필요할 때
일요일: [풀 백업 100GB]
월요일: [차등: 일요일 이후 변경 5GB]
화요일: [차등: 일요일 이후 변경 8GB]  ← 월요일 변경분 포함
복구 시: 일요일 풀 + 화요일 차등 (두 개만 필요)

실무에서의 선택

대부분의 운영 환경에서는 혼합 전략을 사용합니다:

  • 주 1회 풀 백업 (일요일 새벽 2시)
  • 매일 증분 백업 (평일 새벽 2시)
  • 특정 중요 설정 파일은 시간별 백업

이 챕터에서는 rsync --link-dest를 활용하여 스토리지 효율은 증분 백업 수준이면서 복구는 풀 백업처럼 단순한 방식을 구현합니다.

💡개념

3-2-1 백업 원칙

3-2-1 백업 원칙 — 3개 복사본·2종 매체·1개 오프사이트확대

랜섬웨어 공격을 받은 서버를 복구하려 했더니 백업 서버가 같은 내부 네트워크에 있어서 함께 암호화된 사례가 있습니다. 로컬 디스크 하나에만 백업해두다가 서버가 물리적으로 손상되어 백업도 같이 날아가는 경우도 있습니다. "백업이 있다"고 생각했지만 실제로는 모두 같은 위험에 노출된 구조입니다. 3-2-1 원칙은 이런 상황을 막기 위한 최소 기준으로, 현재 백업 구성이 이 기준을 충족하는지 점검하는 체크리스트입니다.

3-2-1 규칙은 데이터 보호의 황금률입니다. 랜섬웨어, 화재, 도난 등 어떤 상황에서도 살아남을 수 있도록 설계된 원칙입니다.

3-2-1 규칙이란

  • 3: 데이터 사본을 3개 유지 (원본 1개 + 백업 2개)
  • 2: 2가지 서로 다른 미디어/저장 매체에 저장
  • 1: 1개는 반드시 외부(오프사이트)에 보관

실제 구현 예시

[3개의 사본]
사본 1: 운영 서버 (원본)
사본 2: 같은 데이터센터의 백업 서버
사본 3: 클라우드 (AWS S3, Backblaze B2, 외부 NAS)

[2가지 다른 미디어]
로컬: 서버 내부 SSD + 외장 HDD
또는: 온프레미스 서버 + 클라우드 스토리지

[1개는 오프사이트]
화재가 났을 때 같은 건물에 있는 백업은 무의미
클라우드 또는 물리적으로 다른 위치에 1개 보관

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part5/exam_6/{source,backups,restore}
cd /tmp/linux/part5/exam_6

# 백업 대상 예제 파일 생성
echo "DB_HOST=prod-db.internal" > source/app.conf
echo "APP_PORT=8080" >> source/app.conf
cp /etc/hosts source/hosts.bak 2>/dev/null || true
echo "user: admin, role: superuser" > source/users.txt
for i in 1 2 3; do
  echo "$(date): Log entry $i" >> source/app.log
done
ls -la source/

이제 실습을 진행합니다.

랜섬웨어 방어를 위한 추가 원칙

랜섬웨어는 네트워크로 연결된 모든 드라이브를 암호화할 수 있습니다. 이를 방어하려면:

로컬 터미널
# 백업 완료 후 즉시 마운트 해제 (연결 차단)
umount /mnt/backup-drive

# 또는 백업 서버에서 풀(pull) 방식으로 백업
# 운영 서버가 백업 서버에 접근하는 것이 아니라
# 백업 서버가 운영 서버에서 데이터를 가져오는 방식

# S3 버킷에 버전 관리 활성화 (랜섬웨어 감염 이전 버전 복구 가능)
aws s3api put-bucket-versioning \
  --bucket my-backup-bucket \
  --versioning-configuration Status=Enabled

3-2-1 원칙을 따르면 어떤 단일 장애점(Single Point of Failure)이 발생해도 데이터를 복구할 수 있습니다.

3-2-1 백업 원칙 — 데이터 사본 3개, 서로 다른 저장 매체 2종, 그중 1개는 오프사이트(원격). 같은 서버·같은 디스크에만 백업하면 그 디스크 장애 시 원본과 백업이 함께 소멸하므로 매체 분리와 원격 보관이 핵심확대

💡개념

백업이 만들어지고 복구되는 전 과정 — 결정부터 역순 복구까지

매일 백업이 "성공"으로 찍히는데 막상 복구가 안 되는 사고가 가장 무섭습니다. 백업은 파일을 한 번 복사하는 동작이 아니라, 대상 선정부터 복구 검증까지 이어진 파이프라인이고, 단계마다 조용히 깨지는 지점이 따로 있습니다. 이 전체 흐름을 그려 보면 "백업이 생성됨"과 "백업으로 복구됨"이 어디에서 갈라지는지 보입니다.

TEXT
[백업 설계]
   │
   ① 대상·주기 결정            (무엇을 / RPO에 맞춰 얼마나 자주)
   │
   ② 방식 선택                 (풀 / 증분 / 차등 — 복구속도 ↔ 저장비용)
   │
   ③ 스냅샷·복사               (실행 중이면 순간을 얼려 정합성 확보)
   │
   ④ 검증                       (아카이브 무결성·체크섬 — 실제로 풀리는지)
   │
   ⑤ 원격 보관                 (3-2-1: 다른 매체·오프사이트 1곳)
   │
 ─ ─ ─ ─ ─ (장애 발생) ─ ─ ─ ─ ─
   │
   ⑥ 복구: 역순 적용           (풀 → 증분 체인을 순서대로)
   │
   ⑦ 복구 검증                 (데이터 정합·서비스 기동 — RTO 실측)
   ▼
[복구 완료]  RTO 안에 RPO 시점으로 되돌림

각 단계에서 하는 일과, 여기서 깨지면 나타나는 증상:

단계하는 일여기서 깨지면 증상
① 대상·주기백업 범위와 빈도 결정주기가 길면 마지막 백업~장애 사이 데이터가 소실(RPO 초과)
② 방식 선택풀·증분·차등 중 선택증분만 길게 쌓으면 복구가 풀+증분 N개 순차라 느리고(RTO 악화), 체인 중 하나만 손상돼도 그 뒤 전부 복구 불가
③ 스냅샷·복사데이터를 아카이브·동기화살아있는 DB를 그대로 tar하면 파일마다 다른 시점이 섞여 정합성이 깨짐(아카이브는 멀쩡히 풀려도 DB 손상)
④ 검증무결성 확인(gzip -t·체크섬)검증을 건너뛰면 잘린·손상 아카이브를 "성공"으로 착각 — 미검증 백업
⑤ 원격 보관매체 분리·오프사이트 사본같은 서버·디스크에만 두면 그 디스크 장애·랜섬웨어에 원본과 함께 소멸
⑥ 역순 복구풀 → 증분 순서대로 적용순서가 틀리거나 체인이 끊기면 부분 복구·실패, 하드링크 스냅샷은 참조 원본이 지워지면 깨짐
⑦ 복구 검증격리 환경에 실제 복구·기동리허설을 안 하면 진짜 장애 때 절차 누락·권한 문제로 복구 실패

즉 신뢰할 수 있는 백업은 이 파이프라인 전체가 참일 때 성립합니다 — 주기가 RPO를 지키고(①), 실행 중 데이터는 스냅샷으로 순간을 얼리고(③), 아카이브가 실제로 풀리고(④), 사본이 물리적으로 분리돼 있고(⑤), 정기 복구 리허설로 RTO·RPO를 실측한다(⑦). "백업 성공" 로그 한 줄이 아니라 격리 환경에 실제로 복구해 보는 것만이 이 사슬이 끊기지 않았음을 증명합니다.

백업 전략 판단 — 무엇을 어떤 방식·주기로, 복구는 얼마나 걸리나
풀 vs 증분 vs 차등 선택복구 단순·속도 우선=풀(매번 전체, 저장공간 큼), 저장 효율=증분(직전 대비 변경분, 복구 시 풀+모든 증분 필요), 절충=차등(마지막 풀 대비 변경분). '복구 속도 ↔ 저장 비용'의 트레이드오프
복구 시간(RTO) 추정풀 1개 복구 ≈ 백업 크기 ÷ 복원 처리량. 증분 체인은 풀+증분 N개를 순서대로 → N이 클수록 복구 느림. '증분이 길면 복구가 느리다 — 주기적 풀로 체인 끊기'
백업 주기를 얼마로허용 데이터 손실(RPO)이 기준 — 일 1회면 최대 24h 손실. 변경 잦은 DB는 자주(+WAL/PITR), 정적 자산은 드물게. '얼마까지 잃어도 되나'로 주기 결정
rsync --link-dest(하드링크 증분)변경 안 된 파일은 하드링크로 공간 절약하면서 각 스냅샷이 '풀처럼' 보임 → 복구 단순 + 저장 효율. '증분의 저장 효율 + 풀의 복구 단순'
백업이 정말 되는지주기적 복구 리허설 필수 — 백업 존재 ≠ 복구 가능. 압축 무결성(gzip -t)·격리 복구·데이터 검증. '검증 안 한 백업은 백업이 아니다'
3-2-1 어디까지사본 3개·매체 2종·오프사이트 1개. 같은 서버·같은 디스크 백업은 그 디스크 죽으면 같이 소멸 → 최소 다른 매체·원격 1곳. '한 바구니에 담지 말 것'

RTO와 RPO 타임라인 — 장애 시점을 기준으로 RPO(복구 목표 시점)는 마지막 백업까지 거슬러 허용하는 데이터 손실 폭(백업 주기가 결정), RTO(복구 목표 시간)는 장애부터 서비스 정상화까지 허용 시간(복구 절차·백업 방식이 결정). 둘을 함께 정해 백업 주기와 복구 전략을 설계확대


2. tar: 아카이브와 압축의 기본

tar(Tape ARchive)는 Linux에서 가장 오래되고 보편적인 백업 도구입니다. 여러 파일과 디렉토리를 하나의 아카이브 파일로 묶고 압축합니다.

tar 기본 사용법: 생성, 해제, 목록 확인

아카이브 생성: -czf

로컬 터미널
# 기본 형식
tar -czf 출력파일.tar.gz 백업할_디렉토리/

# /etc 디렉토리 전체를 압축
tar -czf /backup/etc-backup.tar.gz /etc/

# 옵션 설명
# -c : create (새 아카이브 생성)
# -z : gzip 압축 적용
# -f : 파일 이름 지정 (다음 인수가 파일명)
# -v : verbose (처리 중인 파일 목록 출력)

압축 형식별 비교:

로컬 터미널
# gzip 압축 (.tar.gz 또는 .tgz) — 가장 보편적
tar -czf backup.tar.gz /data/

# bzip2 압축 (.tar.bz2) — gzip보다 압축률 높음, 느림
tar -cjf backup.tar.bz2 /data/

# xz 압축 (.tar.xz) — 최고 압축률, 가장 느림
tar -cJf backup.tar.xz /data/

# 압축 없이 묶기만 (.tar) — 빠름, 크기 큼
tar -cf backup.tar /data/

아카이브 해제: -xzf

로컬 터미널
# 현재 디렉토리에 해제
tar -xzf backup.tar.gz

# 특정 디렉토리에 해제
tar -xzf backup.tar.gz -C /restore/

# 특정 파일만 선택적으로 해제
tar -xzf backup.tar.gz etc/nginx/nginx.conf

# -C 옵션: 해제할 디렉토리 지정 (Change directory)
# -x : extract (아카이브에서 파일 추출)

내용 확인: -tzf (압축 해제 없이 목록만 보기)

로컬 터미널
# 아카이브 내 파일 목록 확인
tar -tzf backup.tar.gz

# 상세 정보와 함께 목록 확인 (권한, 소유자, 크기 포함)
tar -tzvf backup.tar.gz

# 특정 패턴의 파일만 확인
tar -tzf backup.tar.gz | grep "\.conf$"

# 아카이브 크기 확인 (실제 압축 해제 시 크기)
tar -tzf backup.tar.gz | awk '{sum += $3} END {print sum/1024/1024 " MB"}'
🔍실행 후 확인할 것
  • 먼저 rsync --dry-run -av 로 변경된 파일 목록과 총 건수를 확인하고, 그 다음 실제 rsync 실행 후 전송 속도(MB/s)와 예상 완료 시간을 확인한다
  • tar -czf 실행 후 ls -lh 에서 압축 파일이 원본 대비 30% 이상 작아야 정상 — 압축률이 10% 미만이면 이미 압축된 파일(jpg, mp4 등)이 대부분임을 의미
  • tar -xzf 해제 후 diff -rq 원본경로 해제경로 에서 출력이 없어야 복구 검증 완료 — 차이가 있으면 아카이브 생성 중 파일이 변경된 것
  • 증분 백업에서 rsync --dry-run 변경 파일 수가 0이고 이전 실행과 동일한 상태라면 → 파일 타임스탬프가 갱신됐는지, 또는 대상 디렉토리가 올바른지 재확인
tar 고급: --exclude와 날짜 포함 파일명 자동 생성

--exclude 옵션으로 불필요한 파일 제외

로컬 터미널
# 특정 디렉토리 제외
tar -czf backup.tar.gz /var/www/ \
  --exclude=/var/www/html/cache \
  --exclude=/var/www/html/tmp

# 특정 패턴 파일 제외 (와일드카드)
tar -czf backup.tar.gz /home/user/ \
  --exclude="*.log" \
  --exclude="*.tmp" \
  --exclude=".git"

# 여러 제외 패턴을 파일로 관리
cat > /etc/backup-exclude.txt << 'EOF'
*.log
*.tmp
*.cache
/var/www/html/cache/*
/var/www/html/uploads/tmp/*
node_modules/
.git/
__pycache__/
*.pyc
EOF

tar -czf backup.tar.gz /var/www/ \
  --exclude-from=/etc/backup-exclude.txt

# --exclude-vcs: git, svn 등 버전관리 디렉토리 자동 제외
tar -czf backup.tar.gz /home/user/project/ \
  --exclude-vcs

날짜 포함 파일명 자동 생성 패턴

실무에서는 백업 파일에 날짜와 시간을 포함시켜 관리합니다:

로컬 터미널
# 기본 날짜 패턴 (YYYY-MM-DD)
DATE=$(date +%Y-%m-%d)
tar -czf /backup/etc-${DATE}.tar.gz /etc/
# 결과: /backup/etc-2026-03-26.tar.gz

# 시간까지 포함 (YYYY-MM-DD_HH-MM-SS)
DATETIME=$(date +%Y-%m-%d_%H-%M-%S)
tar -czf /backup/www-${DATETIME}.tar.gz /var/www/
# 결과: /backup/www-2026-03-26_02-30-00.tar.gz

# 호스트명 포함 (다중 서버 백업 관리 시 유용)
HOSTNAME=$(hostname -s)
DATE=$(date +%Y-%m-%d)
tar -czf /backup/${HOSTNAME}-etc-${DATE}.tar.gz /etc/
# 결과: /backup/web01-etc-2026-03-26.tar.gz

# 백업 후 파일 크기 확인
BACKUP_FILE="/backup/etc-${DATE}.tar.gz"
tar -czf "${BACKUP_FILE}" /etc/ 2>/dev/null
echo "백업 완료: ${BACKUP_FILE} ($(du -sh ${BACKUP_FILE} | cut -f1))"

백업 무결성 검증

로컬 터미널
# 아카이브 무결성 테스트 (실제 해제 없이 오류 확인)
tar -tzf /backup/etc-2026-03-26.tar.gz > /dev/null && \
  echo "백업 무결성 OK" || echo "백업 파일 손상됨!"

# MD5 체크섬으로 전송 후 무결성 확인
md5sum /backup/etc-2026-03-26.tar.gz > /backup/etc-2026-03-26.tar.gz.md5
# 나중에 검증 시:
md5sum -c /backup/etc-2026-03-26.tar.gz.md5

3. rsync: 효율적인 동기화와 원격 백업

rsync는 단순한 복사 도구가 아닙니다. 변경된 부분만 전송하는 델타 알고리즘을 사용하여 대역폭과 시간을 절약합니다.

rsync 기본 옵션 마스터하기

핵심 옵션 조합: -avz

로컬 터미널
# 기본 로컬 동기화
rsync -avz /source/ /destination/

# 옵션 설명
# -a : archive 모드 (권한, 타임스탬프, 심볼릭링크, 소유자 등 모두 보존)
#      -rlptgoD 의 단축형
# -v : verbose (상세 출력)
# -z : 전송 시 압축 (네트워크 전송 시 유용, 로컬에서는 오히려 느릴 수 있음)

# -a 가 포함하는 것들:
# -r : 재귀적으로 디렉토리 처리
# -l : 심볼릭 링크 보존
# -p : 파일 권한(permission) 보존
# -t : 타임스탬프 보존
# -g : 그룹 소유권 보존
# -o : 파일 소유자 보존 (root 권한 필요)
# -D : 디바이스 파일과 특수 파일 보존

--delete: 원본에서 삭제된 파일 동기화

로컬 터미널
# --delete 없이: 원본에서 삭제해도 목적지에 남아있음
rsync -avz /source/ /destination/

# --delete: 원본에 없는 파일은 목적지에서도 삭제 (진정한 동기화)
rsync -avz --delete /source/ /destination/

# 주의: 실수로 원본을 지웠다면 목적지에서도 삭제됨
# 사용 전 반드시 --dry-run으로 확인

--progress: 전송 진행 상황 표시

로컬 터미널
# 각 파일의 전송 진행률 표시
rsync -avz --progress /source/ /destination/

# 전체 전송 통계만 보기 (--info=progress2)
rsync -avz --info=progress2 /source/ /destination/

# 출력 예시:
#     1,234,567  45%   12.34MB/s    0:00:08 (xfr#5, to-chk=42/50)
# [전송 바이트] [%] [속도]       [남은시간] [파일번호/전체]

--dry-run: 실제 실행 전 미리 확인

로컬 터미널
# 실제로 아무것도 변경하지 않고 무엇이 바뀔지 시뮬레이션
rsync -avz --dry-run --delete /source/ /destination/

# 짧은 형태: -n
rsync -avzn --delete /source/ /destination/

# dry-run 결과 예시:
# sending incremental file list
# deleting old-file.txt       ← 삭제될 파일
# new-file.txt                ← 새로 추가될 파일
# modified-file.conf          ← 변경된 파일
# sent 1,234 bytes  received 456 bytes

# 실전 워크플로우: 항상 dry-run 먼저, 확인 후 실제 실행
rsync -avz --dry-run --delete /var/www/ /backup/www/ && \
  read -p "위 내용이 맞습니까? [y/N] " confirm && \
  [ "$confirm" = "y" ] && \
  rsync -avz --delete /var/www/ /backup/www/

중요한 경로 슬래시 주의사항

로컬 터미널
# /source/ (슬래시 있음): source 디렉토리의 내용을 복사
rsync -avz /source/ /destination/
# /destination/ 안에 파일들이 직접 위치

# /source (슬래시 없음): source 디렉토리 자체를 복사
rsync -avz /source /destination/
# /destination/source/ 디렉토리가 생성됨
rsync over SSH: 원격 서버 동기화

SSH를 통한 원격 백업

로컬 터미널
# 기본 원격 동기화 (로컬 → 원격)
rsync -avz -e ssh /local/data/ user@remote-server:/remote/backup/

# -e ssh : SSH를 전송 프로토콜로 사용
# 또는 더 명시적으로:
rsync -avz -e "ssh" /local/data/ user@remote-server:/remote/backup/

# SSH 포트가 기본(22)이 아닌 경우
rsync -avz -e "ssh -p 2222" /local/data/ user@192.168.1.100:/backup/

# SSH 키 파일 명시 (자동화 스크립트에서 필수)
rsync -avz -e "ssh -i /root/.ssh/backup_key -p 22" \
  /local/data/ backup@192.168.1.100:/backup/

# 원격 → 로컬 (원격에서 풀링)
rsync -avz -e ssh user@remote-server:/remote/data/ /local/backup/

원격 백업 실전 옵션 조합

로컬 터미널
# 운영 서버에서 백업 서버로 전체 동기화
rsync -avz \
  --delete \
  --progress \
  --exclude="*.log" \
  --exclude="*.tmp" \
  --exclude="cache/" \
  -e "ssh -i /root/.ssh/backup_key" \
  /var/www/ \
  backup@192.168.1.200:/backup/web/

# 백업 완료 후 원격 서버에서 압축 (선택사항)
# SSH 원격 명령 실행
ssh -i /root/.ssh/backup_key backup@192.168.1.200 \
  "tar -czf /backup/web-$(date +%Y-%m-%d).tar.gz /backup/web/"

scp로 단일 파일 전송

rsync가 디렉토리 동기화에 특화되었다면, scp는 단일 파일 전송에 간편합니다:

SSH 접속 후
# 로컬 → 원격 단일 파일 전송
scp /etc/nginx/nginx.conf user@remote-server:/tmp/

# 원격 → 로컬 다운로드
scp user@remote-server:/var/log/app.log /tmp/

# 디렉토리 전체 전송 (-r 옵션)
scp -r /etc/nginx/ user@remote-server:/backup/nginx/

# 특정 포트 지정 (-P 대문자)
scp -P 2222 /backup/db.sql user@remote-server:/restore/

# 여러 파일을 한 번에 전송
scp /etc/hosts /etc/fstab user@remote-server:/backup/etc/

# 전송 진행 상황 표시 (-v)
scp -v /backup/large-file.tar.gz user@remote-server:/backup/

4. 증분 백업 구현: rsync --link-dest

하드링크 기반 증분 백업 구현

rsync --link-dest는 증분 백업의 효율성과 풀 백업의 단순성을 동시에 제공합니다. 변경된 파일만 실제로 복사하고, 변경되지 않은 파일은 이전 백업과 하드링크로 연결합니다.

하드링크 기반 백업의 원리

하드링크 기반 증분 백업 — 화요일 백업은 변경 없는 file-a/file-b를 하드링크로 공유(추가 공간 0)하고 변경된 file-c만 새로 복사, 그래도 완전한 스냅샷처럼 보임확대

--link-dest 구현

로컬 터미널
#!/bin/bash
# 하드링크 기반 증분 백업 스크립트

SOURCE="/var/www/"
BACKUP_DIR="/backup/snapshots"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
CURRENT="${BACKUP_DIR}/${DATE}"
LATEST="${BACKUP_DIR}/latest"

# 이전 백업이 있으면 하드링크 기반으로, 없으면 풀 백업
if [ -d "${LATEST}" ]; then
  rsync -avz \
    --delete \
    --link-dest="${LATEST}" \
    /var/www/ \
    "${CURRENT}/"
  echo "증분 백업 완료: ${CURRENT}"
else
  rsync -avz \
    --delete \
    /var/www/ \
    "${CURRENT}/"
  echo "최초 풀 백업 완료: ${CURRENT}"
fi

# latest 심볼릭 링크 업데이트
rm -f "${LATEST}"
ln -s "${CURRENT}" "${LATEST}"

# 공간 확인
echo "전체 백업 공간: $(du -sh ${BACKUP_DIR} | cut -f1)"
echo "최신 스냅샷: $(du -sh ${CURRENT} | cut -f1)"

실제 공간 절약 확인

로컬 터미널
# 각 스냅샷이 실제로 사용하는 공간 확인
# du -sh 는 하드링크 공유 공간을 중복 계산할 수 있음
# du -sh --separate-dirs 또는 각각 확인

ls -la /backup/snapshots/
# drwxr-xr-x  2026-03-23_02-00-00
# drwxr-xr-x  2026-03-24_02-00-00
# drwxr-xr-x  2026-03-25_02-00-00
# lrwxrwxrwx  latest -> 2026-03-25_02-00-00

# 전체 실제 사용량
du -sh /backup/snapshots/
# 105M  /backup/snapshots/   ← 3일치인데 105M (풀 백업 100M + 증분 5M)

5. 백업 서버 완전 자동화

cron + rsync 전체 백업 자동화 스크립트

백업 우선순위: 무엇을 먼저 백업할 것인가

서버에서 가장 먼저 백업해야 할 파일들:

로컬 터미널
# 1순위: 시스템 설정 파일
/etc/hosts          # DNS 매핑
/etc/crontab        # 예약 작업
/etc/fstab          # 파일시스템 마운트
/etc/passwd         # 사용자 계정
/etc/sudoers        # sudo 권한
/etc/ssh/           # SSH 서버 설정 및 키
/etc/nginx/         # Nginx 설정
/etc/apache2/       # Apache 설정 (해당하는 경우)
/etc/mysql/         # MySQL 설정

# 2순위: 애플리케이션 데이터
/var/www/           # 웹 서버 파일
/home/              # 사용자 홈 디렉토리
/opt/               # 서드파티 소프트웨어

# 3순위: 데이터베이스
# DB는 파일 직접 복사가 아닌 전용 덤프 도구 사용
mysqldump --all-databases > /backup/db-$(date +%Y-%m-%d).sql
pg_dumpall > /backup/postgres-$(date +%Y-%m-%d).sql

완전한 자동화 백업 스크립트

로컬 터미널
#!/bin/bash
# /usr/local/bin/backup.sh
# 서버 자동 백업 스크립트 — cron으로 매일 새벽 2시 실행

set -euo pipefail  # 오류 시 즉시 종료, 미정의 변수 오류, 파이프 오류 감지

# ============ 설정 섹션 ============
BACKUP_SERVER="backup@192.168.1.200"
BACKUP_KEY="/root/.ssh/backup_key"
REMOTE_BASE="/backup/$(hostname -s)"
LOCAL_TEMP="/tmp/backup-staging"
LOG_FILE="/var/log/backup.log"
NOTIFY_EMAIL="admin@example.com"
RETENTION_DAYS=30

# ============ 함수 정의 ============
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}

send_alert() {
  echo "$1" | mail -s "[백업 실패] $(hostname -s) $(date +%Y-%m-%d)" \
    "${NOTIFY_EMAIL}" 2>/dev/null || true
}

check_disk_space() {
  local path="$1"
  local min_gb="$2"
  local available_kb
  available_kb=$(df "${path}" | awk 'NR==2 {print $4}')
  local available_gb=$((available_kb / 1024 / 1024))
  if [ "${available_gb}" -lt "${min_gb}" ]; then
    log "경고: ${path} 여유 공간 부족 (${available_gb}GB < ${min_gb}GB 필요)"
    return 1
  fi
  return 0
}

# ============ 실행 시작 ============
DATE=$(date +%Y-%m-%d_%H-%M-%S)
log "===== 백업 시작: ${DATE} ====="

# 최소 여유 공간 확인 (5GB 이상 필요)
if ! check_disk_space "/tmp" 5; then
  send_alert "백업 실패: /tmp 공간 부족"
  exit 1
fi

# 임시 스테이징 디렉토리 생성
mkdir -p "${LOCAL_TEMP}"

# ============ 1단계: 시스템 설정 파일 tar 백업 ============
log "1단계: 시스템 설정 파일 백업 시작"
CONFIG_BACKUP="${LOCAL_TEMP}/configs-${DATE}.tar.gz"

tar -czf "${CONFIG_BACKUP}" \
  --exclude="/etc/ssl/private" \
  /etc/hosts \
  /etc/fstab \
  /etc/crontab \
  /etc/cron.d/ \
  /etc/ssh/sshd_config \
  /etc/nginx/ \
  /etc/passwd \
  /etc/group \
  /etc/sudoers \
  2>/dev/null || {
    log "경고: 일부 설정 파일 백업 실패 (권한 문제일 수 있음)"
  }

log "설정 파일 백업 완료: $(du -sh ${CONFIG_BACKUP} | cut -f1)"

# ============ 2단계: 데이터베이스 덤프 ============
log "2단계: 데이터베이스 백업 시작"
if command -v mysqldump &>/dev/null; then
  DB_BACKUP="${LOCAL_TEMP}/mysql-${DATE}.sql.gz"
  mysqldump --defaults-file=/root/.my.cnf \
    --all-databases \
    --single-transaction \
    --routines \
    --triggers \
    2>/dev/null | gzip > "${DB_BACKUP}" || {
      log "경고: MySQL 백업 실패"
    }
  log "MySQL 백업 완료: $(du -sh ${DB_BACKUP} | cut -f1)"
fi

# ============ 3단계: 웹 파일 rsync 동기화 ============
log "3단계: 웹 파일 원격 동기화 시작"
LATEST_LINK="${REMOTE_BASE}/latest"

# SSH 연결 테스트
if ! ssh -i "${BACKUP_KEY}" -o ConnectTimeout=10 \
     -o BatchMode=yes "${BACKUP_SERVER}" "echo ok" &>/dev/null; then
  log "오류: 백업 서버 SSH 연결 실패"
  send_alert "백업 서버 SSH 연결 실패"
  exit 1
fi

# 원격에 최신 백업이 있으면 --link-dest로 증분 백업
if ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
   "[ -d '${LATEST_LINK}' ]" 2>/dev/null; then
  rsync -avz \
    --delete \
    --exclude="*.log" \
    --exclude="cache/" \
    --exclude="*.tmp" \
    --link-dest="${LATEST_LINK}" \
    -e "ssh -i ${BACKUP_KEY}" \
    /var/www/ \
    "${BACKUP_SERVER}:${REMOTE_BASE}/snapshots/${DATE}/" \
    2>&1 | tail -5 | tee -a "${LOG_FILE}"
else
  log "최초 풀 백업 시작"
  rsync -avz \
    --delete \
    -e "ssh -i ${BACKUP_KEY}" \
    /var/www/ \
    "${BACKUP_SERVER}:${REMOTE_BASE}/snapshots/${DATE}/" \
    2>&1 | tail -5 | tee -a "${LOG_FILE}"
fi

# latest 심볼릭 링크 업데이트
ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
  "rm -f '${LATEST_LINK}' && ln -s '${REMOTE_BASE}/snapshots/${DATE}' '${LATEST_LINK}'"

log "웹 파일 동기화 완료"

# ============ 4단계: 스테이징 파일을 원격으로 전송 ============
log "4단계: 설정/DB 백업 파일 원격 전송"
scp -i "${BACKUP_KEY}" "${LOCAL_TEMP}"/*.tar.gz "${LOCAL_TEMP}"/*.sql.gz \
  "${BACKUP_SERVER}:${REMOTE_BASE}/archives/" 2>/dev/null || true

# ============ 5단계: 보존 정책 적용 (오래된 백업 삭제) ============
log "5단계: ${RETENTION_DAYS}일 이상 지난 백업 삭제"
ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
  "find '${REMOTE_BASE}/archives/' -type f -mtime +${RETENTION_DAYS} -delete && \
   find '${REMOTE_BASE}/snapshots/' -maxdepth 1 -type d -mtime +${RETENTION_DAYS} \
   -exec rm -rf {} \; 2>/dev/null || true"

# ============ 정리 ============
rm -rf "${LOCAL_TEMP}"
log "===== 백업 완료: $(date '+%Y-%m-%d %H:%M:%S') ====="

cron 등록

로컬 터미널
# crontab 편집
crontab -e

# 매일 새벽 2시 백업 실행
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 매주 일요일 새벽 1시 풀 백업 (스크립트 내에서 요일 판단 가능)
0 1 * * 0 /usr/local/bin/full-backup.sh >> /var/log/backup.log 2>&1

# 1시간마다 중요 설정 파일만 백업
0 * * * * tar -czf /backup/etc-hourly-$(date +\%H).tar.gz /etc/ 2>/dev/null

# /etc/crontab 에 추가 (시스템 전체 cron)
# /etc/cron.d/backup 파일로 관리 권장
cat > /etc/cron.d/server-backup << 'EOF'
# 서버 백업 작업
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@example.com

# 매일 새벽 2시 자동 백업
0 2 * * * root /usr/local/bin/backup.sh
EOF

6. 보존 정책(Retention Policy)

오래된 백업 자동 삭제 및 보존 정책 구현

백업은 쌓이면 디스크를 가득 채웁니다. 보존 정책을 통해 필요한 기간만큼만 유지합니다.

find -mtime를 사용한 오래된 파일 삭제

로컬 터미널
# 30일 이상 된 파일 삭제 (파일만, 디렉토리 제외)
find /backup/archives/ -type f -mtime +30 -delete

# 옵션 설명:
# -type f   : 파일만 (디렉토리 제외)
# -mtime +30: 수정 시간이 30일 초과인 것
#             +30: 30일 초과, 30: 정확히 30일, -30: 30일 미만

# 삭제 전 대상 파일 목록 확인 (항상 먼저!)
find /backup/archives/ -type f -mtime +30 -ls

# 특정 패턴의 파일만 삭제
find /backup/archives/ -name "*.tar.gz" -mtime +30 -delete
find /backup/archives/ -name "*.sql.gz" -mtime +7 -delete  # DB는 7일

# 빈 디렉토리도 정리
find /backup/snapshots/ -type d -empty -delete

# 날짜 기반 스냅샷 디렉토리 삭제 (30일 이상)
위험 명령어경로 변수가 잘못 설정되거나 -mtime 값이 0에 가까우면 최신 백업까지 삭제됩니다. 실수로 운영 서버 경로를 지정하면 데이터가 영구 삭제됩니다.

오래된 스냅샷 디렉토리 일괄 삭제 — 복구 불가

안전한 실행 조건: 먼저 -exec rm -rf {} 대신 -ls 또는 -print 로 삭제 대상 목록을 확인한 뒤 실행

실행 전 반드시 확인

  • find 명령에서 rm -rf 대신 -ls 옵션으로 삭제될 디렉토리 목록을 먼저 확인
  • /backup/snapshots/ 경로가 올바른 백업 서버 경로인지 확인 (운영 서버에서 실행 금지)
  • -mtime +30 값이 충분히 큰지 확인 — 최소 보존 기간보다 크게 설정
find /backup/snapshots/ -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;

위 항목을 모두 확인한 후 복사할 수 있습니다

로컬 터미널
find /backup/snapshots/ -maxdepth 1 -type d -mtime +30 \
  -exec rm -rf {} \; 2>/dev/null

세분화된 보존 정책

로컬 터미널
#!/bin/bash
# /usr/local/bin/retention-cleanup.sh
# 백업 보존 정책: 일별 7개, 주별 4개, 월별 12개

BACKUP_DIR="/backup/archives"

# 7일 이상 된 일별 백업 삭제
find "${BACKUP_DIR}/daily/" -type f -mtime +7 -delete
echo "7일 이상 일별 백업 삭제 완료"

# 28일 이상 된 주별 백업 삭제
find "${BACKUP_DIR}/weekly/" -type f -mtime +28 -delete
echo "28일 이상 주별 백업 삭제 완료"

# 365일 이상 된 월별 백업 삭제
find "${BACKUP_DIR}/monthly/" -type f -mtime +365 -delete
echo "365일 이상 월별 백업 삭제 완료"

# 백업 디스크 사용량 리포트
echo ""
echo "=== 백업 스토리지 현황 ==="
du -sh "${BACKUP_DIR}"/*/ 2>/dev/null
df -h "${BACKUP_DIR}"

보존 정책을 cron에 등록

로컬 터미널
# 매일 새벽 3시 정리 작업 (백업 완료 후)
cat >> /etc/cron.d/server-backup << 'EOF'

# 백업 보존 정책 적용
0 3 * * * root /usr/local/bin/retention-cleanup.sh >> /var/log/backup.log 2>&1
EOF

7. 트러블슈팅

증상

tar: /backup/www-2026-03-26.tar.gz: No space left on device
rsync: [sender] write error: No space left on device (28)
rsync error: error in file IO (code 11)

원인 파악

로컬 터미널
# 디스크 사용량 전체 확인
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sdb1        50G   50G     0 100% /backup    ← 풀!

# 어디서 공간을 먹고 있는지 찾기
du -sh /backup/* | sort -rh | head -20

# inode 부족도 확인 (파일 수가 너무 많은 경우)
df -i

즉시 공간 확보

로컬 터미널
# 1. 가장 오래된 백업부터 삭제
ls -lt /backup/archives/ | tail -10  # 가장 오래된 것 확인
find /backup/archives/ -type f -mtime +14 -delete

# 2. 압축 없이 저장된 tar 파일 재압축
find /backup/ -name "*.tar" -size +100M | while read f; do
  gzip "$f" && echo "압축 완료: $f"
done

# 3. 로그 파일 정리 (백업 로그 포함)
find /var/log/ -name "*.gz" -mtime +30 -delete
journalctl --vacuum-time=7d  # systemd 저널 7일 이상 삭제

# 4. 중복 하드링크 백업 공간 계산 오류 주의
# du 는 하드링크를 중복 계산할 수 있음
# 실제 공간은 du --count-links 또는 ncdu 로 확인
ncdu /backup/  # interactive disk usage 분석 (설치 필요)

예방: 백업 전 공간 체크

로컬 터미널
#!/bin/bash
# 백업 시작 전 공간 확인 함수
check_space_before_backup() {
  local backup_path="$1"
  local source_path="$2"

  # 원본 크기 계산
  local source_size_kb
  source_size_kb=$(du -sk "${source_path}" | cut -f1)

  # 백업 경로 여유 공간
  local available_kb
  available_kb=$(df "${backup_path}" | awk 'NR==2 {print $4}')

  # 원본의 120% 필요 (압축 후 크기 + 여유분)
  local required_kb=$((source_size_kb * 120 / 100))

  if [ "${available_kb}" -lt "${required_kb}" ]; then
    echo "오류: 공간 부족!"
    echo "필요: $((required_kb / 1024))MB, 여유: $((available_kb / 1024))MB"
    exit 1
  fi

  echo "공간 확인 OK: 여유 $((available_kb / 1024))MB"
}

check_space_before_backup /backup /var/www/

증상

rsync: failed to set permissions on "/destination/file.conf":
  Operation not permitted (1)
rsync: chown "/destination/file.txt" failed: Operation not permitted (1)

또는 파일이 전송되었지만 소유자가 달라진 경우:

로컬 터미널
ls -la /destination/
# -rw-r--r-- 1 root root  file.conf   ← 원래는 www-data:www-data 여야 함

원인

  • 백업 대상 서버에 해당 사용자(www-data 등)가 없는 경우
  • root 권한 없이 rsync를 실행하는 경우
  • NFS나 특수 파일시스템에서 권한 설정 불가

해결 방법

로컬 터미널
# 방법 1: 권한/소유권 동기화 건너뜀 (단순 파일 내용만 복사)
rsync -avz --no-perms --no-owner --no-group \
  /source/ /destination/

# 방법 2: 목적지에서 특정 소유권으로 강제 지정
rsync -avz --chown=www-data:www-data \
  /source/ /destination/

# 방법 3: 권한은 유지하되 소유자는 현재 사용자로
rsync -rltz --no-owner --no-group \
  /source/ /destination/
# -rl: 재귀 + 심볼릭링크
# -t: 타임스탬프
# -z: 압축

# 방법 4: rsync 후 별도로 소유권 일괄 변경
rsync -avz /source/ /destination/
chown -R www-data:www-data /destination/

# 방법 5: sudo를 통한 rsync (원격)
rsync -avz -e "ssh -i /root/.ssh/key" \
  --rsync-path="sudo rsync" \
  /source/ user@remote:/destination/

파일 복원 시 소유권 처리

로컬 터미널
# tar 해제 시 소유권 보존 (root로 실행 필요)
tar -xzf backup.tar.gz -C /restore/ --same-owner

# tar 해제 시 소유권 무시 (현재 사용자로 복원)
tar -xzf backup.tar.gz -C /restore/ --no-same-owner

"file changed as we read it" 경고

rsync warning: some files vanished before they could be transferred (code 24)
file has vanished: "/source/temp-file.tmp"
file changed as we read it: "/source/app.log"

원인: rsync가 파일 목록을 스캔한 후 실제 전송 사이에 파일이 변경되거나 삭제됨. 주로 활성 로그 파일이나 임시 파일에서 발생.

로컬 터미널
# 해결책 1: 오류 코드 24(vanished files)를 무시
rsync -avz /source/ /destination/
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 24 ]; then
  echo "백업 성공 (일부 파일 변경 무시)"
else
  echo "백업 실패: 코드 ${EXIT_CODE}"
  exit 1
fi

# 해결책 2: 로그 파일 등 변경되는 파일 제외
rsync -avz \
  --exclude="*.log" \
  --exclude="*.pid" \
  --exclude="/var/run/" \
  /source/ /destination/

# 해결책 3: --checksum 으로 파일 변경 감지 방식 변경
# (크기+시간 대신 체크섬으로 비교 — 느리지만 정확)
rsync -avz --checksum /source/ /destination/

대용량 파일 전송 중단 후 재개: --partial, --append

로컬 터미널
# 문제: 네트워크 끊김으로 대용량 파일 전송 중단
# 기본 rsync는 불완전한 파일을 삭제하고 처음부터 다시 시작

# 해결책: --partial 옵션으로 중단된 파일 보존 후 재개
rsync -avz --partial \
  -e "ssh -i /root/.ssh/key" \
  /backup/large-database.sql.gz \
  backup@remote:/backup/

# --partial-dir: 불완전한 파일을 임시 디렉토리에 보관
rsync -avz --partial-dir=/tmp/rsync-partial \
  -e "ssh -i /root/.ssh/key" \
  /backup/ backup@remote:/backup/

# --append: 파일 끝부분에 이어서 전송 (로그처럼 추가만 되는 파일)
rsync -avz --append \
  /var/log/app.log backup@remote:/backup/logs/

# 권장 조합: --partial + --append-verify (무결성 검증 포함)
rsync -avz \
  --partial \
  --append-verify \
  --progress \
  -e "ssh -i /root/.ssh/key" \
  /backup/large-file.tar.gz \
  backup@remote:/backup/

# 자동 재시도 스크립트
for i in 1 2 3 4 5; do
  rsync -avz --partial --progress \
    -e "ssh -i /root/.ssh/key" \
    /backup/ backup@remote:/backup/ && break
  echo "시도 ${i} 실패, 30초 후 재시도..."
  sleep 30
done

rsync 종료 코드 참조

로컬 터미널
# 주요 rsync 종료 코드
# 0  : 성공
# 1  : 문법 오류
# 2  : 프로토콜 비호환
# 10 : I/O 오류 (파일 읽기/쓰기 실패)
# 11 : 파일 I/O 오류
# 12 : 스트림 프로토콜 오류
# 23 : 일부 파일 전송 실패 (권한 오류 등)
# 24 : 일부 파일이 전송 중 사라짐 (경고 수준)
# 25 : 삭제 작업 실패
# 30 : 타임아웃

심화 — 살아있는 데이터를 백업한다는 것

💡개념

심화: 파일 단위 백업이 '지금 이 순간'을 담지 못하는 이유 — 정합성과 스냅샷

지금까지 tar와 rsync로 디렉터리를 통째로 백업했습니다. 정적인 웹 파일이나 설정에는 이걸로 충분합니다. 하지만 백업 대상이 계속 바뀌는 데이터라면, 파일 단위 복사에는 눈에 잘 안 보이는 함정이 있습니다.

  • 백업은 순간이 아니라 기간에 걸쳐 일어난다: tar·rsync는 디렉터리 트리를 처음부터 끝까지 훑으며 파일을 하나씩 읽습니다. 파일이 많으면 이 과정에 수 초에서 수 분이 걸리고, 그동안 원본은 계속 바뀔 수 있습니다. 그래서 백업 안의 A 파일은 10:00:00의 상태, Z 파일은 10:02:30의 상태 — 하나의 순간이 아니라 뒤섞인 시점들의 모음이 됩니다.
  • 정적 데이터는 괜찮고, 관계형 데이터는 깨진다: 서로 참조하지 않는 정적 파일이면 시점이 조금 어긋나도 문제없습니다. 하지만 DB 데이터 파일처럼 여러 파일이 서로 정합성을 유지해야 하는 경우(데이터 파일 + 인덱스 + 트랜잭션 로그), 시점이 어긋난 사본은 논리적으로 모순된 상태라 복구해도 열리지 않거나 손상으로 보고됩니다. 쓰기 중인 파일 하나를 중간에 읽으면 반만 쓰인 페이지(torn write)가 그대로 복사되기도 합니다.
  • 해결은 순간을 얼리는 것: 세 가지 방법이 있습니다. (1) 쓰기를 멈추고(quiesce) 복사 — 확실하지만 다운타임, (2) 애플리케이션 자체 덤프(mysqldump·pg_dump) — DB가 트랜잭션 정합성을 보장한 하나의 시점 이미지를 뽑아 줌, (3) 파일시스템·LVM·클라우드 스냅샷 — 순간을 원자적으로 얼린 뒤 그 스냅샷을 tar·rsync. 살아있는 데이터는 (2)나 (3) 위에서 백업하는 것이 원칙입니다.

즉 tar·rsync는 무엇을 어디로 옮길지의 도구이고, 언제의 상태를 담을지는 스냅샷·덤프가 책임집니다. 백업 대상이 살아 움직이면 이 층을 하나 더 얹어야 복구되는 백업이 됩니다.

상황: 운영 중인 MySQL의 /var/lib/mysql을 매일 밤 tar로 백업해 왔습니다. 백업은 매번 성공했고 아카이브도 정상적으로 풀립니다. 그런데 막상 다른 서버에 복구해 mysqld를 올리니 크래시 리커버리가 실패하거나 일부 테이블이 corrupt로 표시됩니다.

원인: MySQL이 실행 중인 상태에서 데이터 파일을 tar로 읽었기 때문입니다. tar가 트리를 훑는 수 초에서 수십 초 동안 DB는 계속 페이지를 쓰고 있어, 아카이브 안의 데이터 파일·인덱스·redo 로그가 서로 다른 시점의 사본이 됩니다. 하나의 정합한 순간이 아니라 뒤섞인 시점이라 논리적으로 모순된 상태이고, 크래시 리커버리로도 복원되지 않습니다. 아카이브가 멀쩡히 풀리는 것과 그 안의 DB가 정합한 것은 별개입니다.

진단: 백업 스크립트가 백업 직전 DB를 멈추거나 덤프를 뜨는지 확인합니다(둘 다 안 하면 이 문제로 확정). 복구한 서버의 MySQL 에러 로그에서 InnoDB: Database page corruption 또는 크래시 리커버리 실패 메시지를 찾습니다. 같은 백업으로 여러 번 복구해도 재현되면 전송 무결성이 아니라 시점 정합성 문제입니다.

해결: 살아있는 DB는 파일 복사가 아니라 정합성을 보장하는 방법으로 백업합니다. 작은 규모는 mysqldump --single-transaction(InnoDB에서 잠금 없이 일관된 스냅샷)으로 논리 백업을, 큰 규모는 xtrabackup 같은 핫백업 도구나 LVM·클라우드 스냅샷으로 순간을 얼린 뒤 그 사본을 tar합니다. 그리고 이 문제는 복구 리허설을 정기적으로 했다면 사고 전에 드러났을 것입니다 — 백업 성공 로그가 아니라 실제로 복구되는지로 검증해야 합니다.


8. 실무 적용

💼
실무 맥락
현업 패턴

현실적인 온프레미스 백업 아키텍처

중소규모 회사의 Linux 서버를 관리하는 상황을 가정해봅니다. 예산이 제한되어 있고, 클라우드는 일부만 사용하며, 물리 서버 2대와 외장 NAS가 있습니다.

3-2-1 백업 구성 — 운영 서버 web01의 데이터를 백업 서버 backup01로, 다시 주간 단위로 외장 NAS(오프사이트)에 보관해 사본 3개·매체 2종·오프사이트 1개 유지확대

실제 구현 체크리스트

로컬 터미널
# 1. SSH 키 기반 인증 설정 (비밀번호 없이 자동 접속)
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub backup@192.168.1.200

# 2. 백업 스크립트 권한 설정
chmod 700 /usr/local/bin/backup.sh
chown root:root /usr/local/bin/backup.sh

# 3. 로그 로테이션 설정
cat > /etc/logrotate.d/backup << 'EOF'
/var/log/backup.log {
    weekly
    rotate 8
    compress
    delaycompress
    missingok
    notifempty
}
EOF

# 4. 백업 모니터링: 마지막 백업 시간 확인 스크립트
cat > /usr/local/bin/check-backup.sh << 'SCRIPT'
#!/bin/bash
LAST_BACKUP=$(stat -c %Y /backup/latest 2>/dev/null || echo 0)
NOW=$(date +%s)
DIFF=$(( (NOW - LAST_BACKUP) / 3600 ))

if [ $DIFF -gt 26 ]; then
  echo "경고: 마지막 백업이 ${DIFF}시간 전입니다!"
  exit 1
else
  echo "OK: 마지막 백업 ${DIFF}시간 전"
fi
SCRIPT
chmod +x /usr/local/bin/check-backup.sh

# 5. 복구 훈련 (분기별 1회 권장)
# 실제 복구를 테스트하지 않은 백업은 백업이 아님
# 테스트 환경에서 실제 복구 절차 수행

백업 복구 절차 문서화

로컬 터미널
# /etc/backup-recovery-procedure.txt
# 이 파일은 인쇄해서 서버실에 보관할 것

=== 서버 복구 절차 ===

1. 백업 서버 접속
   ssh backup@192.168.1.200

2. 사용 가능한 스냅샷 확인
   ls -la /backup/web01/snapshots/

3. 특정 날짜의 파일 복구
   rsync -avz /backup/web01/snapshots/2026-03-25_02-00-00/ \
     web01:/restore/

4. 설정 파일 복구
   tar -xzf /backup/web01/archives/configs-2026-03-25_02-00-00.tar.gz \
     -C /restore/

5. DB 복구
   gunzip < /backup/web01/archives/mysql-2026-03-25_02-00-00.sql.gz \
     | mysql -u root -p
💼
실무 맥락
현업 패턴

CI/CD 파이프라인에 백업 통합

현대적인 DevOps 환경에서는 배포 전후로 백업을 자동화합니다:

로컬 터미널
#!/bin/bash
# deploy.sh — 배포 스크립트에 백업 통합

DEPLOY_TAG="$1"
BACKUP_SERVER="backup@backup01"

echo "=== 배포 전 백업 시작 ==="

# 배포 전 스냅샷 생성
SNAPSHOT_NAME="pre-deploy-${DEPLOY_TAG}-$(date +%Y%m%d-%H%M%S)"

rsync -avz \
  --link-dest="${BACKUP_SERVER}:/backup/app/latest" \
  -e "ssh -i /root/.ssh/backup_key" \
  /var/www/app/ \
  "${BACKUP_SERVER}:/backup/app/snapshots/${SNAPSHOT_NAME}/"

# 배포 실행
echo "=== 배포 실행 중 ==="
./actual-deploy.sh "${DEPLOY_TAG}"

DEPLOY_EXIT=$?

if [ $DEPLOY_EXIT -ne 0 ]; then
  echo "=== 배포 실패! 롤백 시작 ==="
  rsync -avz \
    -e "ssh -i /root/.ssh/backup_key" \
    "${BACKUP_SERVER}:/backup/app/snapshots/${SNAPSHOT_NAME}/" \
    /var/www/app/
  echo "롤백 완료: ${SNAPSHOT_NAME}"
else
  echo "=== 배포 성공: ${DEPLOY_TAG} ==="
fi

Grafana/Prometheus로 백업 상태 모니터링

로컬 터미널
# 백업 완료 시 메트릭 푸시 (Prometheus Pushgateway 사용)
BACKUP_STATUS=$?  # 0: 성공, 그 외: 실패
BACKUP_SIZE=$(du -sb /backup/ | cut -f1)

cat << EOF | curl --data-binary @- http://pushgateway:9091/metrics/job/backup/instance/$(hostname)
# HELP backup_last_success_timestamp 마지막 성공한 백업의 Unix 타임스탬프
# TYPE backup_last_success_timestamp gauge
backup_last_success_timestamp $(date +%s)

# HELP backup_size_bytes 전체 백업 크기 (바이트)
# TYPE backup_size_bytes gauge
backup_size_bytes ${BACKUP_SIZE}

# HELP backup_exit_code 마지막 백업 종료 코드
# TYPE backup_exit_code gauge
backup_exit_code ${BACKUP_STATUS}
EOF

백업 스크립트 테스트 자동화

로컬 터미널
# 백업 스크립트의 기능을 자동으로 검증하는 테스트
#!/bin/bash
# /usr/local/bin/test-backup.sh

FAIL=0

# 테스트 1: 백업 파일이 오늘 생성되었는지
TODAY=$(date +%Y-%m-%d)
if ls /backup/archives/*${TODAY}* &>/dev/null; then
  echo "PASS: 오늘의 백업 파일 존재"
else
  echo "FAIL: 오늘의 백업 파일 없음"
  FAIL=1
fi

# 테스트 2: 최신 스냅샷이 24시간 내에 생성되었는지
LATEST_MTIME=$(stat -c %Y /backup/snapshots/latest 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( (NOW - LATEST_MTIME) / 3600 ))
if [ $AGE -lt 25 ]; then
  echo "PASS: 최신 스냅샷 ${AGE}시간 전 생성"
else
  echo "FAIL: 최신 스냅샷이 ${AGE}시간 전 (24시간 초과)"
  FAIL=1
fi

# 테스트 3: tar 아카이브 무결성 검증
LATEST_TAR=$(ls -t /backup/archives/*.tar.gz 2>/dev/null | head -1)
if [ -n "$LATEST_TAR" ] && tar -tzf "$LATEST_TAR" > /dev/null 2>&1; then
  echo "PASS: tar 아카이브 무결성 OK"
else
  echo "FAIL: tar 아카이브 손상 또는 없음"
  FAIL=1
fi

exit $FAIL

9. 핵심 명령어 빠른 참조

로컬 터미널
# ============ tar 명령어 ============

# 생성 (gzip 압축)
tar -czf backup.tar.gz /path/to/source/

# 생성 (날짜 포함)
tar -czf backup-$(date +%Y-%m-%d).tar.gz /path/

# 생성 (특정 파일 제외)
tar -czf backup.tar.gz /path/ --exclude="*.log" --exclude="cache/"

# 내용 확인 (해제 없이)
tar -tzf backup.tar.gz

# 해제 (현재 위치)
tar -xzf backup.tar.gz

# 해제 (특정 위치)
tar -xzf backup.tar.gz -C /restore/

# ============ rsync 명령어 ============

# 로컬 동기화 (기본)
rsync -avz /source/ /destination/

# 삭제 포함 완전 동기화
rsync -avz --delete /source/ /destination/

# 미리 보기 (dry-run)
rsync -avzn --delete /source/ /destination/

# SSH 원격 동기화
rsync -avz -e "ssh -i /root/.ssh/key" /source/ user@remote:/dest/

# 증분 백업 (하드링크)
rsync -avz --link-dest=/backup/latest /source/ /backup/$(date +%Y-%m-%d)/

# 부분 전송 재개
rsync -avz --partial --progress /source/ user@remote:/dest/

# 권한 무시
rsync -avz --no-perms --no-owner --no-group /source/ /dest/

# ============ scp 명령어 ============

# 단일 파일 전송
scp /local/file.conf user@remote:/remote/path/

# 원격에서 다운로드
scp user@remote:/remote/file.conf /local/path/

# 디렉토리 전송
scp -r /local/dir/ user@remote:/remote/

# ============ 보존 정책 ============

# 30일 이상 파일 삭제
find /backup/ -type f -mtime +30 -delete

# 대상 확인 후 삭제
find /backup/ -type f -mtime +30 -ls
find /backup/ -type f -mtime +30 -delete

# 빈 디렉토리 정리
find /backup/ -type d -empty -delete
💡개념

복구 리허설 — RTO/RPO 정의와 정기 복구 테스트

RTO·RPO 정의 — 허용 가능한 복구 시간과 데이터 손실 범위확대

백업이 있어도 복구가 된다는 검증 없이는 백업이 없는 것과 같습니다. 실제 장애가 나기 전에 정기적으로 복구를 연습해야 합니다.

RTO/RPO 정의:

지표의미예시
RPO (Recovery Point Objective)허용 가능한 최대 데이터 손실 시간"최대 1시간치 데이터 손실 허용"
RTO (Recovery Time Objective)허용 가능한 최대 복구 소요 시간"2시간 내 서비스 복구"

무결성 검증 — 백업 직후 자동 검증:

로컬 터미널
# tar 백업 무결성 검증 (압축 해제 없이 테스트)
tar -tzf /backup/app_20260405.tar.gz > /dev/null && echo "OK" || echo "CORRUPT"

# SHA256 체크섬 생성 및 검증
sha256sum /backup/app_20260405.tar.gz > /backup/app_20260405.tar.gz.sha256

# 나중에 검증
sha256sum -c /backup/app_20260405.tar.gz.sha256
# /backup/app_20260405.tar.gz: OK

# 자동화된 백업+검증 스크립트 핵심
BACKUP_FILE="/backup/app_$(date +%Y%m%d).tar.gz"
tar -czf "$BACKUP_FILE" /opt/myapp/
if sha256sum "$BACKUP_FILE" > "${BACKUP_FILE}.sha256"; then
    echo "[OK] 백업 완료 및 무결성 검증 성공: $BACKUP_FILE"
else
    echo "[ERROR] 백업 무결성 검증 실패!" >&2
    exit 1
fi

정기 복구 테스트 체크리스트:

로컬 터미널
# 매월 1회 복구 테스트 절차 (스테이징 환경에서)
# 1. 최신 백업 파일 확인
ls -lth /backup/ | head -5

# 2. 복구 환경 준비 (스테이징 서버 또는 임시 디렉토리)
mkdir -p /tmp/restore-test

# 3. 실제 복구 실행
tar -xzf /backup/app_$(date +%Y%m%d).tar.gz -C /tmp/restore-test

# 4. 복구된 데이터 검증
ls -la /tmp/restore-test/opt/myapp/
diff -rq /opt/myapp/ /tmp/restore-test/opt/myapp/ && echo "복구 데이터 일치"

# 5. 복구 소요 시간 기록 → RTO 달성 여부 확인
💡개념

백업 암호화 — gpg/age로 랜섬웨어 대응

백업 암호화 흐름 — gpg/age로 랜섬웨어 대응확대

백업 파일 자체가 암호화되어 있지 않으면 랜섬웨어나 내부자 유출 시 백업 데이터도 위험합니다.

gpg 대칭 암호화 (패스워드 기반):

로컬 터미널
# 암호화 (AES256)
gpg --symmetric --cipher-algo AES256 \
    --batch --passphrase "$(cat /etc/backup/passphrase)" \
    /backup/app_20260405.tar.gz
# → /backup/app_20260405.tar.gz.gpg 생성

# 복호화
gpg --batch --passphrase "$(cat /etc/backup/passphrase)" \
    -d /backup/app_20260405.tar.gz.gpg > /tmp/restored.tar.gz

age — 더 현대적인 암호화 도구 (권장):

로컬 터미널
# age 설치
sudo apt install age  # 또는 sudo dnf install age

# 공개키 기반 암호화 (수신자 공개키 필요)
age-keygen -o /etc/backup/backup.key  # 키 쌍 생성
age -r "$(grep 'public key' /etc/backup/backup.key | awk '{print $4}')" \
    -o /backup/app_20260405.tar.gz.age \
    /backup/app_20260405.tar.gz

# 복호화 (개인키 필요)
age -d -i /etc/backup/backup.key \
    -o /tmp/restored.tar.gz \
    /backup/app_20260405.tar.gz.age

백업 불변성 (Immutability) — 랜섬웨어 방어:

로컬 터미널
# 오브젝트 스토리지 불변 락 (AWS S3 Object Lock)
aws s3api put-object-lock-configuration \
    --bucket backup-bucket \
    --object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}'

# Linux chattr으로 파일 불변 설정 (로컬 백업)
sudo chattr +i /backup/app_20260405.tar.gz
# → root도 삭제/수정 불가 (랜섬웨어 방어)
lsattr /backup/app_20260405.tar.gz
# ----i--------e-- /backup/app_20260405.tar.gz

마무리

이 챕터에서 다룬 내용을 정리합니다:

  1. 백업 전략 — 풀/증분/차등 백업의 장단점과 현실적인 선택 기준
  2. 3-2-1 원칙 — 데이터 보호의 황금률과 랜섬웨어 방어
  3. tar — 아카이브 생성, 해제, 목록 확인, 날짜 파일명 패턴
  4. rsync — 로컬/원격 동기화, --delete, --dry-run, --progress
  5. rsync --link-dest — 스토리지 효율적인 스냅샷 백업 구현
  6. 자동화 — cron + 전체 백업 스크립트로 무인 운영
  7. 보존 정책 — find -mtime으로 오래된 백업 자동 정리
  8. 트러블슈팅 — Disk Full, 소유권 문제, 전송 재개

가장 중요한 것은 백업은 한 번 설정하고 끝이 아니라는 것입니다. 정기적으로 복구 테스트를 수행하고, 백업 로그를 모니터링하며, 보존 정책이 제대로 작동하는지 확인하는 것이 실제 운영의 핵심입니다. 테스트하지 않은 백업은 존재하지 않는 것과 같습니다.


관련 모듈로 더 깊이:

다음 모듈에서는 서버 하드웨어 스펙을 lscpu, dmidecode, /proc 파일시스템으로 5분 안에 파악하는 방법을 다룹니다.

지식 확인

퀴즈 — 10문제

Q1

rsync로 /data/ 디렉토리를 원격 서버 192.168.1.10의 /backup/ 경로에 동기화할 때, 원본에서 삭제된 파일도 대상에서 제거하려면 어떤 옵션을 추가해야 하는가?

Q2

현재 디렉토리 /var/www를 backup.tar.gz 파일로 압축 아카이브할 때 올바른 tar 명령어는?

Q3

랜섬웨어 공격이 발생했는데 백업 파일도 같은 서버에 있어서 모두 암호화됐습니다. 이런 사고를 막는 백업 설계 원칙 관점에서 무엇이 잘못됐습니까?

Q4

cron을 이용해 매일 새벽 2시에 rsync 백업 스크립트(/opt/backup.sh)를 실행하는 crontab 설정으로 올바른 것은?

Q5

증분 백업(incremental backup)과 차등 백업(differential backup)의 복구 과정 차이로 올바른 것은?

Q6

백업이 매일 정상 생성되고 있어도 '복구 리허설(정기 복구 테스트)'을 따로 해야 하는 이유는?

Q7

[심화] 실행 중인 데이터베이스의 데이터 디렉터리를 tar로 백업하면, 아카이브는 멀쩡히 풀리는데도 복구한 DB가 손상되곤 한다. 근본 원인은?

Q8

[심화] 운영 중인 MySQL의 데이터 디렉터리를 매일 tar로 백업해 왔는데 복구할 때마다 테이블 손상이 재현된다. 가장 올바른 대응은?

Q9

[1급] 매일 전체 스냅샷을 남기되 디스크는 변경분만큼만 쓰고 싶다. `rsync -a --link-dest=/backup/2026-07-06 /data/ /backup/2026-07-07/`에서 --link-dest의 역할은?

Q10

[1급] `tar --listed-incremental=/backup/snapshot.snar -czf level0.tar.gz /data`를 처음 실행한 뒤, 같은 명령을 다음 날 다시 실행하면 어떻게 되는가?

0 / 10 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요