sunucu.com.tr
Linux Sunucular

Sunucu yedekleme bash betiği: Linux'ta tar, mysqldump ve rsync ile uzak sunucuya yedek, systemd timer ve geri yükleme testi

Panel ya da eklenti kullanmadan sunucu yedekleme: dosyaları tar ile, MySQL/MariaDB veritabanlarını mysqldump ile şifreli arşivleyen, rsync ile uzak sunucuya taşıyan, günlük, haftalık ve aylık rotasyon yapan bash betiği. systemd timer ile zamanlama, hata e-postası ve geri yükleme testi dahil.

15 dk okuma

Panel ya da eklenti kullanmayan bir Linux sunucuda sunucu yedekleme işini genelde elle alınan tek bir tar dosyası ya da hiç denenmemiş bir cron satırı üstleniyor. Bu rehber, dosyaları ve MySQL/MariaDB veritabanlarını şifreli arşivleyen, uzak sunucuya taşıyan, eski yedekleri sınıfına göre silen ve hata olunca e-posta gönderen bir bash betiğini adım adım kuruyor. Betiğin bir de geri yükleme testi tarafı var. Hedef okur, kendi Linux sunucusunu yöneten sistem yöneticisi ya da KOBİ teknik sorumlusu.

Neden betikle sunucu yedekleme: snapshot, panel yedeği ve dosya düzeyi yedek

Üç yöntem farklı sorunları çözer:

  • Snapshot: Diskin o anki görüntüsüdür. Hızlı alınır ve hızlı döner, ama çoğu zaman aynı depolama altyapısında durur. Çalışan bir veritabanı için de "çökme anı tutarlılığı" sağlar, işlem tutarlılığı sağlamaz.
  • Panel yedeği: CloudPanel, HestiaCP gibi panellerin yedeği site bazında pratiktir. Panelin bilmediği dizinleri ve servisleri ise kapsamaz.
  • Dosya düzeyi yedek: Neyin, hangi biçimde ve nereye yedeklendiğini siz belirlersiniz. Tek bir veritabanını ya da tek bir dosyayı geri getirmek kolaydır.

Buradaki betik üçüncü gruba girer ve snapshot'ın yerini almaz, onu tamamlar. Mantığı 3-2-1 kuralıyla özetlenebilir: verinin en az üç kopyası, iki farklı ortamda ve biri sunucunun dışında.

Ön koşullar

  • Ubuntu 24.04 ya da Debian 12 (AlmaLinux/Rocky 9'da paket adları değişir, betik aynı kalır).
  • root ya da sudo yetkisi.
  • Yedeğin gideceği ikinci bir Linux sunucu ya da SSH ile erişilebilen bir yedek alanı.
  • Hata e-postası için çalışan bir mail komutu, yani yapılandırılmış bir MTA ya da SMTP aktarımı.

Gerekli paketleri kurun:

bash
# Ubuntu 24.04 / Debian 12
sudo apt update
sudo apt install -y zstd gnupg rsync mailutils

# AlmaLinux / Rocky Linux 9 için karşılığı:
# sudo dnf install -y zstd gnupg2 rsync s-nail

Hazırlık: şifre dosyası, veritabanı kullanıcısı ve SSH anahtarı

Arşiv şifresi

Yedekler GnuPG ile simetrik olarak (AES256) şifrelenecek. Parolayı bir dosyada tutun ve mutlaka sunucu dışında, bir parola yöneticisinde de saklayın. Sunucu tamamen kaybolursa bu parola olmadan hiçbir yedek açılmaz.

bash
# Rastgele 48 karakterlik parola üretir, yalnız root okuyabilir
sudo sh -c 'umask 077; openssl rand -base64 36 > /root/.yedek-parola'
sudo cat /root/.yedek-parola   # bu değeri parola yöneticinize kopyalayın

Yalnız okuma yetkili veritabanı kullanıcısı

Yedek için root yerine dar yetkili bir kullanıcı açın. Aşağıdaki SQL, MySQL 8.x ve MariaDB 10.6 ve sonrası için hazırlandı. Sürümünüze göre ek yetki istenirse hata mesajı hangi yetkinin eksik olduğunu söyler.

sql
CREATE USER IF NOT EXISTS 'yedek'@'localhost' IDENTIFIED BY 'ORNEK_GUCLU_PAROLA';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT ON *.* TO 'yedek'@'localhost';
FLUSH PRIVILEGES;

Bağlantı bilgisini komut satırında değil, bir ayar dosyasında tutun:

bash
sudo sh -c 'umask 077; cat > /root/.yedek-my.cnf <<EOF
[client]
user=yedek
password=ORNEK_GUCLU_PAROLA
EOF'
# Doğrulama: veritabanı listesi gelmeli
sudo mysql --defaults-extra-file=/root/.yedek-my.cnf -N -e "SHOW DATABASES"

Uzak sunucu için kısıtlı SSH anahtarı

Kaynak sunucuda parolasız, yalnız yedek için kullanılacak bir anahtar üretin:

bash
sudo ssh-keygen -t ed25519 -N "" -C "yedek@$(hostname -s)" -f /root/.ssh/yedek_ed25519
sudo cat /root/.ssh/yedek_ed25519.pub

Uzak sunucuda (örnekte 192.0.2.20) yedek adlı bir kullanıcı ve hedef dizini oluşturun, ardından açık anahtarı rrsync ile kısıtlayarak ekleyin. rrsync, rsync paketiyle gelen ve anahtarı tek bir dizine kilitleyen yardımcı betiktir. Yolunu command -v rrsync ile kontrol edin. -wo seçeneği yalnız yazmaya izin verir, bu anahtarla uzak sunucuda kabuk açılamaz ve dosya okunamaz.

bash
# UZAK sunucuda, root olarak çalıştırın
id yedek >/dev/null 2>&1 || useradd -m -s /bin/bash yedek
install -d -o yedek -g yedek -m 700 /srv/yedek /srv/yedek/sunucu1
install -d -o yedek -g yedek -m 700 /home/yedek/.ssh
# Aşağıdaki satırdaki anahtarı kaynak sunucudaki .pub içeriğiyle değiştirin
echo 'command="/usr/bin/rrsync -wo /srv/yedek",restrict ssh-ed25519 AAAA_ORNEK_ANAHTAR yedek@kaynak' \
  >> /home/yedek/.ssh/authorized_keys
chown yedek:yedek /home/yedek/.ssh/authorized_keys && chmod 600 /home/yedek/.ssh/authorized_keys

Kaynak sunucuda uzak sunucunun parmak izini bir kez kaydedin ve çıktıyı uzak sunucuda ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ile karşılaştırın:

bash
sudo sh -c 'ssh-keyscan -p 22 192.0.2.20 >> /root/.ssh/known_hosts'

Betiğin yapısı: set -euo pipefail, kilit dosyası ve günlük kaydı

Betik üç ilkeye dayanıyor:

  1. Hata anında durmak: set -Eeuo pipefail sayesinde boru hattının herhangi bir aşaması (örneğin mysqldump) hata verirse betik durur ve yarım dosya "başarılı yedek" gibi görünmez.
  2. Tek çalışma: flock ile kilit alınır. Önceki çalışma bitmeden ikincisi başlamaz.
  3. Atomik sonuç: Yedek önce gizli bir geçici dizine yazılır, her şey bitince tek bir mv ile asıl adına taşınır. Yarım kalan çalışma rotasyona ve aktarıma girmez.

Ayarlar betikten ayrı, /etc/yedek.conf dosyasında durur. Böylece betiği güncellerken ayarlarınız ezilmez:

bash
# /etc/yedek.conf : yedek.sh ve yedek-test.sh tarafından okunur
YEREL_DIZIN=/var/backups/yedek            # yerel yedeklerin tutulduğu dizin
KAYNAKLAR=(/etc /var/www /home /root)     # yedeklenecek dizinler
HARIC=('*/cache/*' '/home/*/.cache' '*.tmp' '*/node_modules/*')
PAROLA_DOSYASI=/root/.yedek-parola
MYSQL_AYAR=/root/.yedek-my.cnf            # boş bırakırsanız veritabanı yedeği alınmaz
UZAK_HEDEF="yedek@192.0.2.20:sunucu1"     # rrsync kökü /srv/yedek olduğu için göreli yol
SSH_ANAHTAR=/root/.ssh/yedek_ed25519
SSH_PORT=22
MIN_BOS_GB=5                              # yerel diskte en az bu kadar boş alan yoksa çalışmaz
GUNLUK_GUN=7                              # günlük yedekler kaç gün tutulur
HAFTALIK_GUN=35                           # haftalık (pazar) yedekler
AYLIK_GUN=370                             # aylık (ayın 1'i) yedekler
LOG_DOSYASI=/var/log/yedek.log

Ana betik: tar, mysqldump, şifreleme, rsync ve rotasyon

Aşağıdaki betik Ubuntu 24.04 ve Debian 12 için hazırlandı, GNU tar ve bash 5 bekler. Her çalışmada şunları yapar:

  • Dosyaları tar ile arşivler, zstd ile sıkıştırır ve şifreler.
  • Her veritabanını ayrı dosyaya döker.
  • SHA256 sağlama toplamı üretir.
  • Yedeği uzak sunucuya kopyalar.
  • Süresi dolan yerel yedekleri siler.

Geri almak için /usr/local/sbin/yedek.sh dosyasını ve /etc/yedek.conf'u silmeniz, aşağıdaki systemd birimlerini devre dışı bırakmanız yeterli. Betik sistemde başka bir şeyi değiştirmez.

bash
#!/usr/bin/env bash
# /usr/local/sbin/yedek.sh
# Dosya + MySQL/MariaDB yedeği, GPG şifreleme, rsync ile uzak kopya, sınıflı rotasyon.
# Ubuntu 24.04 ve Debian 12 için hazırlandı. Ayarlar: /etc/yedek.conf
set -Eeuo pipefail
umask 077
# cron/systemd ortamında PATH kısıtlı olabilir, açıkça belirliyoruz
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export LC_ALL=C

AYAR=/etc/yedek.conf
[[ -r $AYAR ]] || { echo "Ayar dosyası okunamadı: $AYAR" >&2; exit 2; }
# shellcheck source=/dev/null
source "$AYAR"
: "${YEREL_DIZIN:?}" "${PAROLA_DOSYASI:?}" "${MIN_BOS_GB:=5}"
: "${LOG_DOSYASI:=/var/log/yedek.log}"

# Yanlış ayarla sistem dizininde silme yapılmasını engelle
case "$YEREL_DIZIN" in
  /|/root|/home|/etc|/var|/usr|/srv) echo "Güvensiz YEREL_DIZIN: $YEREL_DIZIN" >&2; exit 2 ;;
esac

log()  { printf '%s [%s] %s\n' "$(date '+%F %T')" "$$" "$*" | tee -a "$LOG_DOSYASI"; }
trap 'log "HATA: satır $LINENO, komut: $BASH_COMMAND"' ERR

# Aynı anda tek çalışma
exec 9>/run/lock/yedek.lock
flock -n 9 || { log "Başka bir yedek çalışıyor, çıkılıyor."; exit 1; }

# Yedek sınıfı: ayın 1'i aylık, pazar haftalık, diğer günler günlük
if   [[ $(date +%d) == 01 ]]; then SINIF=aylik
elif [[ $(date +%u) == 7  ]]; then SINIF=haftalik
else SINIF=gunluk; fi
AD="$(date +%F_%H%M)_${SINIF}"
CALISMA="$YEREL_DIZIN/.${AD}.tmp"
SON="$YEREL_DIZIN/$AD"

mkdir -p "$YEREL_DIZIN"
# Başarısız çalışmada yalnız bu çalışmanın geçici dizinini temizle
trap '[[ -d "$CALISMA" ]] && rm -rf -- "$CALISMA"' EXIT
mkdir "$CALISMA"

BOS_GB=$(df --output=avail -BG "$YEREL_DIZIN" | tail -n1 | tr -dc '0-9')
if (( BOS_GB < MIN_BOS_GB )); then log "Yetersiz boş alan: ${BOS_GB}G"; exit 3; fi

sifrele() {
  gpg --batch --yes --quiet --pinentry-mode loopback --symmetric \
      --cipher-algo AES256 --passphrase-file "$PAROLA_DOSYASI" -o "$1"
}

# 1) Dosyalar
log "Başladı: $AD, kaynaklar: ${KAYNAKLAR[*]}"
HARIC_ARG=(--exclude="$YEREL_DIZIN")
for h in "${HARIC[@]}"; do HARIC_ARG+=(--exclude="$h"); done
{
  rc=0
  tar --create --file=- --acls --xattrs --numeric-owner \
      --warning=no-file-changed "${HARIC_ARG[@]}" "${KAYNAKLAR[@]}" 2>/dev/null || rc=$?
  # tar çıkış kodu 1: okunurken değişen dosya var, bunu hata saymıyoruz
  (( rc <= 1 ))
} | zstd -q -T0 -3 | sifrele "$CALISMA/dosyalar.tar.zst.gpg"
log "Dosya arşivi tamam."

# 2) Veritabanları (her biri ayrı dosya, CREATE DATABASE/USE satırı olmadan)
if [[ -n ${MYSQL_AYAR:-} ]]; then
  MYSQL_ARG=(--defaults-extra-file="$MYSQL_AYAR")
  DB_LISTE=$(mysql "${MYSQL_ARG[@]}" -N -B -e "SHOW DATABASES" \
             | grep -Ev '^(information_schema|performance_schema|sys)$' || true)
  while IFS= read -r db; do
    [[ -z $db ]] && continue
    mysqldump "${MYSQL_ARG[@]}" --single-transaction --quick --no-tablespaces \
      --routines --triggers --events --hex-blob --default-character-set=utf8mb4 "$db" \
      | zstd -q -T0 -3 | sifrele "$CALISMA/db_${db}.sql.zst.gpg"
    log "Veritabanı tamam: $db"
  done <<< "$DB_LISTE"
fi

# 3) Sağlama toplamı ve atomik taşıma
( cd "$CALISMA" && sha256sum -- *.gpg > SHA256SUMS )
mv -- "$CALISMA" "$SON"
log "Yerel yedek hazır: $SON ($(du -sh "$SON" | cut -f1))"

# 4) Uzak kopya (silme yok; uzak rotasyonu uzak sunucu yapar)
if [[ -n ${UZAK_HEDEF:-} ]]; then
  rsync -a --partial --timeout=600 \
    -e "ssh -i $SSH_ANAHTAR -p ${SSH_PORT:-22} -o BatchMode=yes -o StrictHostKeyChecking=yes" \
    "$SON/" "$UZAK_HEDEF/$AD/"
  log "Uzak kopya tamam: $UZAK_HEDEF/$AD"
fi

# 5) Yerel rotasyon: yalnız YEREL_DIZIN altındaki, adı sınıfla biten dizinler
temizle() {
  find "$YEREL_DIZIN" -mindepth 1 -maxdepth 1 -type d -name "*_$1" -mtime +"$2" \
       -print -exec rm -rf -- {} + | while read -r d; do log "Silindi: $d"; done
}
temizle gunluk   "${GUNLUK_GUN:-7}"
temizle haftalik "${HAFTALIK_GUN:-35}"
temizle aylik    "${AYLIK_GUN:-370}"
# Önceki çalışmalardan kalmış yarım geçici dizinler
find "$YEREL_DIZIN" -mindepth 1 -maxdepth 1 -type d -name '.*.tmp' -mtime +1 -exec rm -rf -- {} +

log "Bitti: $AD"

Kurulum ve ilk elle çalıştırma:

bash
sudo install -m 700 -o root -g root yedek.sh /usr/local/sbin/yedek.sh
sudo install -m 600 -o root -g root yedek.conf /etc/yedek.conf
sudo /usr/local/sbin/yedek.sh
ls -la /var/backups/yedek/

Beklenen çıktı, 2026-11-09_0215_gunluk biçiminde bir dizin. İçinde dosyalar.tar.zst.gpg, her veritabanı için bir db_ADI.sql.zst.gpg ve SHA256SUMS dosyası olmalı.

mysqldump ayrıntıları neden önemli

--single-transaction InnoDB tablolarında tabloları kilitlemeden tutarlı bir anlık görüntü alır. MyISAM tablolarında bu tutarlılık yoktur. Hâlâ MyISAM kullanıyorsanız InnoDB'ye geçmeyi değerlendirin. Veritabanı adının --databases olmadan verilmesi bilinçli bir tercih: dökümde CREATE DATABASE ve USE satırı olmaz, dolayısıyla geri yükleme testinde canlı veritabanının üstüne yazma riski kalmaz. Seçeneklerin tamamı için MySQL mysqldump belgesine bakabilirsiniz. Tek bir veritabanını başka sunucuya kopyalama senaryosu MySQL veritabanı kopyalama rehberinde ayrıca anlatılıyor.

Saklama süresi: günlük, haftalık, aylık rotasyon

Betik, sınıfı yedeğin adına yazar. Ayın 1'inde alınan yedek _aylik, pazar günü alınan _haftalik, diğerleri _gunluk olur. Silme kuralı da sınıfa göre çalışır. Varsayılan ayarla yerelde yaklaşık 7 günlük, 5 haftalık ve 12 aylık yedek kalır. Disk alanınızı hesaplarken tek bir yedeğin boyutunu du -sh ile ölçün ve kabaca 7 + 5 + 12 = 24 katını ayırın. Kaynak veri büyüdükçe bu hesabı yenileyin.

Uzak sunucuya yedek silme komutu gönderilmez. Kaynak sunucu ele geçirilse bile uzak kopyayı rsync üzerinden silmek zorlaşır. Uzak tarafın rotasyonunu uzak sunucunun kendisi yapar. Uzak sunucuda root crontab'ına (crontab -e) şu satırları ekleyin:

bash
# UZAK sunucu: her gece 05:00'te yalnız /srv/yedek/sunucu1 altındaki eski yedekleri siler
0 5 * * * find /srv/yedek/sunucu1 -mindepth 1 -maxdepth 1 -type d -name '*_gunluk'   -mtime +14  -exec rm -rf -- {} +
5 5 * * * find /srv/yedek/sunucu1 -mindepth 1 -maxdepth 1 -type d -name '*_haftalik' -mtime +60  -exec rm -rf -- {} +
10 5 * * * find /srv/yedek/sunucu1 -mindepth 1 -maxdepth 1 -type d -name '*_aylik'   -mtime +400 -exec rm -rf -- {} +

Silme komutunu etkinleştirmeden önce -exec rm -rf -- {} + kısmını kaldırıp -print ile çalıştırın ve listede yalnız beklediğiniz dizinlerin olduğunu görün. Uzak tarafta da yerelden daha uzun süre saklamak mantıklıdır: yerel kopya hızlı dönüş içindir, uzak kopya felaket içindir.

Uzak hedef olarak sunucu yerine S3 uyumlu depolama kullanmak isterseniz, object lock ile silinemez yedek kurulumu S3 yedekleme rehberinde restic ve rclone ile anlatılıyor.

systemd timer ile zamanlama ve başarısızlıkta e-posta uyarısı

Cron da iş görür ama systemd timer'ın üç avantajı var. Sunucu kapalıyken kaçan çalışmayı açılışta yapar (Persistent=true). Çıktı journal'a düşer. Hata olunca OnFailure ile otomatik bildirim tetikler. Seçeneklerin tamamını sunucuda man systemd.timer komutuyla açılan kılavuz sayfasında bulabilirsiniz.

Aşağıdaki birimler systemd 249 ve sonrası (Ubuntu 24.04, Debian 12, RHEL 9) için hazırlandı. Geri almak için systemctl disable --now yedek.timer çalıştırıp birim dosyalarını silin ve systemctl daemon-reload yapın.

ini
# /etc/systemd/system/yedek.service
[Unit]
Description=Sunucu yedekleme betiği
Wants=network-online.target
After=network-online.target mysql.service mariadb.service
OnFailure=yedek-bildir@%n.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/yedek.sh
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=6h
ini
# /etc/systemd/system/yedek.timer
[Unit]
Description=Her gece sunucu yedeği

[Timer]
OnCalendar=*-*-* 02:15:00
RandomizedDelaySec=10min
Persistent=true

[Install]
WantedBy=timers.target
ini
# /etc/systemd/system/yedek-bildir@.service
[Unit]
Description=Yedek hatası bildirimi (%i)

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/yedek-bildir.sh %i

Bildirim betiği, başarısız birimin durumunu ve son günlük satırlarını e-postayla gönderir. ORNEK_ALICI@example.com adresini kendi adresinizle değiştirin.

bash
#!/usr/bin/env bash
# /usr/local/sbin/yedek-bildir.sh : başarısız birimin özetini e-postayla gönderir
set -u
BIRIM="${1:?birim adı gerekli}"
ALICI="ORNEK_ALICI@example.com"
{
  echo "Sunucu: $(hostname -f)"
  echo "Zaman : $(date '+%F %T')"
  echo
  systemctl status --no-pager "$BIRIM"
  echo
  journalctl -u "$BIRIM" -n 60 --no-pager
} | mail -s "[YEDEK HATA] $(hostname -s) $BIRIM" "$ALICI"

Etkinleştirme ve doğrulama:

bash
sudo install -m 700 yedek-bildir.sh /usr/local/sbin/yedek-bildir.sh
sudo systemctl daemon-reload
sudo systemctl enable --now yedek.timer
systemctl list-timers yedek.timer          # NEXT sütununda bir sonraki 02:15 görünmeli
sudo systemctl start yedek-bildir@deneme.service   # e-postanın ulaştığını kontrol edin
sudo systemctl start yedek.service && journalctl -u yedek.service -n 20 --no-pager

Bildirim e-postası gelmiyorsa sorun büyük olasılıkla MTA yapılandırmasındadır. Bunu yedekten önce çözün, yoksa hata olduğunda kimse haberdar olmaz.

Geri yükleme testi: sağlama toplamı, arşivi açma, ayrı şemaya yükleme

Açılmamış bir yedeğin çalıştığı varsayımdan ibarettir. Aşağıdaki test betiği en son yedeği alıp şu adımları uygular:

  • Sağlama toplamını doğrular.
  • Arşivdeki dosya sayısını sayar ve örnek bir dosyayı çıkarıp canlı kopyayla karşılaştırır.
  • Her veritabanını yedektest_ önekli geçici bir şemaya yükler ve tablo sayısını karşılaştırır.

Betik Ubuntu 24.04 ve Debian 12 için hazırlandı. Veritabanı oluşturabilmesi için root olarak, MySQL/MariaDB'ye unix socket ile bağlanabilen bir ortamda çalışmalıdır. Yalnız yedektest_ önekli şemaları oluşturup siler, canlı veritabanlarına dokunmaz.

bash
#!/usr/bin/env bash
# /usr/local/sbin/yedek-test.sh : son yedeği doğrular ve ayrı şemaya yükler
set -Eeuo pipefail
umask 077
source /etc/yedek.conf
ORNEK_DOSYA="${1:-etc/hostname}"     # arşivden çıkarılıp karşılaştırılacak dosya

SON=$(find "$YEREL_DIZIN" -mindepth 1 -maxdepth 1 -type d -name '20*' | sort | tail -n1)
[[ -n $SON ]] || { echo "Yedek bulunamadı"; exit 1; }
echo "Test edilen yedek: $SON"
cd "$SON"

# 1) Bütünlük
sha256sum -c --quiet SHA256SUMS && echo "Sağlama toplamı: TAMAM"

GECICI=$(mktemp -d /var/tmp/yedektest.XXXXXX)
trap 'rm -rf -- "$GECICI"' EXIT
coz() { gpg --batch --quiet --pinentry-mode loopback --passphrase-file "$PAROLA_DOSYASI" -d "$1"; }

# 2) Dosya arşivi
ADET=$(coz dosyalar.tar.zst.gpg | zstd -dq | tar -tf - | wc -l)
echo "Arşivdeki öğe sayısı: $ADET"
coz dosyalar.tar.zst.gpg | zstd -dq | tar -xf - -C "$GECICI" "$ORNEK_DOSYA"
if cmp -s "/$ORNEK_DOSYA" "$GECICI/$ORNEK_DOSYA"; then
  echo "Örnek dosya ($ORNEK_DOSYA): canlı kopyayla aynı"
else
  echo "Örnek dosya ($ORNEK_DOSYA): FARKLI (yedekten sonra değişmiş olabilir)"
fi

# 3) Veritabanları: her birini yedektest_ADI şemasına yükle
shopt -s nullglob
for f in db_*.sql.zst.gpg; do
  db=${f#db_}; db=${db%.sql.zst.gpg}
  [[ $db == mysql ]] && continue          # sistem şemasını test şemasına yüklemiyoruz
  t="yedektest_${db}"
  mysql -e "DROP DATABASE IF EXISTS \`$t\`; CREATE DATABASE \`$t\`;"
  coz "$f" | zstd -dq | mysql "$t"
  say() { mysql -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$1'"; }
  echo "Veritabanı $db: canlı $(say "$db") tablo, yedekten yüklenen $(say "$t") tablo"
  mysql -e "DROP DATABASE \`$t\`;"
done
echo "Test bitti."

Beklenen çıktı aşağıdakine benzer. Tablo sayıları eşit olmalı. Satır sayıları ise canlı veri yedekten sonra değiştiği için farklı çıkabilir, bu normaldir.

bash
Test edilen yedek: /var/backups/yedek/2026-11-09_0215_gunluk
Sağlama toplamı: TAMAM
Arşivdeki öğe sayısı: 48213
Örnek dosya (etc/hostname): canlı kopyayla aynı
Veritabanı magaza: canlı 87 tablo, yedekten yüklenen 87 tablo
Test bitti.

Bu testi ayda en az bir kez, tercihen ayrı bir sunucuda çalıştırın. Ayrı sunucuda yapılan test, parolanın ve anahtarın gerçekten sunucu dışında da bulunduğunu da kanıtlar. Uzak kopyayı test etmek için uzak dizini geçici bir sunucuya çekip sha256sum -c SHA256SUMS komutunu orada çalıştırmanız yeterli.

Sık yapılan hatalar

  • Yedeğin aynı diskte durması: /var/backups aynı disktedir. Disk arızasında ya da fidye yazılımında yerel kopya da gider. Uzak kopya isteğe bağlı değildir.
  • Şifresiz arşiv: Uzak sunucuya ya da depolamaya giden arşivde müşteri verisi vardır. Şifre dosyasını kaybetmek de şifresiz bırakmak kadar kötüdür, parolayı sunucu dışında saklayın.
  • Cron ortam değişkenleri: Cron çok kısa bir PATH ile çalışır. Elle çalışan betik zamanlanınca mysqldump: command not found hatası verebilir. Betikteki export PATH satırı bu yüzden var.
  • Boru hattında hatanın kaybolması: pipefail olmadan mysqldump hata verse bile gpg başarılı döner ve boş bir yedek "başarılı" görünür.
  • GTID ve ikili günlük: GTID açık bir MySQL'de dökümü aynı sunucuya yüklerken GTID hatası alırsanız mysqldump'a --set-gtid-purged=OFF ekleyin (bu seçenek MariaDB'de yoktur). İkili günlük açıkken saklı yordam yükleme hatası alırsanız test ortamında log_bin_trust_function_creators ayarını inceleyin.
  • Hiç test edilmemiş yedek: Sorun genellikle ilk geri yüklemede ortaya çıkar. Test betiğini takvime bağlayın.

KVKK açısından yedeğin nerede saklanacağı

Kişisel veri içeren bir veritabanının yedeği de kişisel veridir. Ana sunucu Türkiye'de olsa bile uzak kopyayı yurt dışındaki bir sunucuya ya da depolamaya göndermek, KVKK açısından ayrıca değerlendirilmesi gereken bir aktarım olabilir. Karar vermeden önce şu üç soruyu yanıtlayın: yedekte hangi kişisel veri var, kopya hangi ülkede duruyor, kopyaya kimler erişebiliyor. Ayrıntılı çerçeve için KVKK açısından yedeğin konumu rehberine bakabilirsiniz. Şifreleme ve erişim kısıtlaması teknik tedbirler açısından önemlidir, ama konum sorusunu tek başına ortadan kaldırmaz.

İkinci bir sunucu kurup bakımını üstlenmek yerine yedeğin Türkiye'de ayrı bir altyapıda tutulmasını isterseniz bulut yedekleme hizmeti bu betiğin uzak hedefi olarak da düşünülebilir.

Sık sorulan sorular

mysqldump yerine fiziksel yedek (mariabackup, xtrabackup) ne zaman gerekir?

Veritabanı onlarca gigabayta çıktığında dökümün alınması ve özellikle geri yüklenmesi saatler sürebilir. Geri yükleme süresi kabul ettiğiniz kesinti süresini aşıyorsa fiziksel yedek araçlarını değerlendirin. Küçük ve orta boy veritabanlarında mysqldump taşınabilir ve okunabilir olduğu için pratiktir.

Yedek çalışırken site yavaşlar mı?

tar ve zstd CPU ve disk kullanır. Birimdeki Nice=10 ve IOSchedulingClass=idle ayarları yedeği diğer işlerin arkasına iter. --single-transaction InnoDB tablolarını kilitlemez. Yine de çalışmayı trafiğin en düşük olduğu saate koyun ve ilk haftalarda yük grafiğini izleyin.

Şifre dosyası da yedeğin içinde, bu sorun mu?

Değil, çünkü arşiv aynı parolayla şifreli ve parola olmadan açılamaz. Asıl risk parolanın yalnız sunucuda bulunmasıdır. Parolayı bir parola yöneticisinde ya da kasada ayrıca saklayın. Geri yükleme testini başka bir sunucuda yapmak bunu doğrulamanın en kolay yoludur.

Tek bir dosyayı ya da tek bir veritabanını nasıl geri yüklerim?

Tek dosya için arşivi çözüp yalnız o yolu çıkarın: gpg -d dosyalar.tar.zst.gpg | zstd -d | tar -xf - -C /var/tmp/geri var/www/site/config.php. Önce geçici bir dizine çıkarıp karşılaştırın, sonra yerine kopyalayın. Veritabanı için ilgili db_ADI.sql.zst.gpg dosyasını çözüp mysql ADI komutuna verin. Canlıya yüklemeden önce test şemasında denemeniz önerilir.

Cron mu systemd timer mı kullanmalıyım?

İkisi de çalışır. systemd timer, kaçan çalışmayı telafi ettiği, çıktıyı journal'da topladığı ve OnFailure ile hata bildirimi kolaylaştırdığı için önerilir. Cron kullanacaksanız betiği tam yoluyla çağırın ve çıktıyı bir günlük dosyasına yönlendirin.

Sonuç

Bu kurulum sonunda sunucunuz her gece dosyalarını ve veritabanlarını şifreli olarak arşivliyor. Yedekler başka bir sunucuya kopyalanıyor, eskileri sınıfına göre siliniyor ve hata olursa e-posta geliyor. Kurulumu bitmiş saymak için iki kontrol noktası var: hata bildirimi e-postasının gerçekten ulaşması ve yedek-test.sh komutunun başka bir sunucuda başarıyla tamamlanması. Bu ikisini görmediğiniz sürece elinizdeki şey yedek değil, yedek olduğu umulan bir dosyadır.

  • #sunucu yedekleme
  • #bash
  • #mysqldump
  • #rsync
  • #systemd timer
  • #geri yükleme
  • #ubuntu 24.04

Yazar

Kurucu, sunucu.com.tr · Bulut altyapısı ve yapay zeka otomasyonları

sunucu.com.tr ve Sunucu UK'in kurucusu. Bulut altyapısı, sanallaştırma ve sunucu yönetimi üzerine çalışıyor; şirketlerin iş yazılımlarını, web sitelerini ve yapay zeka iş yüklerini güvenli sunuculara taşıyor. Blogdaki rehberler sahada karşılaşılan kurulum, güvenlik ve taşıma işlerinden derlenir; yazılar yapay zeka desteğiyle hazırlanır ve kaynaklarla doğrulanır.

Tüm yazılarıProfil