rsync دستوری در لینوکس برای کپی و همگام‌سازی فایل‌ها و پوشه‌هاست، چه روی یک سیستم و چه بین دو سرور از طریق SSH. فرقش با cp و scp این است که فقط تفاوت‌ها را منتقل می‌کند: بار دوم که اجرایش کنید، فایل‌هایی که تغییر نکرده‌اند دوباره فرستاده نمی‌شوند. رایج‌ترین شکل استفاده این است:

bash
rsync -avzh --progress /var/www/app/ user@server:/var/www/app/

برای کنار گذاشتن بعضی فایل‌ها هم --exclude را اضافه می‌کنید، مثلاً --exclude='node_modules/' --exclude='*.log'. در ادامه معنی هر گزینه، قاعده‌ی مهم «اسلش آخر مسیر» که بیشترین اشتباه را به بار می‌آورد، تمرین با --dry-run، خطر --delete و چند سناریوی واقعی را می‌بینیم. مثال‌ها برای Ubuntu 24.04 نوشته شده‌اند.

نصب و پیش‌نیاز

rsync روی بیشتر توزیع‌ها از قبل نصب است. اگر نبود:

bash
sudo apt install -y rsync

نکته‌ای که زیاد فراموش می‌شود: برای انتقال بین دو سرور، rsync باید روی هر دو طرف نصب باشد؛ چون rsync سمت مقصد را از طریق SSH اجرا می‌کند و دو طرف با هم مقایسه می‌کنند. اگر خطای rsync: command not found از سمت مقصد گرفتید، دلیلش همین است.

برای انتقال از طریق SSH هم بهتر است ورود با کلید را راه انداخته باشید تا هر بار رمز نخواهد؛ جزئیاتش در کلید SSH و اتصال به سرور آمده است.

گزینه‌های پایه: -a -v -z -h --progress

گزینه معنی
-a حالت آرشیو: پوشه‌ها را بازگشتی کپی می‌کند و دسترسی‌ها، زمان تغییر، لینک‌های نمادین، مالک و گروه را حفظ می‌کند
-v گزارش فایل‌هایی که منتقل می‌شوند
-z فشرده‌سازی داده در مسیر انتقال
-h اعداد خوانا (K، M، G)
--progress نمایش پیشرفت هر فایل
-n یا --dry-run فقط نشان می‌دهد چه کار می‌کرد، بدون تغییر
-P معادل --partial --progress

-a تقریباً همیشه لازم است. بدون آن rsync پوشه‌ها را بازگشتی کپی نمی‌کند و زمان تغییر فایل‌ها را هم حفظ نمی‌کند، و بار بعد چون زمان‌ها فرق دارند، دوباره همه‌چیز را مقایسه می‌کند. توجه کنید که حفظ مالک فایل‌ها فقط وقتی ممکن است که سمت مقصد با root اجرا شود.

-z روی اینترنت مفید است، ولی برای فایل‌های از پیش فشرده (.gz، .zip، تصویر، ویدیو) سودی ندارد و فقط CPU می‌سوزاند. در شبکه‌ی محلی سریع هم معمولاً بدون آن سریع‌تر است.

اگر به‌جای پیشرفت هر فایل، پیشرفت کل انتقال را می‌خواهید، به جای --progress از --info=progress2 استفاده کنید.

قاعده‌ی اسلش آخر مسیر

مهم‌ترین نکته‌ی rsync همین است. اسلش آخرِ مسیر مبدأ تعیین می‌کند خود پوشه کپی شود یا محتوایش:

bash
# با اسلش: محتوای site داخل backup ریخته می‌شود
rsync -a site/ backup/
# نتیجه: backup/index.php  backup/css/ ...

# بدون اسلش: خود پوشه‌ی site داخل backup ساخته می‌شود
rsync -a site backup/
# نتیجه: backup/site/index.php  backup/site/css/ ...

یک راه به یاد سپردن: site/ یعنی «آنچه داخل site است» و site یعنی «خود site».

اسلش آخرِ مسیر مقصد فرق چندانی ایجاد نمی‌کند؛ backup و backup/ هر دو یک پوشه‌اند و اگر نباشد ساخته می‌شود. ولی برای یکدستی، بهتر است همیشه شکل src/ dest/ را بنویسید تا با یک نگاه معلوم باشد دو پوشه را همگام می‌کنید.

اشتباه رایج: کسی rsync -a /var/www/app user@server:/var/www/app می‌زند و بعد روی سرور با /var/www/app/app روبه‌رو می‌شود. اگر --delete هم در کار بود، نتیجه بدتر هم می‌شود. برای همین بخش بعد مهم است.

همیشه اول --dry-run

قبل از هر rsync که مطمئن نیستید، همان دستور را با -n (یا --dry-run) اجرا کنید. هیچ چیز تغییر نمی‌کند، فقط فهرست کارهایی که انجام می‌شد را می‌بینید:

bash
rsync -avhn --delete /var/www/app/ user@server:/var/www/app/

با -i (--itemize-changes) هم برای هر فایل یک کد کوتاه می‌گیرید که می‌گوید چرا منتقل می‌شود (فایل جدید، تغییر اندازه، تغییر زمان و …). خطوطی که با deleting شروع می‌شوند را با دقت بخوانید. وقتی خروجی همان بود که انتظار داشتید، -n را بردارید و دوباره اجرا کنید.

--delete و خطرش

به‌طور پیش‌فرض rsync فقط اضافه و به‌روز می‌کند؛ اگر فایلی را از مبدأ پاک کنید، در مقصد می‌ماند. --delete این را عوض می‌کند: هر چیزی که در مبدأ نیست، از مقصد پاک می‌شود تا دو طرف دقیقاً یکسان شوند.

این گزینه برای انتشار یک نسخه‌ی تمیز مفید است، ولی سه خطر جدی دارد:

  1. جابه‌جا نوشتن مبدأ و مقصد. اگر جای دو مسیر را اشتباه بنویسید، rsync نسخه‌ی قدیمی را روی نسخه‌ی جدید همگام می‌کند و فایل‌های جدید را پاک می‌کند.
  2. مبدأ خالی یا اشتباه. اگر مبدأ به هر دلیلی خالی باشد (دیسک mount نشده، متغیر خالی در اسکریپت)، --delete کل مقصد را خالی می‌کند.
  3. پشتیبان‌گیری. اگر فایلی در مبدأ خراب یا پاک شود، --delete همان خرابی را به نسخه‌ی پشتیبان هم منتقل می‌کند. برای همین در مقاله‌ی پشتیبان‌گیری ۳-۲-۱ عمداً از --delete استفاده نکرده‌ایم.

اگر --delete لازم است، چند لایه احتیاط بگذارید:

bash
# فایل‌های پاک‌شده یا بازنویسی‌شده را به جای حذف، کنار بگذار
rsync -a --delete --backup --backup-dir=/var/backups/rsync-$(date +%F) \
  /var/www/app/ /srv/app-mirror/

# بیش از ۱۰۰ فایل پاک نکن؛ بقیه‌ی حذف‌ها رد می‌شوند و rsync با کد خطا تمام می‌شود
rsync -a --delete --max-delete=100 /var/www/app/ /srv/app-mirror/

--backup-dir اگر مسیر نسبی بدهید، نسبت به مقصد حساب می‌شود؛ برای همین مسیر مطلق بنویسید.

کنار گذاشتن فایل‌ها: --exclude و --exclude-from

bash
rsync -avh \
  --exclude='.git/' \
  --exclude='node_modules/' \
  --exclude='*.log' \
  --exclude='/storage/logs/' \
  ./ user@server:/var/www/app/

چند قاعده درباره‌ی الگوها:

  • الگوها نسبت به ریشه‌ی انتقال (همان مبدأ) سنجیده می‌شوند، نه نسبت به ریشه‌ی سیستم.
  • الگوی بدون / در ابتدا در هر عمقی پیدا می‌شود: node_modules/ هر پوشه‌ای با این نام را در هر جای درخت کنار می‌گذارد.
  • / در ابتدا الگو را به ریشه‌ی انتقال می‌چسباند: /storage/logs/ فقط همان مسیر را، نه vendor/x/storage/logs/ را.
  • / در انتها یعنی «فقط پوشه».
  • الگو را داخل کوتیشن بگذارید تا شل خودش * را باز نکند.

وقتی فهرست بلند شد، آن را در یک فایل بنویسید:

deploy-exclude.txt
.git/
.env
node_modules/
/storage/logs/
/storage/framework/cache/
*.log
bash
rsync -avh --exclude-from=deploy-exclude.txt ./ user@server:/var/www/app/

اگر --delete هم دارید، بدانید فایل‌های exclude‌شده در مقصد پاک نمی‌شوند؛ یعنی .env سرور سالم می‌ماند. مگر اینکه --delete-excluded بدهید، که آن‌ها را هم پاک می‌کند.

انتقال از طریق SSH با پورت غیراستاندارد

اگر SSH سرور روی پورتی غیر از ۲۲ است، با -e دستور SSH را به rsync بدهید:

bash
rsync -avzh -e "ssh -p 2222" /var/www/app/ deploy@server:/var/www/app/

# با کلید مشخص
rsync -avzh -e "ssh -p 2222 -i ~/.ssh/deploy_key" ./ deploy@server:/var/www/app/

جهت انتقال را هم می‌شود برعکس کرد و از سرور به سیستم خودتان کشید:

bash
rsync -avzh -e "ssh -p 2222" deploy@server:/var/backups/db/ ./db-backups/

راه تمیزتر این است که پورت و کاربر و کلید را در ~/.ssh/config تعریف کنید؛ آن‌وقت rsync هم بدون -e از همان تنظیمات استفاده می‌کند و فقط می‌نویسید rsync -avzh ./ app:/var/www/app/.

ادامه‌ی انتقال فایل‌های بزرگ

اگر وسط انتقال یک فایل چند گیگابایتی اتصال قطع شود، rsync به‌طور پیش‌فرض بخش منتقل‌شده را دور می‌اندازد. با --partial آن بخش نگه داشته می‌شود و اجرای بعدی از روی آن ادامه می‌دهد:

bash
rsync -avh -P -e "ssh -p 2222" big-dump.sql.gz deploy@server:/var/backups/

-P همان --partial --progress است. اگر نمی‌خواهید فایل نیمه‌کاره با نام اصلی در مقصد دیده شود (مثلاً برنامه‌ای پوشه را می‌پاید)، --partial-dir=.rsync-partial را اضافه کنید تا تکه‌ها در پوشه‌ای جدا بمانند. برای انتقال‌های خیلی طولانی هم rsync را داخل tmux اجرا کنید تا قطع SSH آن را نکشد.

rsync، scp یا cp؟

معیار rsync scp cp
بین دو سیستم بله (SSH) بله (SSH) خیر
فقط انتقال تغییرات بله خیر، همیشه کامل خیر
ادامه‌ی انتقال قطع‌شده بله (--partial) خیر خیر
exclude بله خیر خیر
همگام‌سازی با حذف بله (--delete) خیر خیر
dry-run بله خیر خیر
نیاز به نصب در مقصد بله خیر —
مناسب برای پوشه‌ها، تکرار، پشتیبان یک فایل سریع کپی محلی ساده

قاعده‌ی سرانگشتی: برای یک فایل که یک بار جابه‌جا می‌شود scp کافی است. هر چیزی که پوشه است، تکرار می‌شود یا بزرگ است، کار rsync است.

جمع‌بندی

  • شکل پایه را به خاطر بسپارید: rsync -avh --progress src/ dest/.
  • اسلش آخر مبدأ یعنی «محتوا»، بدونش یعنی «خود پوشه».
  • هر دستور تازه را اول با -n اجرا کنید، به‌خصوص اگر --delete دارد.
  • --delete را در پشتیبان‌گیری کنار بگذارید و جاهای دیگر با --backup-dir یا --max-delete مهارش کنید.
  • فهرست exclude را در فایل نگه دارید و پورت SSH را در ~/.ssh/config.

اگر rsync را در یک اسکریپت زمان‌بندی‌شده می‌گذارید، اسکریپت‌نویسی bash و کرون جاب در لینوکس قدم‌های بعدی‌اند.