
به اشتراک بگذارید

به اشتراک بگذارید
راهاندازی سرور دانلود یعنی فایلهای حجیم را از وبسرور سایت جدا کنید و یک nginx مستقل فقط برای تحویل فایل تنظیم شود. چهار چیز دست خودتان است: نوع فایل، سرعت هر دانلود، تعداد اتصال همزمان و اعتبار زمانی لینک. سه مورد اول با ماژولهای همراه بسته آماده nginx انجام میشود؛ لینک امضاشده استثناست.
| خواسته شما | ابزار در nginx | چیزی که از قلم میافتد |
|---|---|---|
| تحویل فایل بزرگ | دستورهای sendfile، aio و directio | دستور directio همان درخواست را از sendfile بیرون میبرد |
| ادامه دانلود نیمهکاره | پشتیبانی Range و دستور max_ranges | مقدار صفر، بازه بایتی را کامل خاموش میکند |
| سقف سرعت | دستورهای limit_rate و limit_rate_after | سقف روی هر درخواست است، نه هر کاربر |
| سهمیه اتصال | دستورهای limit_conn_zone و limit_conn | در HTTP/2 هر درخواست همزمان یک اتصال شمرده میشود |
| لینک موقت امضاشده | ماژول secure_link | پیشفرض ساخته نمیشود؛ با nginx -V بررسی کنید |
💡 سرور دانلود در یک جمله
سروری که تنها کارش تحویل فایل است و با وبسرور صفحههای سایت یکی نیست.
✅ قبل از هر کانفیگ خروجی nginx -V را بخوانید؛ همین دستور میگوید کدام ماژول در بسته شما ساخته شده است.
✅ کانفیگهای این مقاله به دسترسی root و فایل nginx.conf در اختیار خودتان نیاز دارند.
فرق یک سرور دانلود روان با یک سرور کُند، بیشتر در چند خط کانفیگ است تا در سختافزار گرانتر. از ساختار پوشه شروع میکنیم، بعد تحویل فایل حجیم، سپس مهار سرعت و اتصال و آخرِ کار لینک موقت. همه این دستورها روی هر سروری با دسترسی root اجرا میشوند، از جمله سرور مجازی ایران. هر دستور از مستندات رسمی nginx نقل شده و کنارش نوشتهام چه چیزی را حل نمیکند.
📌 آنچه در این مقاله میخوانید
◀️ ساختار پوشهها، فهرست فایل و نوع محتوا
◀️ تحویل فایل حجیم: sendfile، aio، directio و ادامه دانلود
◀️ مهار پهنای باند: سقف سرعت، سهمیه اتصال و حساب ترافیک
◀️ لینک موقت امضاشده، تست نهایی و اشتباههای پرتکرار

راهاندازی سرور دانلود در سادهترین شکل، یک بلوک server جدا روی زیردامنهای مثل dl.example.ir است که ریشهاش به پوشه فایلها اشاره میکند و هیچ کد برنامهای در آن اجرا نمیشود. سود این جدایی روشن است: سقف سرعت، سهمیه اتصال و لاگ جداگانه فقط روی مسیر فایلها مینشیند.
طبق مستندات ngx_http_core_module، دستور root مسیر فایل را «با افزودن ساده URI به مقدار root» میسازد؛ با location /i/ و root /data/w3; فایل /data/w3/i/top.gif پاسخ درخواست /i/top.gif است. اگر نام مسیر در URL با نام پوشه روی دیسک یکی نیست، alias لازم است.
server {
listen 80;
server_name dl.example.ir;
root /srv/files;
location /download/ {
autoindex off; # پیشفرض؛ هشدار پایین را بخوانید
# autoindex on; # فقط برای پوشه کاملاً عمومی
autoindex_exact_size off;
autoindex_localtime on;
}
}
ماژول ngx_http_autoindex_module درخواستهایی را که به اسلش تمام میشوند میگیرد و فهرست پوشه را میسازد؛ مستندات میگوید معمولاً وقتی به این ماژول میرسد که ماژول index فایل ایندکس را پیدا نکرده باشد. پیشفرضش off است. autoindex_exact_size off اندازهها را گرد میکند، autoindex_localtime پیشفرض off است و زمان را UTC میدهد، و autoindex_format (از 1.7.9) خروجی json هم میدهد.
⚠️ فهرست باز یعنی همه فایلها دیده میشوند
دستور autoindex هیچ کنترل دسترسی ندارد؛ هر فایلی در آن پوشه باشد دیده میشود، حتی بکاپی که اشتباهی آنجا کپی شده است.
اگر فایلها عمومی نیستند، فهرست را خاموش بگذارید.
یکی از علتهای رایج اینکه مرورگر فایل را باز میکند و دانلود نمیشود، نوع محتوای اشتباه است؛ علت رایج دیگر نبودن هدر Content-Disposition. دستور types پسوندها را به نوع محتوا نگاشت میکند و مستندات میگوید جدول نسبتاً کاملی از آن در فایل conf/mime.types همراه nginx پخش میشود:
location /download/ {
types { }
default_type application/octet-stream;
add_header Content-Disposition 'attachment' always;
}
خط سوم هدر Content-Disposition را اضافه میکند و به مرورگر میگوید فایل را ذخیره کن. مستندات ماژول ngx_http_headers_module میگوید add_header تنها برای کدهای ۲۰۰، ۲۰۱، ۲۰۴، ۲۰۶ و کدهای ریدایرکت عمل میکند و پارامتر always این محدودیت را برمیدارد. ارثبری هم فقط وقتی رخ میدهد که در سطح فعلی هیچ add_header نباشد.

تنظیمهای تحویل فایل مشخص میکنند که nginx بایتهای فایل را چطور از دیسک به سوکت برساند: sendfile برای کپی بدون عبور از حافظه کاربر، directio برای خواندن بدون کش صفحه و aio برای اینکه خواندن دیسک پروسه worker را قفل نکند. انتخاب بین این سه، سختترین بخش راهاندازی سرور دانلود است.
پیشفرض sendfile off; است و روشن کردنش برای فایل ثابت تقریباً همیشه درست است. دستور دوم اهمیتش از ظاهرش بیشتر است: sendfile_max_chunk که از نسخه 1.21.4 پیشفرضش 2m شد و پیش از آن سقفی نداشت، چون «بدون این سقف یک اتصال سریع میتواند کل پروسه worker را در اختیار بگیرد». حواستان به directio هم باشد: مستندات میگوید این دستور sendfile را «برای همان درخواست» خودکار غیرفعال میکند.
مستندات برای لینوکس دو راه پیش میگذارد. راه اول AIO سنتی است که از کرنل 2.6.22 در دسترس است و «لازم است directio فعال شود، وگرنه خواندن مسدودکننده خواهد بود»؛ نمونه مستندات aio on با directio 512; و output_buffers 1 128k; است. با روشن بودن هر دو، AIO برای فایلهای بزرگتر یا مساوی مقدار directio بهکار میرود. راه دوم سادهتر است: aio threads;.
location /download/ {
sendfile on;
aio threads;
tcp_nopush on;
}
# مسیر جایگزین، بدون نخ:
# location /download/ {
# sendfile on;
# aio on;
# directio 8m;
# }
🔸 پیش از نوشتن aio threads خروجی nginx -V را ببینید
مستندات میگوید چندنخی «بهطور پیشفرض غیرفعال است و باید با پارامتر --with-threads فعال شود». راه اول (aio on;) پارامتر دیگری میخواهد: صفحه configure در nginx.org میگوید --with-file-aio «استفاده از ورودی/خروجی غیرهمگام فایل روی FreeBSD و لینوکس را فعال میکند».
directio روی لینوکس تنها بلوکهای همتراز با ۵۱۲ بایت را میخواند (۴ کیلوبایت برای XFS) و پایان ناهمتراز فایل مسدودکننده خوانده میشود.
ادامه دانلود با درخواست Range و پاسخ 206 Partial Content کار میکند که در استاندارد HTTP (سند RFC 9110) تعریف شدهاند. در nginx این پشتیبانی چیزی نیست که روشنش کنید؛ چیزی است که میتوانید خاموشش کنید: مستندات max_ranges (از نسخه 1.1.2) میگوید «مقدار صفر پشتیبانی بازه بایتی را کامل غیرفعال میکند»:
curl -sI -H 'Range: bytes=0-1023' https://dl.example.ir/download/app.iso
# در پاسخ سالم: HTTP/1.1 206 Partial Content
# Content-Range: bytes 0-1023/4294967296
# Accept-Ranges: bytes

سرور دانلودی که سقف سرعت ندارد، همان پهنای باندی را میبلعد که برای سایت و پنل لازم دارید؛ پس مهار منابع بخش جدانشدنی راهاندازی سرور دانلود است. limit_rate سرعت هر درخواست را میبندد و limit_conn تعداد اتصال همزمان هر IP را؛ هر دو را در یک location بگذارید.
مقدار limit_rate بر حسب بایت در ثانیه است و پیشفرضش صفر یعنی بیمحدودیت. مستندات یک قید مهم دارد: «محدودیت بهازای هر درخواست تنظیم میشود، پس اگر یک کلاینت همزمان دو اتصال باز کند، سرعت کل دو برابر خواهد بود». دستور همراهش limit_rate_after (از نسخه 0.8.0) تعیین میکند بعد از چه مقداری سقف اعمال شود:
http {
server {
location /download/ {
limit_rate 1m; # سقف هر درخواست: ۱ مگابایت در ثانیه
limit_rate_after 20m; # ۲۰ مگابایت اول بدون سقف
}
}
}
از نسخه 1.17.0 هر دو دستور مقدار متغیر میپذیرند، پس با map میتوان نرخ دوسطحی ساخت. فقط کلید map را از آرگومان دلخواه کاربر نگیرید: اگر نرخ بالا را به وجود صرفِ ?token=… گره بزنید، هر بازدیدکنندهای با همان آرگومان سقف را دور میزند. کلید باید متغیر تأییدشدهای مثل $secure_link باشد.
دستور limit_conn_zone یک ناحیه حافظه مشترک میسازد که وضعیت هر کلید، از جمله تعداد اتصال فعلی، در آن نگه داشته میشود؛ بعد limit_conn سقف هر کلید را تعیین میکند. دلیل انتخاب $binary_remote_addr در مستندات آمده: اندازه $remote_addr بین ۷ تا ۱۵ بایت تغییر میکند، ولی $binary_remote_addr همیشه ۴ بایت (IPv4) یا ۱۶ بایت (IPv6) است. یک مگابایت ناحیه حدود ۱۶ هزار حالت نگه میدارد.
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
log_format dl '$time_iso8601 $remote_addr $status '
'$request_uri $bytes_sent $body_bytes_sent $request_time';
server {
location /download/ {
limit_rate 1m;
limit_rate_after 20m;
limit_conn addr 2;
limit_conn_status 429;
# limit_conn_dry_run on; # فقط شمارش، بدون رد کردن
access_log /var/log/nginx/dl.log dl buffer=64k flush=5s;
}
}
}
🔸 سه نکته که سهمیه اتصال را دور میزنند
در HTTP/2 و HTTP/3 هر درخواست همزمان یک اتصال جدا شمرده میشود.
به همین دلیل سقف پایین با دانلودمنیجرهای چندبخشی مثل IDM و aria2 تداخل دارد: همان کاربری که «ادامه دانلود» میخواهد، با چند بخش موازی به کد رد شدن میخورد.
برای سنجیدن بدون قطع کردن کاربر، limit_conn_dry_run (از نسخه 1.17.6) را روشن کنید: اتصالها محدود نمیشوند اما اضافیها شمرده میشوند.
عدد یکسانی برای همه وجود ندارد و حسابش را خودتان باید بکنید. ترافیک ماهانه در خوشبینانهترین حالت برابر است با حجم فایل × تعداد دانلود کامل: فایل ۷۰۰ مگابایتی با ۳٬۰۰۰ دانلود، ۲٬۱۰۰ گیگابایت. دانلودهای ناتمام این عدد را بالا میبرند و سهمشان را باید از لاگ خواند. پورت ۱۰۰ مگابیتی هم حدود ۱۲.۵ مگابایت در ثانیه تحویل میدهد.
عدد واقعی را فقط لاگ میگوید. متغیر $bytes_sent «تعداد بایتهای ارسالشده به کلاینت» است و هدرها را هم میشمارد؛ $body_bytes_sent که در قالب پیشفرض combined آمده، تنها بدنه پاسخ را. قالب dl در کانفیگ بالا هر دو را مینویسد و $bytes_sent ستون پنجم آن است:
awk '{s+=$5} END {printf "%.2f GiB\n", s/1024/1024/1024}' /var/log/nginx/dl.log
متغیر $request_time زمان پردازش را از اولین بایت خواندهشده تا نوشتن لاگ پس از آخرین بایت ارسالی میدهد، پس تقسیم $bytes_sent بر آن سرعت مؤثر همان دانلود را میدهد. برای هزینه ترافیک داخلی هم راهنمای نیمبها کردن ترافیک سایت و ثبت IP را ببینید.

لینک امضاشده، آخرین قدم راهاندازی سرور دانلود، یعنی نشانی دانلود یک امضای رمزنگاریشده و یک زمان انقضا با خود دارد و nginx پیش از تحویل فایل امضا را دوباره حساب میکند. خودِ nginx کد خطایی برنمیگرداند؛ تنها $secure_link را مقدار میدهد و کد پاسخ را شما انتخاب میکنید. ماژولش «بهطور پیشفرض ساخته نمیشود و باید با پارامتر --with-http_secure_link_module فعال شود».
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'secure_link|with-threads|file-aio'
# خط نیامده یعنی آن پارامتر در این بسته نبوده است
ماژول دو حالت کار دارد. حالت دومی که تاریخ انقضا میپذیرد با دو دستور secure_link و secure_link_md5 ساخته میشود. عبارت امضا در نمونه زیر چهار جزء دارد: $secure_link_expires، $uri، $remote_addr و یک کلمه رمز. هر جزئی که در عبارت بیاید لینک را به همان چیز گره میزند، پس درباره $remote_addr خودتان تصمیم بگیرید:
# روی HTTPS سرو کنید؛ روی HTTP پارامترهای md5 و expires در مسیر خوانده میشوند
location /s/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr secret";
# نسخه بدون قفل IP (برای کاربر موبایل):
# secure_link_md5 "$secure_link_expires$uri secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
root /srv/files;
types { }
default_type application/octet-stream;
add_header Content-Disposition 'attachment' always;
}
منطقش این است: secure_link رشتهای را تعریف میکند که چکسام و عمر لینک از آن بیرون کشیده میشود و secure_link_md5 عبارتی را که هش MD5 آن با مقدار درخواست مقایسه میشود. مستندات میگوید اگر چکسامها یکی نباشند $secure_link خالی میماند، اگر لینک منقضی باشد «0» میگیرد و در غیر این صورت «1»؛ دو بلوک if بالا همین مقادیر را به ۴۰۳ و ۴۱۰ ترجمه میکنند.
امضا را سروری تولید میکند که کاربر را میشناسد، مثل وردپرس یا پنل فروش شما. در هر زبانی همین سه کار تکرار میشود: زمان انقضا را از زمان فعلی بهعلاوه عمر لینک بسازید، رشته را MD5 دودویی بگیرید و خروجی را base64url کنید:
echo -n '2147483647/s/link127.0.0.1 secret' | \
openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =
# نتیجه در این نشانی بهکار میرود:
# /s/link?md5=_e4Nc3iduzkWRm01TBBNYw&expires=2147483647
در این نمونه ۲۱۴۷۴۸۳۶۴۷ زمان انقضا بر حسب ثانیه از Epoch است و 127.0.0.1 نشانی کلاینت؛ مستندات میگوید این لینک تا ۱۹ ژانویه ۲۰۳۸ و برای همان کلاینت معتبر است. ترتیب اجزا در دستور تولید و در secure_link_md5 باید یکی باشد.
⚠️ لینک امضاشده چه چیزی را حل نمیکند
لینک تا پیش از انقضا قابل اشتراکگذاری است؛ هرکس نشانی را داشته باشد فایل را میگیرد، مگر چیزی از خود کلاینت در عبارت امضا آمده باشد. مستندات این امکان را نشان میدهد: «عبارت میتواند اطلاعاتی درباره کلاینت، مثل نشانی و نسخه مرورگرش، در خود داشته باشد».
با $remote_addr لینک به یک IP قفل میشود و برای کاربر موبایل دردسر است؛ بدون آن، قابل دستبهدست شدن میماند.
امضا تعداد دانلود را نمیشمارد و محتوا را رمز نمیکند.
پیش از آوردن فایلهای اصلی، کانفیگ را روی یک فایل آزمایشی بسنجید. کار وقتی تمام است که اینها را دیده باشید: پاسخ ۲۰۶ به درخواست Range، نوع درست فایل در هدرها، سقف سرعت در عمل و ۴۰۳ گرفتن لینک دستکاریشده.
nginx -t و بعد systemctl reload nginx.nginx -V: secure_link و with-threads و file-aio.curl -sI -H 'Range: bytes=0-1023' … و دیدن 206 و Content-Range.curl -o /dev/null … و مقایسه با limit_rate و limit_rate_after.برای سایت وردپرسی یا فروشگاه، الگو این است: فایل روی سرور دانلود بماند و سایت فقط لینک امضاشده بسازد. برای مقایسه سرور اختصاصی و مجازی در حجمهای بالا، راهنمای سرور اختصاصی ایران برای سایتهای دانلودی را ببینید.
پاسخ کوتاه به پرسشهای پرتکرار درباره فایلهای حجیم، لینک موقت و ترافیک:
هاست دانلود فضای میزبانی آماده با پنل است. قاعده تجربی این است که روی هاست اشتراکی به فایل کانفیگ nginx دسترسی ندارید؛ روی سرور خودتان limit_rate و secure_link و aio را مستقیم تنظیم میکنید.
عدد جهانی ندارد؛ از پهنای باند پورت شروع کنید. پورت ۱۰۰ مگابیتی حدود ۱۲.۵ مگابایت در ثانیه تحویل میدهد، پس limit_rate روی 1m با حدود ۱۲ دانلود همزمان پر میشود. سقف روی هر درخواست است.
یعنی پاسخ 206 Partial Content داده نمیشود. اول بگردید جایی max_ranges با مقدار صفر تنظیم نشده باشد، چون مستندات میگوید مقدار صفر پشتیبانی بازه بایتی را کامل خاموش میکند. بعد مطمئن شوید فایل ثابت از دیسک سرو میشود.
لزوماً نه. مستندات میگوید این ماژول پیشفرض ساخته نمیشود و به –with-http_secure_link_module نیاز دارد. با nginx -V بستهتان را ببینید؛ اگر نبود، یا بستهای با همین پارامتر بهکار ببرید یا از سورس بسازید.
نه بهتنهایی. لینک تا لحظه انقضا معتبر است و هرکس آن را داشته باشد فایل را میگیرد. مستندات میگوید عبارت امضا میتواند اطلاعاتی از کلاینت، مثل نشانی IP، در خود داشته باشد؛ هزینهاش این است که کاربر موبایل با عوض شدن IP لینکش از کار میافتد.
از لاگ nginx. قالبی با متغیر $bytes_sent بسازید که بایتهای ارسالی به کلاینت را با هدرها میشمارد، روی مسیر دانلود بگذارید و جمع ستون را با awk حساب کنید.
روی لینوکس هر دو میتوانند روشن باشند؛ AIO برای فایلهای بزرگتر یا مساوی مقدار directio بهکار میرود و sendfile برای کوچکترها. aio threads سادهتر است، به شرط ساخت nginx با –with-threads.
جمعبندی؛ راهاندازی سرور دانلود یعنی چهار تصمیم روشن، و هر چهار با دستورهای خود nginx اجرا میشود. تصمیم اول ساختار است: یک بلوک server جدا، ریشه مشخص با root، نوع محتوای درست با types و default_type و هدر Content-Disposition، و فهرست پوشه فقط برای فایلهای کاملاً عمومی. تصمیم دوم تحویل فایل است: sendfile روشن، سقف sendfile_max_chunk و انتخاب بین aio threads یا ترکیب aio با directio که هرکدام پارامتر ساخت خودش را میخواهد. تصمیم سوم مهار منابع است: limit_rate سقف هر درخواست را میگذارد و limit_conn سهمیه هر IP را، و هر دو در یک location مینشینند. تصمیم چهارم دسترسی است: لینک امضاشده که ماژولش پیشفرض ساخته نمیشود و تا لحظه انقضا قابل دستبهدست شدن است. عددهای ترافیک را از لاگ خودتان بخوانید.
🚀 سروری که کانفیگ دانلودش دست خودتان است
سرور مجازی ایران وبداده در دیتاسنتر تهران و روی شبکه رسپینا میزبانی میشود، هر سرور IP مستقیم با پشتیبانی IPv4 و IPv6 دارد و اسنپشات و بازگردانی و نصب مجدد سیستمعامل از پنل در دسترس است. امکان تست ساعتی رایگان سرور هم وجود دارد.
اگر سوالی داشتید، در بخش نظرات با ما در ارتباط باشید.