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.
Batuhan Çelikel15 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
mailkomutu, yani yapılandırılmış bir MTA ya da SMTP aktarımı.
Gerekli paketleri kurun:
# 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.
# 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.
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:
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:
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.
# 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:
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:
- Hata anında durmak:
set -Eeuo pipefailsayesinde boru hattının herhangi bir aşaması (örneğinmysqldump) hata verirse betik durur ve yarım dosya "başarılı yedek" gibi görünmez. - Tek çalışma:
flockile kilit alınır. Önceki çalışma bitmeden ikincisi başlamaz. - Atomik sonuç: Yedek önce gizli bir geçici dizine yazılır, her şey bitince tek bir
mvile 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:
# /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ı
tarile arşivler,zstdile 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.
#!/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:
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:
# 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.
# /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
# /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
# /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.
#!/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:
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.
#!/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.
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/backupsaynı 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
PATHile çalışır. Elle çalışan betik zamanlanıncamysqldump: command not foundhatası verebilir. Betiktekiexport PATHsatırı bu yüzden var. - Boru hattında hatanın kaybolması:
pipefailolmadanmysqldumphata verse bilegpgbaş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=OFFekleyin (bu seçenek MariaDB'de yoktur). İkili günlük açıkken saklı yordam yükleme hatası alırsanız test ortamındalog_bin_trust_function_creatorsayarı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.