지난 세 편으로 사이트를 빠르게 만들었다. 이제 방향을 바꾼다. 안정성이다. 티스토리를 쓸 때는 백업을 신경 쓴 적이 없었다. 플랫폼이 알아서 해줬으니까. 하지만 서버를 직접 빌린 순간부터, 백업은 온전히 내 몫이다. 서버가 한 번 죽으면 그동안 쓴 글과 설정이 통째로 사라진다. 이번 편에서는 DB와 파일을 자동으로, 그리고 복원이 검증되는 백업 체계를 만든다. 플러그인 없이, 리눅스 기본 도구만으로.
결론부터. mysqldump + tar + cron 세 가지로 매일 새벽 자동 백업을 세웠다. 백업 한 번은 약 60MB, 3.5초. DB 18개 테이블은 605KB로, 파일 131MB는 59MB로 압축됐다. 7개를 회전 보관해도 420MB로, 디스크 여유의 1%도 안 된다. 만들자마자 gzip 무결성과 테이블 수까지 검증했다. 단, 이 백업은 아직 같은 서버 안에 있다 — 진짜 재해 대비는 다음 편에서 원격으로 보낸다.
무엇을 백업하나 — 사라지면 복구 불가능한 것
워드프레스에서 잃으면 되돌릴 수 없는 건 딱 둘이다. DB(글, 댓글, 설정 전부)와 wp-content(업로드 이미지, 테마, 플러그인). 워드프레스 코어 파일은 언제든 공식 사이트에서 다시 받으면 되니 백업 대상이 아니다. wp-config.php는 DB 접속 정보와 보안 키가 들었으니 꼭 포함한다.
대상별 크기를 먼저 쟀다. 여기서 예상 밖의 그림이 나왔다.
| 대상 | 크기 | 비고 |
|---|---|---|
| DB (/var/lib/mysql) | 148MB | 글·설정 |
| wp-content 전체 | 131MB | — |
| └ plugins | 73MB | 가장 큰 덩어리 |
| └ uploads (이미지) | 25MB | WebP로 이미 최적화됨 |
| └ themes | 25MB | — |
| wp-config.php | 3.7KB | 접속·보안 정보 |
보통 이미지가 제일 클 거라 생각한다. 그런데 plugins(73MB)가 uploads(25MB)보다 훨씬 컸다. 8편에서 이미지를 WebP로 줄여둔 덕분에 업로드 폴더가 가벼워졌고, 결과적으로 플러그인 코드가 백업의 최대 덩어리가 된 것이다. 과거의 최적화가 여기서도 이득으로 돌아왔다.
DB 백업 — mysqldump, 비밀번호 없이
DB는 mysqldump로 뜬다. 두 가지가 핵심이다.
time sudo bash -c 'mysqldump --single-transaction --quick ddlabx_wp | gzip > /var/backups/ddlabx/db_test.sql.gz'
--single-transaction은 사이트를 멈추지 않고도 일관된 스냅샷을 뜬다(테이블을 잠그지 않는다). 그리고 우분투 MariaDB의 root는 sudo(소켓 인증)로 접속되므로, 스크립트 어디에도 DB 비밀번호를 적지 않는다. 비밀번호를 파일에 박아두는 것 자체가 보안 구멍인데, 그걸 원천 차단하는 방식이다.
파이프라인에 sudo 걸 때 주의. sudo mysqldump ... > 파일은 실패한다. sudo가 mysqldump에만 붙고, 파일에 쓰는 리다이렉트(>)는 일반 사용자 권한으로 실행되기 때문이다. 백업 폴더를 chmod 700으로 잠갔다면 여기서 Permission denied가 난다. 전체를 sudo bash -c '...'로 감싸야 리다이렉트까지 root로 실행된다. (실제로 이걸로 한 번 막혔다.)
결과: 148MB DB가 605KB로, 0.16초 만에 압축됐다. gzip이 SQL 텍스트를 250배 넘게 줄인 건, DB 용량 대부분이 인덱스·오버헤드이고 순수 데이터는 작기 때문이다.
파일 백업(tar 한 줄)
time sudo tar czf /var/backups/ddlabx/files_test.tar.gz -C /var/www/ddlabx wp-content wp-config.php
131MB의 wp-content가 59MB로(55% 감소), 3.34초에 압축됐다. 이건 tar czf 파일명 구조라 tar가 직접 파일을 쓰므로, DB와 달리 sudo bash -c로 감쌀 필요가 없다(리다이렉트가 아니라서).
스크립트 + 보관 회전(자동화)
날짜별로 백업하고 오래된 건 자동으로 지우는(최근 7개 보관) 스크립트를 만들었다.
#!/bin/bash
set -euo pipefail
STAMP=$(date +%F_%H%M)
DEST=/var/backups/ddlabx
KEEP=7
SITE=/var/www/ddlabx
DB=ddlabx_wp
mkdir -p "$DEST"
# DB 덤프 (무중단, 압축)
mysqldump --single-transaction --quick "$DB" | gzip > "$DEST/db_$STAMP.sql.gz"
# 파일 압축
tar czf "$DEST/files_$STAMP.tar.gz" -C "$SITE" wp-content wp-config.php
# 보관 회전 — 최근 KEEP개만
ls -1t "$DEST"/db_*.sql.gz | tail -n +$((KEEP+1)) | xargs -r rm -f
ls -1t "$DEST"/files_*.tar.gz | tail -n +$((KEEP+1)) | xargs -r rm -f
echo "$(date '+%F %T') OK db_$STAMP files_$STAMP" >> "$DEST/backup.log"
set -euo pipefail은 중간에 하나라도 실패하면 즉시 멈추게 한다(반쪽짜리 백업 방지). 스크립트 안에서는 root로 실행되니 앞서의 리다이렉트 권한 문제도 없다. 파일명에 날짜·시각(_1621)이 붙어 매일 돌려도 안 겹친다.
백업은 복원되어야 백업이다(핵심 단계)
백업 파일이 조용히 깨진 채 쌓이고 있는데 모르는 것, 그게 최악이다. 정작 서버가 죽은 날 열어보니 복원이 안 되면 백업은 없는 것과 같다. 그래서 만들자마자 검증한다.
sudo bash -c '
cd /var/backups/ddlabx
DB=$(ls -1t db_*.sql.gz | head -1)
FILES=$(ls -1t files_*.tar.gz | head -1)
gzip -t "$DB" && echo "DB gzip OK"
gzip -t "$FILES" && echo "FILES gzip OK"
zcat "$DB" | grep -c "CREATE TABLE"
tar -tzf "$FILES" | grep -E "wp-config.php|wp-content/(uploads|themes|plugins)/" | head
'
세 가지를 확인한다. gzip 무결성(압축이 안 깨졌나), DB 덤프 안의 테이블 수(내용이 실제로 들었나), 아카이브 안의 핵심 폴더(빠진 게 없나). 결과는 gzip OK 2개, 테이블 18개, wp-content 폴더 정상. 복원 가능한 백업임이 확인됐다.
이 폴더는 chmod 700으로 잠가 root만 접근할 수 있다. 백업엔 DB 내용과 wp-config.php의 보안 키가 들어가므로, 일반 사용자가 못 들여다보게 막는 것이 맞다. 그래서 검증·조회 명령도 전부 sudo로 실행한다.
매일 자동 실행 (cron) + 용량 계산
( sudo crontab -l 2>/dev/null; echo "0 3 * * * /usr/local/bin/ddlabx-backup.sh" ) | sudo crontab -
매일 새벽 3시에 자동 실행된다. 전체 그림을 표로 정리하면 이렇다.
| 항목 | 원본 | 백업(압축) | 시간 |
|---|---|---|---|
| DB (18개 테이블) | 148MB | 605KB | 0.16초 |
| 파일 (wp-content+config) | 131MB | 59MB | 3.34초 |
| 합계 (1회) | 279MB | 약 60MB | 약 3.5초 |
| 7개 회전 보관 | — | 약 420MB | — |
7일치를 다 보관해도 420MB. 디스크 여유(46GB)의 1%도 안 된다. 백업이 서버에 주는 부담은 사실상 없다는 게 숫자로 확인됐다. 무겁다는 핑계로 백업을 미룰 이유가 전혀 없는 것이다.
솔직한 한계 — 이건 아직 재해복구가 아니다
여기까지 하면 뿌듯하지만, 냉정하게 짚어야 한다. 이 백업은 전부 같은 서버 안(/var/backups)에 있다. 실수로 파일을 지웠거나 DB가 꼬였을 때 되돌리는 데는 충분하다. 하지만 서버가 통째로 죽는 진짜 재해 — 디스크 장애, 계정 정지, 랜섬웨어 — 앞에서는 백업도 서버와 함께 사라진다.
즉 이번 편은 “복원의 재료를, 자동으로, 검증된 상태로 갖추는” 단계다. 이것만으로도 안 하는 것보다 압도적으로 낫지만, 절반이다. 나머지 절반은 이 백업을 서버 바깥으로 내보내는 것이다.
다음 편 예고 — 백업과 재해복구 2편
다음 편에서는 rclone으로 이 백업을 원격 오브젝트 스토리지에 자동으로 올린다. 서버가 죽어도 백업은 다른 곳에 살아 있게 만드는, 진짜 오프사이트 백업이다.