Uzak Sunucuya Yedekleme: rsync ve SSH Entegrasyonu
Yedek almak sıkıcı bir iş gibi görünür, ta ki bir şeyler patlayana kadar. O an geldiğinde, düzgün kurulmuş bir rsync + SSH pipeline’ı seni kurtarır ya da kurtarmaz. Bu yazıda ikinci senaryoya düşmemek için ne yapman gerektiğini adım adım anlatacağım.
Neden rsync ve SSH?
Piyasada onlarca yedekleme aracı var. Bacula, Amanda, Veeam, Duplicati… Ama rsync hâlâ vazgeçilmez, çünkü:
- Sadece değişen blokları transfer eder. 100 GB’lık bir dizinde 50 MB değişmişse, sadece 50 MB gider.
- SSH ile birlikte kullanıldığında şifreli ve güvenli bir kanal üzerinden çalışır.
- Neredeyse her Linux/macOS sisteminde kurulu gelir.
- Scripting için son derece esnek bir yapısı vardır.
Basit bir senaryoyla başlayalım: Üretim sunucun prod-server (192.168.1.10), yedek sunucun backup-server (192.168.1.20). Amacımız prod-server’daki kritik dizinleri backup-server’a düzenli aralıklarla, güvenli biçimde yedeklemek.
SSH Anahtarı Kurulumu: Şifresiz Ama Güvenli
Otomatik yedekleme için SSH bağlantısı interaktif şifre sormadan çalışmalı. Bunun için anahtar tabanlı kimlik doğrulama kullanıyoruz. Ama “şifresiz = güvensiz” diye düşünme, aksine doğru yapılandırılmış anahtar sistemi şifreden çok daha güvenli.
Önce prod-server üzerinde yedekleme işini çalıştıracak bir kullanıcı için anahtar oluşturuyoruz:
# prod-server üzerinde
ssh-keygen -t ed25519 -C "backup-prod-$(date +%Y%m%d)" -f ~/.ssh/backup_key
# Passphrase sorusunda Enter'a bas (otomasyonu için boş bırakıyoruz)
Ed25519 kullanmamın nedeni RSA’dan daha modern, daha küçük ve daha hızlı olması. 4096-bit RSA ile aynı güvenlik seviyesini çok daha küçük bir anahtar boyutuyla sağlıyor.
Şimdi public key’i backup-server’a kopyalayalım:
# prod-server üzerinde
ssh-copy-id -i ~/.ssh/backup_key.pub [email protected]
Backup-server tarafında bu bağlantıyı biraz daha sıkılaştırabiliriz. ~/.ssh/authorized_keys dosyasına eklenen satırın önüne kısıtlamalar ekleyebilirsin:
# backup-server: ~/.ssh/authorized_keys dosyasında
command="/usr/bin/rsync --server --daemon .",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA...buraya_public_key... backup-prod-20240115
Bu sayede bu anahtar sadece rsync komutunu çalıştırabilir, sunucuya tam shell erişimi veremez. Üretim ortamında bu adımı atlama.
Temel rsync + SSH Komutu
Karmaşık şeylere girmeden önce temel komutu görelim:
rsync -avz --delete
-e "ssh -i ~/.ssh/backup_key -p 22"
/var/www/html/
[email protected]:/backups/prod/www/
Kullandığım parametreleri açıklayayım:
- -a: Archive modu. Sembolik linkler, izinler, zaman damgaları, owner/group bilgilerini korur. Aslında
-rlptgoDflaglerinin kısaltması. - -v: Verbose. Ne transfer edildiğini gösterir.
- -z: Transfer sırasında sıkıştırma uygular. Düşük bant genişliğinde işe yarar.
- –delete: Kaynaktan silinmiş dosyaları hedeften de siler. Dikkatli kullan!
- -e: Shell belirtimi. SSH parametrelerini buraya ekliyoruz.
--delete bayrağı hayat kurtarır ama aynı zamanda veri kaybına da yol açabilir. Eğer kaynak tarafında yanlışlıkla bir dosyayı sildiysen, bir sonraki rsync çalıştığında hedeften de silecek. Bunun için ya versiyonlama ya da --backup parametresi kullanmalısın.
Gerçek Dünya Senaryosu 1: Web Sunucusu Yedeklemesi
Bir müşterinin WordPress sitesi üzerinde çalışıyorum diyelim. /var/www, /etc/nginx, MySQL dump’ları yedeklenmeli. İşte bunu yapacak script:
#!/bin/bash
# /usr/local/bin/backup-webserver.sh
set -euo pipefail
# Değişkenler
BACKUP_SERVER="[email protected]"
BACKUP_BASE="/backups/prod-web"
SSH_KEY="/root/.ssh/backup_key"
LOG_FILE="/var/log/backup-webserver.log"
DATE=$(date +%Y-%m-%d)
MYSQL_PASS="$(cat /etc/backup-secrets/mysql_pass)"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# MySQL dump
log "MySQL dump başlıyor..."
mysqldump -u root -p"${MYSQL_PASS}" --all-databases
--single-transaction --quick
| gzip > /tmp/mysql-backup-${DATE}.sql.gz
log "MySQL dump tamamlandı."
# Web dosyaları
log "Web dosyaları sync ediliyor..."
rsync -az --delete
--exclude="*.log"
--exclude="wp-content/cache/"
--exclude=".git/"
-e "ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no -o ConnectTimeout=30"
/var/www/
"${BACKUP_SERVER}:${BACKUP_BASE}/www/"
# Nginx config
log "Nginx config sync ediliyor..."
rsync -az
-e "ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no"
/etc/nginx/
"${BACKUP_SERVER}:${BACKUP_BASE}/nginx-conf/"
# MySQL dump transfer
log "MySQL dump transfer ediliyor..."
rsync -az
-e "ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no"
/tmp/mysql-backup-${DATE}.sql.gz
"${BACKUP_SERVER}:${BACKUP_BASE}/mysql/"
# Temizlik
rm -f /tmp/mysql-backup-${DATE}.sql.gz
log "Tüm yedeklemeler tamamlandı."
--exclude parametreleri önemli. Cache klasörlerini, log dosyalarını, .git dizinlerini yedeklemek hem yer kaybettirir hem de transfer süresini uzatır.
Bandwidth Throttling: Ağı Boğma
Üretim ortamında gündüz yedekleme yapıyorsan bandwidth’i kontrol altında tutman gerekir. rsync’in --bwlimit parametresi MB/s cinsinden limit koymana izin verir:
rsync -az --delete
--bwlimit=10240
-e "ssh -i ~/.ssh/backup_key"
/data/
[email protected]:/backups/data/
Burada --bwlimit=10240 değeri 10 MB/s anlamına geliyor (KB/s cinsinden). Gece 02:00’de çalıştıracaksan limiti kaldırabilirsin, gündüz çalıştıracaksan trafiğe göre ayarla.
Incremental Yedekleme: –link-dest ile Snapshot Almak
rsync’in en güçlü özelliklerinden biri --link-dest. Bu parametre sayesinde her gün tam kopya gibi görünen ama aslında sadece değişen dosyaları depolayan snapshot’lar oluşturabilirsin. Hard link sihri sayesinde disk alanı da verimli kullanılır.
#!/bin/bash
# /usr/local/bin/snapshot-backup.sh
BACKUP_SERVER="[email protected]"
SSH_KEY="/root/.ssh/backup_key"
REMOTE_BASE="/backups/snapshots"
DATE=$(date +%Y-%m-%d_%H-%M)
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
# Dünkü snapshot'ı link-dest olarak kullan
rsync -az --delete
--link-dest="${REMOTE_BASE}/latest"
-e "ssh -i ${SSH_KEY}"
/var/www/
"${BACKUP_SERVER}:${REMOTE_BASE}/${DATE}/"
# 'latest' symlink'ini güncelle
ssh -i "${SSH_KEY}" "${BACKUP_SERVER}"
"ln -sfn ${REMOTE_BASE}/${DATE} ${REMOTE_BASE}/latest"
# 30 günden eski snapshot'ları temizle
ssh -i "${SSH_KEY}" "${BACKUP_SERVER}"
"find ${REMOTE_BASE} -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +"
Bu script sayesinde backup-server’da /backups/snapshots/ altında her güne ait klasörler oluşur. Her klasör sanki tam kopya gibi görünür ama aslında değişmeyen dosyalar önceki snapshot’la hard link paylaşır. Disk tasarrufu muazzam olur.
Gerçek Dünya Senaryosu 2: Çok Sunuculu Ortam
5 uygulama sunucusu olan bir yapıda her sunucudan merkezi backup sunucusuna yedek alıyoruz diyelim. Her sunucu için ayrı script yazmak yerine parametrik bir yapı kuruyoruz:
#!/bin/bash
# /usr/local/bin/multi-server-backup.sh
set -euo pipefail
BACKUP_SERVER="[email protected]"
SSH_KEY="/root/.ssh/backup_key"
LOG_DIR="/var/log/backups"
DATE=$(date +%Y-%m-%d)
mkdir -p "$LOG_DIR"
# Sunucu listesi: "kaynak_sunucu:port:kaynak_dizin:hedef_dizin"
SERVERS=(
"web01.internal:22:/var/www:/backups/web01/www"
"web02.internal:22:/var/www:/backups/web02/www"
"app01.internal:22:/opt/app:/backups/app01/app"
"db01.internal:22:/var/lib/postgresql:/backups/db01/postgres"
)
backup_server() {
local entry="$1"
local host=$(echo "$entry" | cut -d: -f1)
local port=$(echo "$entry" | cut -d: -f2)
local src=$(echo "$entry" | cut -d: -f3)
local dst=$(echo "$entry" | cut -d: -f4)
local log_file="${LOG_DIR}/${host}-${DATE}.log"
echo "[$(date '+%H:%M:%S')] ${host} yedekleniyor..." | tee -a "$log_file"
# Kaynak sunucudan backup sunucusuna rsync (pull modeli)
# Bu script backup sunucusunda çalışıyor
rsync -az --delete
--timeout=120
--log-file="$log_file"
-e "ssh -i ${SSH_KEY} -p ${port} -o ConnectTimeout=30 -o StrictHostKeyChecking=accept-new"
"backup_user@${host}:${src}/"
"${dst}/" 2>&1 | tee -a "$log_file"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo "[$(date '+%H:%M:%S')] ${host} başarıyla yedeklendi." | tee -a "$log_file"
else
echo "[$(date '+%H:%M:%S')] HATA: ${host} yedeklemesi başarısız!" | tee -a "$log_file"
# Buraya mail/Slack notification ekleyebilirsin
return 1
fi
}
# Paralel çalıştır
for server in "${SERVERS[@]}"; do
backup_server "$server" &
done
# Tüm background işlerinin bitmesini bekle
wait
echo "Tüm yedeklemeler tamamlandı."
Bu script backup sunucusunda çalışıyor ve kaynak sunuculardan pull modeli ile veri çekiyor. Bu yaklaşımın avantajı şu: Kaynak sunucu ele geçirilse bile, saldırgan backup sunucusuna SSH erişimi olmadığı için yedekleri silemez ya da şifreleyemez. Ransomware senaryolarına karşı kritik bir önlem.
cron ile Otomasyona Almak
Script hazır, şimdi otomasyona alalım. /etc/cron.d/backups dosyası oluşturalım:
# /etc/cron.d/backups
# Ortam değişkenleri
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
[email protected]
# Web sunucusu yedekleme: Her gece 02:00'de
0 2 * * * root /usr/local/bin/backup-webserver.sh >> /var/log/backup-webserver.log 2>&1
# Snapshot yedekleme: Hafta içi her gün 03:00'de
0 3 * * 1-5 root /usr/local/bin/snapshot-backup.sh >> /var/log/snapshot-backup.log 2>&1
# Haftalık tam yedek: Pazar 04:00'de
0 4 * * 0 root /usr/local/bin/full-backup.sh >> /var/log/full-backup.log 2>&1
MAILTO satırı cron’ın çıktı ürettiğinde mail göndermesini sağlar. Script hata üretirse anında haberdar olursun.
Yedekleme Doğrulama: Almak Yetmez, Test Etmek Şart
Yedekleme dünyasında altın kural: Test edilmemiş yedek, yedek değildir. Haftalık otomatik doğrulama scripti:
#!/bin/bash
# /usr/local/bin/verify-backup.sh
BACKUP_SERVER="[email protected]"
SSH_KEY="/root/.ssh/backup_key"
REPORT_FILE="/tmp/backup-report-$(date +%Y-%m-%d).txt"
{
echo "=== Yedekleme Doğrulama Raporu ==="
echo "Tarih: $(date)"
echo ""
# Backup sunucusundaki disk kullanımını kontrol et
echo "--- Disk Kullanımı ---"
ssh -i "$SSH_KEY" "$BACKUP_SERVER" "df -h /backups && du -sh /backups/*"
echo ""
echo "--- Son 24 Saatte Değişen Dosyalar ---"
ssh -i "$SSH_KEY" "$BACKUP_SERVER"
"find /backups -newer /backups/.last_check -type f | wc -l"
# Checksum doğrulama: Kaynak ve hedefteki kritik dosyaları karşılaştır
echo ""
echo "--- Kritik Dosya Checksum Kontrolü ---"
LOCAL_HASH=$(md5sum /etc/nginx/nginx.conf | awk '{print $1}')
REMOTE_HASH=$(ssh -i "$SSH_KEY" "$BACKUP_SERVER"
"md5sum /backups/prod-web/nginx-conf/nginx.conf 2>/dev/null | awk '{print $1}'")
if [ "$LOCAL_HASH" = "$REMOTE_HASH" ]; then
echo "nginx.conf: OK (${LOCAL_HASH})"
else
echo "UYARI: nginx.conf checksumları eşleşmiyor!"
echo " Lokal: $LOCAL_HASH"
echo " Remote: $REMOTE_HASH"
fi
# Marker dosyası güncelle
ssh -i "$SSH_KEY" "$BACKUP_SERVER" "touch /backups/.last_check"
} > "$REPORT_FILE" 2>&1
# Raporu mail gönder
mail -s "Haftalık Yedek Doğrulama Raporu" [email protected] < "$REPORT_FILE"
SSH Config ile Hayatı Kolaylaştırmak
Uzun -e "ssh -i ... -o ..." parametreleri yazmaktan kurtulmak için ~/.ssh/config dosyasını kullan:
# ~/.ssh/config
Host backup-server
HostName 192.168.1.20
User backup_user
IdentityFile ~/.ssh/backup_key
Port 22
ConnectTimeout 30
ServerAliveInterval 60
ServerAliveCountMax 3
Compression yes
Cipher [email protected]
Artık rsync komutların çok daha temiz görünür:
rsync -az --delete
/var/www/
backup-server:/backups/www/
Cipher [email protected] satırına dikkat: AES-128-GCM, AES-256’dan daha hızlı ve yerel ağ içinde yeterince güvenli. Büyük veri transferlerinde ciddi fark yaratır.
Sık Karşılaşılan Sorunlar ve Çözümleri
“Permission denied” hatası alıyorum: Authorized_keys dosyasının izinlerine bak. .ssh dizini 700, authorized_keys dosyası 600 olmalı. Çoğu zaman sorun buradan çıkar.
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
rsync sonucu her seferinde aynı dosyaları transfer ediyor: Kaynak ve hedef sunucuların sistem saatleri senkronize olmayabilir. ntpd veya chrony çalışıyor mu kontrol et. Zaman farkı varsa rsync dosyaların değiştiğini düşünür.
timedatectl status
chronyc tracking
Büyük dosya transferi ortada kesiliyor: --partial ve --append-verify parametrelerini kullan. Bağlantı kopsa bile kaldığı yerden devam eder:
rsync -az --partial --append-verify
-e "ssh -i ~/.ssh/backup_key"
/data/büyük-dosya.tar.gz
backup-server:/backups/
Sembolik linklerin hedefte bozuk görünmesi: -a zaten -l içeriyor ama hedef dizin yapısı farklıysa linkler kırılabilir. Bu durumda --copy-unsafe-links parametresini dene.
Güvenlik Sıkılaştırması
Backup sunucusu kritik bir hedef. Birkaç ek önlem:
rsync daemon yerine SSH over rsync kullan. rsync daemon (873 portu) açık bırakma, her şeyi SSH üzerinden yönet.
Backup kullanıcısının shell’ini kısıtla:
# /etc/passwd'de backup kullanıcısı
backup_user:x:1001:1001::/home/backup_user:/usr/sbin/nologin
Yedek transferi için shell’e gerek yok, nologin yeterli.
Backup dizinini immutable yap (opsiyonel, çok katmanlı koruma için):
# Yedek tamamlandıktan sonra
chattr +i /backups/critical-data/
# Değişiklik gerektiğinde
chattr -i /backups/critical-data/
Bu yöntem ransomware senaryolarında faydalı ama otomatik yedeklemeyle çelişir, dikkatli kullan.
İzleme ve Alerting
Script çalışıp çalışmadığını nasıl anlarsın? Basit bir “canary” yöntemi:
# backup-webserver.sh sonuna ekle
EXPECTED_SIZE=1000 # KB cinsinden minimum beklenen boyut
ACTUAL_SIZE=$(ssh -i "$SSH_KEY" "$BACKUP_SERVER"
"du -sk /backups/prod-web/ | awk '{print $1}'")
if [ "$ACTUAL_SIZE" -lt "$EXPECTED_SIZE" ]; then
echo "KRITIK: Yedek boyutu beklenenden küçük! (${ACTUAL_SIZE}KB)" |
mail -s "ALARM: Yedekleme Hatası" [email protected]
fi
Daha gelişmiş izleme için Prometheus + Grafana stack’ine rsync metriklerini besleyebilir ya da Zabbix’te custom item olarak son yedek zamanını takip edebilirsin.
Sonuç
rsync + SSH kombinasyonu, onlarca yıldır sysadmin’lerin güvendiği kanıtlanmış bir çift. Karmaşık backup yazılımlarına gerek duymadan güvenli, hızlı ve esnek bir yedekleme altyapısı kurabilirsin.
Özetlemek gerekirse kritik noktalara vurgu yapalım:
- SSH anahtarlarını doğru yapılandır, şifre istemeden ama güvenli çalışsın
- Pull modeli tercih et, backup sunucusu kaynaklara bağlanmalı, tersi değil
- –link-dest ile snapshot yaklaşımını benimse, disk tasarrufu ve versiyon geçmişi için
- Her yedeklemeyi logla, sorun çıktığında araştıracak verin olsun
- Düzenli olarak restore testi yap, yedek almak yetmez, geri yükleyebilmek şart
En iyi backup sistemi, felakete uğradıktan sonra kurduğun sistem değil, felaketten önce kurduğun ve test ettiğin sistemdir. O güne kadar beklemeden yapılandırmana başla.
