همه سامانه‌ها عملیاتی هستند 6 منطقه آفشور پرداخت بدون KYC
Hands-on راهنمای میدانی

لاگ‌های سرور روی VPS: داده‌ای که تصمیم گرفتید نگه ندارید

باکس را آفشور بردید، با مونرو پرداخت کردید، و کلِ قضیه را پشتِ یک تونل گذاشتید. بعد nginx یک خط نوشت. هر درخواستی که سرورِ شما پاسخ می‌دهد یک رکوردِ تاریخ‌دار از اینکه چه کسی، از کجا، برای چه چیزی پرسیده به‌جا می‌گذارد — و روی یک ایمیجِ استاندارد آن رکورد دو هفته در /var/log دوام می‌آورد، یک ماه در دیتابیسِ fail2banِ شما، و هر چقدر بک‌آپ‌هایتان نگه می‌دارند، که معمولاً از هر دو بیشتر است. این راهنما به‌جای شبکه، به‌سمتِ خودِ ماشین اشاره می‌کند: تصمیم می‌گیرید سرورِ خودتان چه چیزی را بنویسد، آدرس را پیش از ساخته‌شدنِ خط کوتاه می‌کنید نه اینکه بعداً پاکش کنید، و فقط همان مقدار را نگه می‌دارید که برای بلاک‌کردنِ یک مهاجم و دیباگِ یک 500 کافی است، بدونِ آنکه تاریخچه‌ای از بازدیدکننده‌ها جمع کنید که ترجیح می‌دهید نداشته باشیدش.

به‌روزرسانی‌شده در 2026-09-15 · 15 دقیقه مطالعه · عملیات ناوگان
در این صفحه
  1. درخواستی که پاسخ دادید، حالا رکوردی است که نگه داشته‌اید
  2. شش لاگ، و دوتایی که نامِ آدم‌ها را ثبت می‌کنند
  3. کوتاه‌کردن در لحظهٔ نوشتن، نه در لحظهٔ rotation
  4. کوتاه‌سازی چه چیزی را خراب می‌کند، و سه کاری که یک لاگ واقعاً انجام می‌دهد
  5. journald، auth.log، و ردی که به خودتان برمی‌گردد
  6. سیاستِ نگه‌داریِ شما همان چیزی است که بک‌آپ‌هایتان می‌گویند
  7. کپی‌هایی که کنترلشان دست شما نیست
  8. کمینه‌سازی از طریقِ طراحی، نه حذفِ پس از اخطار
  9. گام‌به‌گام
SP·01

درخواستی که پاسخ دادید، حالا رکوردی است که نگه داشته‌اید

بقیهٔ این مجموعه به بیرون اشاره می‌کند. مبدأ را پنهان کنید، دیسک را رمزنگاری کنید، حوزهٔ قضایی را انتخاب کنید، جلوی نگه‌داشتنِ لاگِ خودتان توسطِ یک نفرِ دیگر را بگیرید. این یکی به خودِ ماشین اشاره می‌کند، چون سروری که به درخواست‌ها پاسخ می‌دهد ثبت می‌کند چه کسی آن‌ها را ساخته — پیش‌فرض، در چند جا هم‌زمان، با نگه‌داری‌ای که هیچ‌کس انتخابش نکرده.

به آنچه یک ایمیجِ استاندارد دو هفته بعد از دیپلوی نگه داشته نگاه کنید. /var/log/nginx/access.log به‌ازای هر درخواست یک خط دارد: آدرس، تایم‌استمپ، مسیر، referrer، user agent، که روزانه rotate می‌شود و چهارده روز نگه داشته می‌شود. /var/log/auth.log هر نشستِ SSH را دارد، از جمله آدرسِ مبدأِ تکِ‌تکِ نشست‌های خودتان. journal همان رویدادها را دوباره دارد، به‌علاوهٔ هر چیزی که سرویس‌های شما به stderr چاپ کرده‌اند. اگر Docker در حالِ اجراست، هر کانتینر یک فایلِ لاگِ JSON دارد که از کارخانه اصلاً هیچ محدودیتِ اندازه‌ای ندارد. اگر fail2ban در حالِ اجراست، یک لاگ از هر آدرسی که تا به‌حال بن کرده و یک دیتابیسِ SQLite که همان چیز را می‌گوید دارد، و آدرس‌ها در هر دو کامل‌اند.

هیچ‌کدامِ این‌ها مخرب نیستند و بیشترشان واقعاً مفیدند — برای حدودِ یک ساعت بعد از آنکه چیزی خراب شود. مشکل شکلِ ماجراست: با وفاداریِ کامل، برای مدتِ طولانی نگه داشته می‌شوند، از سرِ تصادف نه تصمیم. سؤالی که ارزش دارد دربارهٔ هرکدام از این فایل‌ها پرسیده شود این نیست که "آیا این حساس است؟" بلکه این است که "واقعاً با خطی که سه هفته پیش نوشتم چه‌کار می‌کردم؟" برای اکثریتِ قریب‌به‌اتفاقِ خط‌ها روی اکثریتِ قریب‌به‌اتفاقِ سرورها جواب هیچی است، و رکوردی که هرگز نمی‌خوانیدش چیزی جز ریسکِ خالص نیست — در برابرِ یک نفوذ، در برابرِ بک‌آپی که از خودِ ماشین بیشتر عمر می‌کند، در برابرِ هر کسی که بالاخره درخواستش کند.

کمینه‌سازیِ داده نامِ رسمیِ این راه‌حل است و کم‌جنجال‌ترین ایده در حوزهٔ حفاظتِ داده است: همان را جمع کنید که کار به آن نیاز دارد، تا وقتی کار به آن نیاز دارد نگهش دارید، بعد متوقف شوید. آنچه در ادامه می‌آید این ایده را روی باکسی پیاده می‌کند که واقعاً اجرا می‌کنید، با این فرض که اولین ساعت پس از دیپلوی را از قبل انجام داده‌اید و چیزی روی ماشین هست که ارزشِ محافظت‌کردن دارد.

SP·02

شش لاگ، و دوتایی که نامِ آدم‌ها را ثبت می‌کنند

پیش از آنکه چیزی را تغییر دهید، فهرستِ موجودی را بشناسید. یک VPSِ کوچکِ Debian یا Ubuntu که یک سرویسِ وب اجرا می‌کند معمولاً شش جریان می‌نویسد، و این‌ها بیشتر از آنچه فکر می‌کنید هم‌پوشانی دارند — یک رویدادِ واحد اغلب در سه فایل با سه نگه‌داریِ متفاوت فرود می‌آید.

لاگِ accessِ nginx همان چیزی است که بازدیدکننده‌های شما را نام می‌برد. به‌ازای هر درخواست یک خط، با آدرسِ کلاینت، مسیرِ دقیق، referrer و یک رشتهٔ user-agent که به‌تنهایی هم به‌قدرِ کافی دقیق است که یک fingerprintِ ضعیف باشد. لاگِ errorِ nginx همان چیزی است که آدم‌ها فراموشش می‌کنند: روی هر upstream timeout، هر 403، هر درخواستِ بدشکل، client: 203.0.113.9 را ثبت می‌کند — و برخلافِ لاگِ access، فرمتش ثابت است و نمی‌توان template‌اش کرد.

لاگِ auth/var/log/auth.log روی خانوادهٔ Debian، /var/log/secure روی خانوادهٔ RHEL — شما را نام می‌برد. هر خطِ publickeyِ پذیرفته‌شده آدرسِ مبدأِ شما و fingerprintِ همان کلیدی که نشست را باز کرده حمل می‌کند. هر کسی که یک ماه از آن را بخواند یاد می‌گیرد از کدام شبکه‌ها ادمین می‌کنید، در چه ساعت‌هایی، و چند کلیدِ متمایز دارید. روی باکسی که مالکیتش ناشناس است، آن فایل اغلب افشاگرتر از هر چیزی است که بازدیدکننده‌های شما تولید کرده‌اند.

journal یک کپی از بیشترِ موارد بالا را به‌علاوهٔ stdout و stderrِ هر یونیت نگه می‌دارد، و روی یک نصبِ پیش‌فرض اجازه دارد تا ۱۰٪ از فایل‌سیستم رشد کند، با سقفِ ۴ گیگابایت، پیش از آنکه شروع کند به انداختنِ قدیمی‌ترین رکوردها. روی هر دیسکِ ۴۰ گیگابایتی یا بزرگ‌تر، آن سقف همان ۴ گیگابایتِ کامل است، که با حجمی که یک سرورِ کوچک تولید می‌کند، چندین ماه می‌شود.

لاگ‌های اپلیکیشن مجهولِ این معادله‌اند. یک فریم‌ورک در حالتِ debug تمامِ URLها را همراه با query string ثبت می‌کند، و query string‌ها به‌طورِ روتین توکن‌های نشست، لینک‌های ریست‌کردنِ پسورد و عبارت‌های جست‌وجو حمل می‌کنند. PHP-FPM می‌تواند طوری تنظیم شود که لاگِ accessِ خودش را بنویسد، که همان خطِ درخواست را که nginx از قبل نوشته تکرار می‌کند، از فایلی که تنظیماتِ nginxِ شما هرگز لمسش نمی‌کند.

لاگ‌های کانتینر همان موردِ ساکت‌اند. درایورِ پیش‌فرضِ json-file در Docker تا وقتی خودتان تنظیمش نکنید هیچ rotationای ندارد، پس /var/lib/docker/containers/*/*-json.log هر چیزی را که کانتینر از لحظهٔ ساخته‌شدنش گفته نگه می‌دارد. این یک راهِ رایج برای کشف‌کردنِ این است که یک سیاستِ نگه‌داریِ "۱۴ روزه" در واقع دارد یازده ماه را نگه می‌دارد، و همان دسته‌ی غافلگیری‌ای است که قوانینِ فایروالی که Docker پشتِ ufw می‌نویسد هم هست.

از این شش‌تا، دوتایشان شناسه‌هایی دربارهٔ انسان‌ها حمل می‌کنند که ارزش دارد عمداً کمینه شوند: لاگِ accessِ nginx (بازدیدکننده‌های شما) و لاگِ auth (خودتان). بقیه بیشتر به یک سقفِ اندازه و یک ساعتِ کوتاه‌تر نیاز دارند.

SP·03

کوتاه‌کردن در لحظهٔ نوشتن، نه در لحظهٔ rotation

غریزه این است که لاگ‌گیری را عادی نگه دارید و بعداً پاک‌سازی کنید — یک cronِ شبانه که فایلِ دیروز را با آدرس‌های حذف‌شده بازنویسی می‌کند. آن را نسازید. یک jobِ پاک‌سازی یعنی آدرس‌های خام واقعاً تا یک روز روی دیسک وجود داشته‌اند، و در طولِ آن روز، رانِ بک‌آپِ شما از آن‌ها اسنپ‌شات گرفته، احتمالاً اسنپ‌شاتِ سطحِ بلاکِ هاستِ شما هم آن‌ها را کپی کرده، و هر کاری که فایل‌سیستم با بلاک‌های قدیمی کرده در آن‌ها باقی مانده. بدتر آنکه، این job یک قطعهٔ متحرک است: همان هفته‌ای که دیسک پر می‌شود بی‌سروصدا شکست می‌خورد، و هیچ‌چیز به شما نمی‌گوید که فایلِ دیروز هنوز کامل است.

تنها کاهشی که واقعاً می‌توانید رویش حساب کنید همان است که پیش از نوشته‌شدنِ خط اتفاق می‌افتد. در nginx آن یک بلاکِ map است که در لحظهٔ لاگ ارزیابی می‌شود و آدرس را در یک متغیرِ تازه بازمی‌نویسد، و یک log_format که به‌جای $remote_addr از همان متغیرِ تازه استفاده می‌کند. آدرسِ کامل هرگز serialize نمی‌شود. بعدش چیزی برای پاک‌کردن نیست، چیزی برای زمان‌بندی‌کردن نیست، و چیزی نیست که روزی که شما تماشا نمی‌کنید خراب شود.

# /etc/nginx/conf.d/00-privacy-log.conf  --  http context, loaded before the sites
map $remote_addr $ip_trunc {
    # IPv4: keep the /24, zero the host part
    ~(?<v4>\d+\.\d+\.\d+)\.     "${v4}.0";
    # IPv6: keep the first two groups, drop the rest
    ~(?<v6>[^:]+:[^:]+):          "${v6}::";
    # anything the two patterns cannot parse -- including compressed forms
    # like ::1 -- falls through here, i.e. fails closed rather than open
    default                       "0.0.0.0";
}

log_format privacy '$ip_trunc - - [$time_local] "$request" $status '
                   '$body_bytes_sent "$http_referer" "$http_user_agent" '
                   'rid=$request_id rt=$request_time';

سه جزئیات تصمیم می‌گیرند که آیا این واقعاً کار می‌کند یا نه. اول، map باید داخلِ کانتکستِ http زندگی کند — داخلِ یک بلاکِ server، nginx از شروع‌شدن سر باز می‌زند. انداختنش در conf.d/ با نامی که زود مرتب می‌شود ساده‌ترین راه برای مطمئن‌شدن است.

دوم، اگر پشتِ Cloudflare یا هر edgeِ دیگری نشسته‌اید، چک کنید $remote_addr چه چیزی دارد. ماژولِ realip آن را در همان اوایلِ پردازشِ درخواست با آدرسِ واقعیِ کلاینت جایگزین می‌کند، و آدرسِ متصل‌شونده را در $realip_remote_addr نگه می‌دارد. آن ترتیب به نفعِ شماست: map در لحظهٔ لاگ اجرا می‌شود، پس بازدیدکنندهٔ واقعی را کوتاه می‌کند نه edge را. اما این هم به این معناست که اگر realip را تنظیم نکرده‌اید، دارید آدرسِ Cloudflare را کوتاه می‌کنید و هیچی یاد نمی‌گیرید، درحالی‌که آدرسِ واقعی داخلِ یک هدر نشسته است.

سوم، بقیهٔ رشتهٔ فرمت را audit کنید. کوتاه‌کردنِ $remote_addr دقیقاً هیچ کاری نمی‌کند اگر خط همچنان با "$http_x_forwarded_for" تمام شود یا $http_cf_connecting_ip را حمل کند — و تعدادِ زیادی از فرمت‌های استاندارد و کنترل‌پنلی یکی از آن‌ها را دارند. آدرسِ کامل داخلِ هدرهای درخواست است؛ فقط وقتی از فایل بیرون می‌ماند که هر هدری که آن را حمل می‌کند از فرمت بیرون بگذارید.

به آنچه جایگزینش شده توجه کنید: $request_id، یک رشتهٔ hexِ ۳۲کاراکتریِ تصادفی که nginx به‌ازای هر درخواست تولید می‌کند. همین همان محورِ بخشِ بعدی است.

SP·04

کوتاه‌سازی چه چیزی را خراب می‌کند، و سه کاری که یک لاگ واقعاً انجام می‌دهد

همیشه یک نفر اعتراض می‌کند که لاگ‌های ناشناس‌شده بی‌فایده‌اند، و نیمی حق دارد — چون "لاگ‌ها" سه کارِ نامرتبط است که یک نامِ فایل پوشیده، و فقط یکی از آن‌ها به آدرس نیاز دارد.

کارِ اول: بلاک‌کردنِ هر کسی که همین الان دارد به شما فشار می‌آورد. این به آدرسِ کامل نیاز دارد و در عرضِ چند ثانیه به آن نیاز دارد. فردا به آن نیاز ندارد. fail2ban همان ابزارِ معمول است و واقعاً نمی‌تواند از روی یک فایلِ کوتاه‌شده کار کند — بن‌کردنِ 203.0.113.0 یک هاستِ بی‌گناه را بن می‌کند و مهاجم را متصل رها می‌کند. اما این کار با فایلی که فقط یک روز وجود دارد هم برآورده می‌شود، یا با اصلاً استفاده‌نکردن از فایل: limit_req و limit_connِ خودِ nginx وضعیتشان را در حافظهٔ مشترک نگه می‌دارند، در میکروثانیه عمل می‌کنند نه با فاصلهٔ پولینگِ fail2ban، و هیچی روی دیسک نمی‌نویسند.

کارِ دوم: فهمیدنِ اینکه چرا آن درخواست یک 500 برگرداند. این به correlation نیاز دارد، نه هویت. یک request ID که از nginx تا اپلیکیشن رشته شده، خطِ access، خطای upstream و stack traceِ اپلیکیشن را به یک رویداد گره می‌زند — که همان کاری است که وقتی سراغِ آدرس می‌رفتید واقعاً می‌خواستید انجام دهید. در عمل، ID بهتر است: از یک کلاینتِ روی شبکهٔ موبایل که آدرسش وسطِ نشست عوض می‌شود جان سالم به در می‌برد، و وقتی چهار بازدیدکننده یک آدرسِ CGNAT مشترک را دارند بیات نمی‌شود.

کارِ سوم: فهمیدنِ ترافیک در طولِ زمان. حجم، ترکیبِ status code، اینکه کدام مسیرها داغ‌اند، اینکه آیا crawler از کنترل خارج شده. این‌جا یک آدرسِ کوتاه‌شده کاملاً کافی است، و همان /24ای که از کوتاه‌سازی جان سالم به در می‌برد کافی است تا ببینید یک شبکه مسئولِ ۴۰٪ از درخواست‌های شماست.

پس طراحی "کمتر لاگ بگیر" نیست بلکه تقسیم بر اساسِ ساعت است: یک جریانِ با وفاداریِ کامل که یک روز زنده می‌ماند و به ابزارهای بلاک‌کردن خوراک می‌دهد، و یک جریانِ کوتاه‌شده که تا هر وقت آماری می‌خواهید زنده می‌ماند. هر دو در همان لحظه توسطِ nginx نوشته می‌شوند، پس هیچ مرحلهٔ پردازشیِ میانی نیست و هیچ پنجره‌ای نیست که در آن چیزِ غلط بیشتر از آنچه قرار بود روی دیسک بماند.

# inside the server block, or in a snippet included by it
access_log  /var/log/nginx/access.log       privacy;   # truncated, keep for weeks
access_log  /var/log/nginx/security.log     secip;     # full address, keep for a day

# static assets are pure noise in a privacy log -- drop them entirely
location ~* \.(?:css|js|svg|png|jpe?g|webp|woff2?|ico)$ {
    access_log off;
    expires 30d;
}

فرمتِ secip در کنارِ آن یکی فقط یک خط است — $remote_addr، تایم‌استمپ، درخواست و status، هیچ چیزِ دیگری. این تنها فایلِ روی باکس است که اجازه دارد یک آدرسِ کاملِ بازدیدکننده در آن بنشیند، که همین نگه‌داریِ آن را به یک تصمیمِ واحد در یک جای واحد تبدیل می‌کند نه یک ویژگی که باید رویش در شش فایل استدلال کنید.

SP·05

journald، auth.log، و ردی که به خودتان برمی‌گردد

حریمِ خصوصیِ بازدیدکننده‌ها همان بخشی است که دربارهٔ آن نوشته می‌شود. ردِ ادمین همان بخشی است که روی باکسی که کلِ هدفش این است که نامِ شما به آن وصل نیست اهمیت دارد، و تقریباً تمام‌وکمال در دو جا نشسته.

/var/log/auth.log به‌ازای هر نشستی که باز می‌کنید یک خطِ Accepted publickey ثبت می‌کند، با آدرسِ مبدأ و fingerprintِ کلیدِ شما. در طولِ یک ماه، این یک جدولِ زمانی از عادت‌های کاریِ شما و فهرستی از شبکه‌هایی است که استفاده می‌کنید. اگر همیشه از همان تونل وصل می‌شوید، آن یک آدرسِ تکرارشونده و نسبتاً کسل‌کننده است. اگر از هر جا که هستید وصل می‌شوید، این یک تاریخچهٔ سفر است.

journal همان رویدادها را به‌علاوهٔ هر چیزی که یونیت‌های شما چاپ کرده‌اند نگه می‌دارد، و پیش‌فرض‌هایش سخاوتمندند: SystemMaxUse= برابرِ ۱۰٪ از فایل‌سیستم است، و MaxRetentionSec= تنظیم‌نشده باقی مانده، که یعنی اصلاً هیچ محدودیتِ زمانی‌ای نیست — فقط یک محدودیتِ اندازه. روی یک سرورِ آرام، آن ترکیب ماه‌ها را نگه می‌دارد.

این‌جا یک معاملهٔ واقعی هست و ارزش دارد صریح گفته شود نه با دست تکان‌دادن رد شود. همان لاگ‌هایی که شما را توصیف می‌کنند، همان لاگ‌هایی‌اند که به شما می‌گویند یک نفر چطور وارد شده. Storage=volatile را تنظیم کنید و journal فقط در RAM زندگی می‌کند، و با ری‌استارت ناپدید می‌شود — واقعاً خصوصی، و واقعاً بی‌فایده همان صبحی که یک پروسه پیدا می‌کنید که خودتان استارتش نکرده‌اید، چون اولین ری‌استارتِ مهاجمِ شما مدرک را پاک کرده. برای بیشترِ آدم‌ها، حدِ وسطِ عاقلانه یک storageِ پایدار با یک سقفِ سخت و یک ساعتِ کوتاه است: به‌اندازهٔ کافی طولانی برای بررسیِ حادثه‌ای که در عرضِ یک هفته متوجهش می‌شوید، به‌اندازهٔ کافی کوتاه که آن فایل یک دفترچه‌خاطرات نباشد.

دو نکتهٔ پیاده‌سازی که آدم‌ها را گیر می‌اندازد. ایمیج‌های Debian و Ubuntu در اینکه آیا rsyslog نصب است یا نه فرق دارند؛ اگر /var/log/auth.log روی باکسِ شما وجود دارد پس rsyslog دارد آن را می‌نویسد، و سقف‌های journald اصلاً آن فایل را اداره نمی‌کنند — آن کارِ logrotate است. و ForwardToSyslog= همان چیزی است که rsyslog را از journal تغذیه می‌کند، پس خاموش‌کردنش روی باکسی که هر دو را دارد جلوی این را می‌گیرد که دو کپی از همه‌چیز را زیرِ دو سیاستِ نگه‌داریِ متفاوت نگه دارید.

SP·06

سیاستِ نگه‌داریِ شما همان چیزی است که بک‌آپ‌هایتان می‌گویند

این همان چیزی است که کلِ کارِ دقیقِ بالا را خنثی می‌کند، و تا وقتی دنبالش نگردید نامرئی است.

فرض کنید logrotate چهارده روز از لاگ‌های nginx را نگه می‌دارد، و از آن راضی‌اید. حالا همان بک‌آپِ خارج از سرور را اضافه کنید که به‌درستی راه‌اندازی کرده بودید: یک رانِ روزانهٔ Borg یا restic، با نگه‌داریِ هفت آرشیوِ روزانه، چهار آرشیوِ هفتگی و شش آرشیوِ ماهانه. هرکدام از آن آرشیوها /var/log را همان‌طور که در روزِ اجرا بوده در خودش دارد. قدیمی‌ترین آرشیوِ ماهانه شش ماه عمر دارد و همان چهارده روز لاگی را نگه داشته که آن‌موقع جاری بود. نگه‌داریِ مؤثرِ لاگِ شما چهارده روز نیست. شش ماه است، در یک ریپازیتوریِ رمزنگاری‌شده که نمی‌توانید بدونِ ریستورکردنش grep کنید، روی یک ماشینِ دوم، در یک کشورِ متفاوت.

دقیقاً دو راه‌حلِ صادقانه وجود دارد. دایرکتوری‌های لاگ را از بک‌آپ کنار بگذارید — آن‌ها همان تک‌چیزی روی یک سرورند که تقریباً همیشه می‌توانید بازسازی‌شان کنید یا بدونشان سر کنید، و یک ریستور که /var/log را حذف می‌کند ریستورِ بدتری نیست. یا عمداً آن‌ها را اضافه کنید و بپذیرید که نگه‌داریِ واقعیِ شما همان نگه‌داریِ ریپازیتوری است، که در آن‌صورت در هر سیاستی که منتشر می‌کنید همین را بگویید، چون جایگزینش یک سیاستِ اعلام‌شده است که زیرساختِ خودتان نقضش می‌کند.

# exclude logs from the backup, and prove it took
borg create --stats                       \
    --exclude '/var/log'                  \
    --exclude '/var/lib/docker/containers' \
    ::'{hostname}-{now:%Y-%m-%d}' /

# the check that matters: can you still find an address in the newest archive?
borg list ::"$(borg list --short --last 1)" | grep -c '^var/log/' || echo "clean"

حالا که در همین حال‌وهوا هستید، دو همسایهٔ همین مشکل. اسنپ‌شات‌های هاستِ شما مالِ شما نیستند. یک اسنپ‌شاتِ سطحِ هایپروایزر دیسک را همان‌طور که بوده ضبط می‌کند، از جمله لاگ‌هایی که از آن‌موقع rotate کرده‌اید، و روی storageِ provider، زیرِ نگه‌داریِ provider زندگی می‌کند — که یک دلیلِ دیگر است برای اینکه آدرس‌ها اصلاً نباید کامل نوشته می‌شدند، نه یک دلیل برای انجامِ کارِ باهوشانه‌ای بعدش.

و روی یک VPS سراغِ shred نروید. بازنویسیِ یک فایل روی یک دیسکِ مجازیِ thin-provisioned، روی یک فایل‌سیستمِ copy-on-write، بالای یک SSD که بلاک‌ها را برای wear-levelling جابه‌جا می‌کند، سلول‌های فیزیکی‌ای که فایل را نگه داشته بودند به‌طورِ قابل‌اعتماد بازنویسی نمی‌کند. حذفِ امن روی storageِ اجاره‌ای و مجازی‌سازی‌شده تئاتر است. تنها کاهشی که کار می‌کند همان است که در لحظهٔ نوشتن انجام دادید؛ هر چیزِ بعدِ آن یک تلاشِ حداکثری است که نمی‌توانید تأییدش کنید. رمزنگاریِ ولوم این معادله را عوض می‌کند — اما آن را پیش از نوشته‌شدنِ داده عوض می‌کند، که همان درسِ قبلی است، دوباره.

SP·07

کپی‌هایی که کنترلشان دست شما نیست

کمینه‌سازی روی باکسِ خودتان فقط یک لایه از چند لایه است، و روشن‌بودن دربارهٔ بقیه همان چیزی است که جلوی تبدیل‌شدنِ این کار به یک حسِ کاذبِ کامل‌بودن را می‌گیرد.

شبکهٔ هاستینگِ شما رکوردِ flow را می‌بیند. مبدأ، مقصد، پورت‌ها، بایت‌ها، زمان‌بندی — برای هر اتصالی که به داخل و خارجِ ماشین می‌رود، فرق نمی‌کند شما چیزی لاگ بگیرید یا نه. هیچ کانفیگی روی سرور آن را عوض نمی‌کند. این بخشِ بزرگی از دلیلِ این است که چرا حوزهٔ قضایی‌ای که باکس در آن نشسته یک متغیرِ واقعی است نه یک متغیرِ بازاریابی؛ آنچه قانوناً از شبکه خواسته می‌شود کشور به کشور به‌شدت فرق می‌کند.

CDN یا edgeِ شما در همان edge لاگ می‌گیرد. اگر Cloudflare برایتان TLS را terminate می‌کند، پیش از آنکه سرورِ شما اصلاً درگیر شود، خطِ درخواست و آدرسِ کلاینت را دارد، روی برنامهٔ نگه‌داریِ خودش، تابعِ فرایندِ قانونیِ خودش. کوتاه‌کردنِ لاگِ originِ شما از میانِ آن به عقب نمی‌رسد. اجرای یک edgeِ خودتان همان نسخه‌ای از این کار است که واقعاً می‌توانید کانفیگش کنید — و قوانینِ لاگ‌گیریِ این راهنما اول روی همان باکسِ edge اعمال می‌شوند، چون آن‌جا همان‌جایی است که آدرس‌های کوتاه‌نشده می‌رسند.

ردیاب‌های خطا و analytics آن را از باکس برایتان خارج می‌کنند. Sentry و بیشترِ همتاهایش به‌طورِ پیش‌فرض IPِ کلاینت را به هر رویداد می‌چسبانند؛ تنظیمش معمولاً یک چیزی شبیهِ send_default_pii نام دارد و ارزش دارد چکش کنید نه اینکه فرضش بگیرید. هر analyticsِ هاست‌شده، از اساس، یک کپیِ شخصِ‌ثالث از همان access logی است که یک بعدازظهر وقت گذاشتید کوتاهش کنید.

ایمیل نشتی‌ترینِ همه است. اگر هر چیزی روی باکس ایمیل می‌فرستد، هدرها هاست و آدرسِ فرستنده را حمل می‌کنند، و هر relay در مسیر یک کپی از envelope را با تایم‌استمپ نگه می‌دارد. یک فرمِ تماس که برایتان ایمیل می‌فرستد یک لاگ است که خودتان اداره‌اش نمی‌کنید.

هیچ‌کدام از این‌ها کارِ محلی را بی‌فایده نمی‌کند — کپیِ محلی همان چیزی است که همراهِ ماشین توقیف می‌شود، در یک نفوذ exfiltrate می‌شود، یا توسطِ خودتان تحویل داده می‌شود. این فقط همان تک‌لایه‌ای است که کاملاً کنترلش می‌کنید، و اشتباه این است که آن را کلِ تصویر فرض کنید.

SP·08

کمینه‌سازی از طریقِ طراحی، نه حذفِ پس از اخطار

این‌جا ارزش دارد دقیق باشیم، چون این دو با هم قاطی می‌شوند، و فرقشان دقیقاً همان فرقِ بینِ یک رویهٔ مهندسیِ استاندارد و کاری است که نباید انجامش دهید.

از قبل تصمیم‌گرفتن دربارهٔ اینکه سرویسِ شما چه چیزی را جمع می‌کند و چه‌مدت نگهش می‌دارد یک رویهٔ عادی، مستندشده و تشویق‌شده است. زیرِ GDPR این یکی از دو اصلِ اصلی است — کمینه‌سازیِ داده و محدودیتِ نگه‌داری — و یک نگه‌داریِ لاگِ کوتاه‌تر همان کنترلی است که auditorها می‌خواهند، نه چیزی که به آن اعتراض کنند. هیچ تعهدِ عمومی‌ای روی یک اپراتورِ وب‌سایت یا یک مشتریِ هاستینگ در اتحادیهٔ اروپا برای نگه‌داریِ لاگ‌های ترافیک نیست؛ دستورالعملِ نگه‌داریِ سراسری که زمانی خلافِ این را نشان می‌داد در سالِ ۲۰۱۴ توسطِ دیوانِ دادگستریِ اتحادیهٔ اروپا لغو شد، و قوانینِ ملی‌ای که از آن جان سالم به در برده‌اند بیشتر ارائه‌دهندگانِ مخابراتی را مقید می‌کنند، نه آدم‌هایی که یک وب‌سرور اجرا می‌کنند. ایالاتِ متحده هم هیچ الزامِ عمومیِ نگه‌داری‌ای برای اپراتورهای سایت ندارد.

نابودکردنِ رکوردهای مشخص بعد از آنکه دربارهٔ آن‌ها به شما اخطار داده شده یک عملِ کاملاً متفاوت است. یک درخواستِ نگه‌داری، یک litigation hold، یک حکمِ دادگاه، یا یک استعلامِ پلیس، آنچه را که می‌توانید با داده‌ای که در همان لحظه وجود دارد انجام دهید عوض می‌کند، و "سیاستِ نگه‌داریِ من حذفش کرد" دفاعی نیست اگر به‌خاطرِ آن اخطار حذف را تسریع کرده باشید. هیچ‌چیز در این راهنما دربارهٔ آن نیست. یک سیاستِ نگه‌داری چیزی است که یک سه‌شنبهٔ آرام تنظیمش می‌کنید و بعد رهایش می‌کنید؛ اگر فقط وقتی چیزی اتفاق می‌افتد کوتاهش می‌کنید، آن اصلاً یک سیاست نبوده.

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

دو پیامدِ عملی. اگر یک سیاستِ حریمِ خصوصی منتشر می‌کنید، عددهای نگه‌داریِ آن را با آنچه واقعاً روی دیسک است منطبق کنید — چهارده روزِ اعلام‌شده و شش ماهِ واقعی در یک ریپازیتوریِ بک‌آپ همان نوع شکافی است که یک اپراتورِ باحسن‌نیت را روی کاغذ به یک اپراتورِ بدحسن‌نیت تبدیل می‌کند. و تصمیم را برای خودتان یادداشت کنید، اگر جای دیگری نه، در یک کامنت بالای فایلِ logrotate، چون کسی که باید در عرضِ هجده ماهِ دیگر این عددها را توجیه کند خودِ شمایید، و یادتان نمی‌آید چرا آن عدد هفت بود. هیچ‌کدام از موارد بالا مشاورهٔ حقوقی نیست؛ اگر جایی کار می‌کنید که تعهداتِ نگه‌داریِ مخصوصِ همان صنعت را دارد، پیش از آنکه چیزی را کوتاه کنید، آن‌ها را با وضعیتِ خودتان چک کنید.

SP·09

گام‌به‌گام

  1. 01

    بفهمید باکس همین الان چه چیزی را نگه داشته

    پیش از آنکه اندازه‌گیری کرده باشید چیزی را کانفیگ نکنید. هدفِ این مرحله پیداکردنِ همان فایلی است که فراموشش کرده‌اید، که روی بیشترِ ماشین‌ها یا یک لاگِ کانتینر است یا یک لاگِ اپلیکیشن که از زمانِ دیپلوی هیچ‌کس نگاهش نکرده.

    # biggest log files anywhere on the box, largest last
    sudo du -ah /var/log /var/lib/docker/containers 2>/dev/null \
      | sort -h | tail -20
    
    # how much disk the journal holds, and how far back it goes
    journalctl --disk-usage
    journalctl --output=short-iso | head -1
    
    # how old is the oldest nginx line still on disk?
    zcat -f /var/log/nginx/access.log* 2>/dev/null | head -1

    بعد یک خط از هر فایل را نگاه کنید و بپرسید چه چیزی را شناسایی می‌کند. دستورِ زیر می‌شمارد چند آدرسِ کاملِ متمایز همین الان از لاگ‌های وبِ شما قابلِ بازیابی است — این معمولاً همان عددی است که دلیلِ بقیهٔ این راهنما را می‌سازد.

    zcat -f /var/log/nginx/access.log* 2>/dev/null \
      | grep -oE '^([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | wc -l

    آنچه پیدا می‌کنید را یادداشت کنید. همین دستورها را در انتها دوباره اجرا می‌کنید تا ثابت کنید تغییر واقعاً اعمال شده.

  2. 02

    آدرسِ کلاینت را پیش از آنکه nginx خط را بنویسد کوتاه کنید

    map و آن دو فرمت را در فایلی بسازید که داخلِ کانتکستِ http لود می‌شود. روی Debian و Ubuntu، /etc/nginx/conf.d/ پیش از کانفیگ‌های سایت از nginx.conf اینکلود می‌شود، که دقیقاً همان‌جایی است که این باید برود.

    sudo tee /etc/nginx/conf.d/00-privacy-log.conf > /dev/null <<'EOF'
    # The client address is reduced here, at log time, and never written in full
    # to the long-retention file. $realip_remote_addr still holds the connecting
    # address if you need it while debugging a proxy problem.
    map $remote_addr $ip_trunc {
        ~(?<v4>\d+\.\d+\.\d+)\.     "${v4}.0";
        ~(?<v6>[^:]+:[^:]+):          "${v6}::";
        # anything the two patterns cannot parse -- including compressed forms
        # like ::1 -- falls through here, i.e. fails closed rather than open
        default                       "0.0.0.0";
    }
    
    # Long retention: no full address, no forwarded-for header, request id instead.
    log_format privacy '$ip_trunc - - [$time_local] "$request" $status '
                       '$body_bytes_sent "$http_referer" "$http_user_agent" '
                       'rid=$request_id rt=$request_time';
    
    # Short retention: the one file allowed to hold a complete address.
    log_format secip   '$remote_addr [$time_local] "$request" $status';
    EOF
    
    sudo nginx -t && sudo systemctl reload nginx

    حالا سایت را به‌سمتِ آن‌ها نشانه بگیرید. در بلاکِ serverِ خودتان، خطِ access_log موجود را با آن جفت جایگزین کنید، و همین‌جا لاگ‌گیری را برای استاتیک‌اسِت‌ها خاموش کنید.

    access_log  /var/log/nginx/access.log    privacy;
    access_log  /var/log/nginx/security.log  secip;

    Reload کنید، یک صفحه را لود کنید، و نتیجه را بخوانید. فیلدِ اول باید به .0 ختم شود، و خط باید یک مقدارِ rid= حمل کند.

    sudo nginx -t && sudo systemctl reload nginx
    curl -s -o /dev/null https://your-domain.example/
    sudo tail -1 /var/log/nginx/access.log

    اگر آدرس هنوز کامل است، بلاکِ server دارد فرمت را یک‌جای دیگر پایین‌تر override می‌کند — grep -rn access_log /etc/nginx/ همان خطی را پیدا می‌کند که برنده می‌شود.

  3. 03

    آدرس‌های کامل را فقط جایی نگه دارید که چیزی رویشان عمل می‌کند

    اگر fail2ban در حالِ اجراست، همین الان دارد همان فایلی را می‌خواند که تازه کوتاهش کردید. به‌جایش آن را به‌سمتِ لاگِ امنیتی نشانه بگیرید، و به آن لاگ یک عمرِ یک‌روزه بدهید تا آدرس‌های کامل خودشان منقضی شوند.

    sudo tee /etc/fail2ban/jail.d/nginx-privacy.local > /dev/null <<'EOF'
    [nginx-http-auth]
    enabled  = true
    logpath  = /var/log/nginx/error.log
    
    [nginx-botsearch]
    enabled  = true
    logpath  = /var/log/nginx/security.log
    maxretry = 6
    findtime = 10m
    bantime  = 1h
    EOF
    
    sudo fail2ban-client reload

    بعد با حافظهٔ خودِ fail2ban سروکار داشته باشید، که بیشترِ آدم‌ها هرگز دست به آن نمی‌زنند: زیرِ پیش‌فرضِ rotate 4 weekly لاگِ خودش از بن‌ها را نگه می‌دارد، و یک دیتابیسِ SQLite از هر بنی که صادر کرده. dbpurgeage همان چیزی است که ردیف‌ها را از آن دیتابیس منقضی می‌کند — آن را به چیزی نزدیک به طولانی‌ترین بنِ خودتان تنظیم کنید نه اینکه روی پیش‌فرض رهایش کنید.

    sudo tee /etc/fail2ban/fail2ban.d/retention.local > /dev/null <<'EOF'
    [Definition]
    dbpurgeage = 2d
    loglevel   = NOTICE
    EOF
    
    sudo systemctl restart fail2ban

    بهتر از آن، برای سیل‌های ساده، کار را در nginx انجام دهید، جایی که اصلاً هیچی نوشته نمی‌شود. limit_req شمارنده‌هایش را در حافظهٔ مشترک نگه می‌دارد، در میکروثانیه پاسخ می‌دهد به‌جای یک فاصلهٔ پولینگ، و هیچ رکوردی از اینکه چه کسی throttle شده باقی نمی‌گذارد — برای اندازه‌گیریِ zoneها زیرِ بارِ واقعی به رانبوکِ اولین ساعتِ DDoS نگاه کنید.

    # http context: keyed on the truncated address, so nothing complete is
    # held in memory either. 10m of shared state is plenty for a small site.
    limit_req_zone $ip_trunc zone=perip:10m rate=20r/s;
    
    # in the location you want protected
    limit_req zone=perip burst=40 nodelay;

    کلیدزدنِ zone روی $ip_trunc به‌جای $binary_remote_addr یک معاملهٔ عمدی است: حالا محدودیت به‌طورِ یک‌جا روی یک /24 کامل اعمال می‌شود، پس یک دفترِ شلوغ که پشتِ یک بلاک است یک بودجه را مشترک می‌شود. برای یک سایتِ کوچک این معمولاً خوب است و گاهی یک پیشرفت؛ اگر نبود، zone را روی آدرسِ کامل کلید بزنید — وضعیتِ یک rate-limiter در حافظهٔ مشترک زندگی می‌کند و هرگز روی دیسک نوشته نمی‌شود، پس این‌جا بخشی از آنچه دارید کمینه می‌کنید نیست.

  4. 04

    journal را سقف بگذارید و جلوی کپیِ دوم را بگیرید

    journald یک drop-in قبول می‌کند، که به شکلی که ویرایشِ journald.conf ندارد از آپگریدهای پکیج جان سالم به در می‌برد. مقادیرِ زیر حدودِ یک هفته را نگه می‌دارند — به‌اندازهٔ کافی برای بررسیِ چیزی که دوشنبه متوجهش می‌شوید و جمعه شروع شده بود — داخلِ یک سقفِ سختِ ۲۰۰ مگابایت.

    sudo mkdir -p /etc/systemd/journald.conf.d
    sudo tee /etc/systemd/journald.conf.d/retention.conf > /dev/null <<'EOF'
    [Journal]
    Storage=persistent
    SystemMaxUse=200M
    SystemMaxFileSize=20M
    MaxRetentionSec=7day
    MaxFileSec=1day
    ForwardToSyslog=no
    EOF
    
    sudo systemctl restart systemd-journald
    journalctl --disk-usage

    ری‌استارت سقف‌های اندازه را فوراً اعمال می‌کند؛ ساعتِ نگه‌داری همان‌طور که فایل‌های تازه rotate می‌شوند اجرا می‌شود، پس یک journalِ قدیمیِ بیش‌ازحدبزرگ در rotationِ بعدی کوچک می‌شود نه فوراً. journalctl --vacuum-time=7d اگر امروز دیسک را پس می‌خواهید همین الان مجبورش می‌کند.

    ForwardToSyslog=no روی هر ایمیجی که rsyslog را همراه دارد اهمیت دارد: بدونِ آن، هر ورودیِ journal به /var/log/syslog هم append می‌شود، زیرِ برنامهٔ logrotate به‌جای برنامهٔ journald، و شما دو کپی با دو تاریخِ انقضای متفاوت دارید. پیش از آنکه فرض کنید، چک کنید در کدام وضعیت هستید.

    systemctl is-active rsyslog 2>/dev/null || echo "rsyslog not running"
    ls -la /var/log/auth.log /var/log/syslog 2>/dev/null

    اگر آن فایل‌ها وجود دارند، rsyslog مالکشان است و مرحلهٔ شش همان‌جایی است که نگه‌داریِ آن‌ها تنظیم می‌شود. اگر وجود ندارند، journal تنها کپی است و همین الان سقفش زدید.

  5. 05

    جلوی این را بگیرید که اپلیکیشن دوباره چیزی را که حذف کردید لاگ کند

    هیچ‌کدام از موارد بالا به اپلیکیشنِ شما دست نمی‌زند، و یک اپلیکیشن در حالتِ غلط با کمالِ میل آدرسِ کامل، URLِ کامل و توکنِ نشست را در فایلِ خودش می‌نویسد. سه چیز برای چک‌کردن.

    PHP-FPM یک دایرکتیوِ access.log را در کانفیگِ poolِ خودش همراه می‌آورد، که پیش‌فرض کامنت شده اما توسطِ تعدادِ زیادی کنترل‌پنل فعال می‌شود. اگر روشن است، یک کپیِ دومِ هر خطِ درخواست است، از فایلی که کارِ nginxِ شما هرگز لمسش نکرده.

    grep -rn '^access.log' /etc/php/*/fpm/pool.d/ || echo "fpm access log off"

    سطحِ لاگِ فریم‌ورکِ شما تصمیم می‌گیرد که آیا query stringها و بدنهٔ درخواست‌ها روی دیسک می‌نشینند یا نه. حالتِ debug در بیشترِ فریم‌ورک‌ها URLِ کامل را لاگ می‌کند، و یک لینکِ ریست‌کردنِ پسورد یک URLِ کامل است. سطحِ production را تنظیم کنید و مطمئن شوید واقعاً همانی است که لود می‌شود، نه همانی که فکر می‌کنید در حالِ خوانده‌شدن است.

    درایورِ پیش‌فرضِ Docker هرگز rotate نمی‌کند. آن را در سطحِ دیمن درست کنید تا هر کانتینرِ آینده آن سقف را به ارث ببرد. توجه کنید این فقط روی کانتینرهایی اعمال می‌شود که بعد از ری‌استارت ساخته می‌شوند — کانتینرهای موجود همان فایلِ فعلی و بی‌سقفشان را تا وقتی دوباره ساخته شوند نگه می‌دارند.

    sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
    {
      "log-driver": "json-file",
      "log-opts": { "max-size": "10m", "max-file": "3" }
    }
    EOF
    
    sudo systemctl restart docker
    docker inspect --format '{{.HostConfig.LogConfig}}' $(docker ps -q) 2>/dev/null

    اگر یک کانتینر همین الان یک فایلِ بزرگ نگه داشته، بازسازی‌اش با docker compose up -d --force-recreate همان چیزی است که واقعاً تاریخچه را کوتاه می‌کند — فقط ری‌استارت‌کردن همان فایلِ لاگ را نگه می‌دارد.

  6. 06

    نگه‌داری را عمداً تنظیم کنید، در یک جا به‌ازای هر فایل

    logrotate همان‌جایی است که ساعت برای هر چیزی که rsyslog و nginx می‌نویسند زندگی می‌کند. بندِ از قبل موجود را ویرایش کنید نه اینکه یکی دوم اضافه کنید: دو بند که نامِ یک مسیرِ یکسان را می‌برند باعث می‌شوند logrotate با یک خطای duplicate-entry شکست بخورد و rotateکردنِ آن فایل را کاملاً متوقف کند، که رایج‌ترین راهی است که یک تغییرِ نگه‌داری بی‌سروصدا به یک نگه‌داریِ بی‌نهایت تبدیل می‌شود.

    # check for duplicates BEFORE editing, then again after
    sudo logrotate --debug /etc/logrotate.conf 2>&1 | grep -i 'duplicate\|error'

    برای nginx، آن دو فایل ساعت‌های متفاوتی می‌خواهند: آن‌یکی که کوتاه‌شده می‌تواند هفته‌ها زنده بماند، آن‌یکی که آدرس‌های کامل را نگه می‌دارد نباید از همان روز جان سالم به در ببرد. یک بندِ جدا برای لاگِ امنیتی اضافه کنید — یک مسیرِ متفاوت، پس بدونِ تکرار — و آن‌یکیِ از قبل موجود را کوتاه‌تر کنید.

    sudo tee /etc/logrotate.d/nginx-security > /dev/null <<'EOF'
    # The only file on this box that holds complete client addresses.
    # One day, uncompressed so fail2ban can read it. Do not lengthen without
    # a reason you would be happy to write down here.
    /var/log/nginx/security.log {
        daily
        rotate 1
        maxage 1
        missingok
        notifempty
        nocompress
        create 0640 www-data adm
        sharedscripts
        postrotate
            [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
        endscript
    }
    EOF
    
    sudo sed -i 's/^\trotate 14$/\trotate 7/' /etc/logrotate.d/nginx
    sudo logrotate --debug /etc/logrotate.d/nginx-security

    همین حساب‌وکتاب را برای /etc/logrotate.d/rsyslog هم انجام دهید اگر rsyslog نصب است — پیش‌فرض‌هایش چهار هفته از auth.log را نگه می‌دارند، که یعنی چهار هفته از نشست‌های SSHِ خودتان. و مرحلهٔ دیباگ را یک‌بارِ آخر اجرا کنید: دقیقاً چاپ می‌کند کدام فایل‌ها را rotate و حذف می‌کرد، که تنها راه برای تأییدِ این است که عددهایی که تازه تایپ کردید همان عددهای در حالِ اجرا هستند.

  7. 07

    ثابتش کنید، حتی از میانِ بک‌آپ

    اندازه‌گیری‌های مرحلهٔ اول را دوباره اجرا کنید. شمارشِ آدرس‌های کاملِ متمایز در لاگِ با نگه‌داریِ بلند حالا باید از رشدکردن بایستد، و بعد از یک چرخهٔ rotation باید صفر باشد.

    # should print 0 once the pre-change files have rotated out
    zcat -f /var/log/nginx/access.log* 2>/dev/null \
      | grep -oE '^([0-9]{1,3}\.){3}[0-9]{1,3}' \
      | grep -v '\.0$' | sort -u | wc -l
    
    # and the journal should now be bounded
    journalctl --disk-usage

    بعد همان چکی که تقریباً هیچ‌کس اجرایش نمی‌کند: داخلِ بک‌آپ را نگاه کنید. یک ریپازیتوری که هنوز /var/log را حمل می‌کند، همان وفاداری و نگه‌داری‌ای را نگه داشته که تازه یک بعدازظهر وقت گذاشتید حذفش کنید، و تا وقتی قدیمی‌ترین آرشیوتان زنده است همچنان نگهش می‌دارد.

    borg list | head -3          # borg lists oldest first
    borg list ::"$(borg list --short | head -1)" 2>/dev/null \
      | grep -c '^var/log/' || echo "no logs in oldest archive"

    اگر شمارش صفر نیست، یا همان exclusionِ قبلی را اضافه کنید و بگذارید آرشیوهای قدیمی خودشان منقضی شوند، یا عمداً هرسشان کنید. تا وقتی یکی از این‌ها اتفاق نیفتد، نگه‌داریِ واقعیِ شما همان نگه‌داریِ ریپازیتوری است — که تنها مفیدترین جملهٔ این راهنماست، و راحت‌ترینش برای فراموش‌کردن.

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

SP·10 — پرسش‌های متداول

پاسخ‌های سریع

آیا صفرکردنِ آخرین octetِ یک آدرسِ IP واقعاً آن را ناشناس می‌کند؟

نه، و ارزش دارد دربارهٔ این دقیق باشیم نه دلداری‌دهنده. کوتاه‌کردن به یک /24، شبه‌ناشناس‌سازی است: یک بازدیدکننده را به یک بلاکِ تا ۲۵۶ آدرس محدود می‌کند به‌جای یک آدرس، که روی یک ISPِ خانگی یک محله است و روی یک شبکهٔ کسب‌وکارِ کوچک می‌تواند هنوز یک سازمانِ واحد باشد. همان /24 را با یک تایم‌استمپِ دقیق و یک رشتهٔ user-agentِ مفصل ترکیب کنید و شناساییِ مجدد برای یک طرفِ مصمم اغلب ممکن است.

کاری که واقعاً می‌کند این است که همان تک‌فیلدی را حذف می‌کند که یک فایلِ لاگ را به‌سادگی قابلِ join‌شدن با هر دیتاستِ دیگری در دنیا می‌کند، و آن را پیش از نوشته‌شدنِ خط حذف می‌کند، که یعنی مقدارِ دقیق هرگز روی دیسکِ شما وجود نداشته تا توقیف، لو یا احضار شود. این یک کاهشِ بزرگ و واقعی در ریسک است. این ناشناسی نیست، و هر ابزاری که اسمش را این بگذارد دارد بیش‌فروشی می‌کند.

آیا اگر access logِ من کوتاه‌شده باشد fail2ban همچنان کار می‌کند؟

نه از روی فایلِ کوتاه‌شده — captureِ <HOST> با 203.0.113.0 match می‌شود و یک آدرسِ تکی را بن می‌کند که هرگز به شما وصل نشده، درحالی‌که مبدأِ واقعی دست‌نخورده باقی می‌ماند. هر کسی که لاگ‌هایش را بدونِ توجه به این ناشناس کند بی‌سروصدا fail2ban را خاموش کرده.

راه‌حل همان تقسیمی است که بالاتر توضیح داده شد: nginx یک فایلِ دومِ کوتاه‌عمر با آدرس‌های کامل می‌نویسد و fail2ban همان را می‌خواند. توجه کنید /var/log/nginx/error.log هم آدرس‌های کامل را حمل می‌کند و نمی‌شود reformatش کرد، پس jailهایی که رویش کلید خورده‌اند (معمولاً همان‌های auth-failure) بی‌توجه به این کار می‌کنند. برای سوءاستفادهٔ حجمی به‌جای credential stuffing، limit_reqِ nginx از هر دو بهتر است: in-process عمل می‌کند، در میکروثانیه، و اصلاً هیچی را یادداشت نمی‌کند.

چطور بدونِ آدرسِ کلاینت یک 500 روی production را دیباگ کنم؟

با یک request ID، که معمولاً از خودِ آدرس هم بهتر است. nginx به‌ازای هر درخواست $request_id تولید می‌کند؛ آن را در فرمتِ لاگِ خودتان بگذارید، با proxy_set_header X-Request-ID $request_id; به upstream فوروارد کنید، و آن را از اپلیکیشن کنارِ stack trace لاگ بگیرید. حالا یک رشته خطِ access، خطای upstream و exception را به هم گره می‌زند.

از سه جهت از آدرس بهتر است: به‌ازای هر درخواست یکتاست نه به‌ازای هر کلاینت، پس دو شکستِ هم‌زمان با هم قاطی نمی‌شوند؛ از یک کلاینتِ موبایل که آدرسش وسطِ نشست عوض می‌شود جان سالم به در می‌برد؛ و نشان‌دادنش به کاربر روی صفحهٔ خطا امن است، پس یک گزارشِ باگ با همان شناسهٔ دقیقی که لازم دارید می‌رسد. برای آن موردِ نادری که واقعاً به آدرس نیاز دارد — مثلاً یک حملهٔ هدفمند — لاگِ امنیتیِ با نگه‌داریِ کوتاه آن را برای یک روز دارد.

آیا قانوناً موظفم لاگ‌های سرور را نگه دارم؟

برای یک اپراتورِ وب‌سایت یا یک مشتریِ هاستینگ در اتحادیهٔ اروپا، عموماً نه. همان رژیمِ نگه‌داریِ سراسری که آدم‌ها نیمه‌یادشان مانده از دستورالعملِ نگه‌داریِ دادهٔ سالِ ۲۰۰۶ آمده، که دیوانِ دادگستریِ اتحادیهٔ اروپا در سالِ ۲۰۱۴ باطلش کرد؛ قوانینِ ملی‌ای که باقی مانده‌اند بیشتر روی ارائه‌دهندگانِ ارتباطاتِ الکترونیک — تلکوها و ISPها — اعمال می‌شوند نه روی کسی که یک وب‌سرور اجرا می‌کند. GDPR به سمتِ مخالف فشار می‌دهد، به‌سمتِ نگه‌داشتنِ کمتر برای مدتِ کوتاه‌تر. ایالاتِ متحده هم هیچ الزامِ عمومیِ نگه‌داری‌ای برای اپراتورهای سایت ندارد، هرچند یک درخواستِ نگه‌داری می‌تواند برسد و آنچه را می‌توانید با داده‌ای که در همان لحظه وجود دارد انجام دهید عوض کند.

جایی که مشخص می‌شود بر اساسِ صنعت و نقش است: پردازشِ پرداخت، مالیِ تحتِ‌نظارت، سلامت و خودِ ISPبودن همگی وظایفِ خودشان را دارند، و بعضی حوزه‌های قضایی روی اپراتورهای وای‌فایِ عمومی یا پلتفرم تعهداتی می‌گذارند. اینکه ماشین در کدام کشور نشسته جواب را به‌طورِ محسوس عوض می‌کند. این مشاورهٔ حقوقی نیست — اگر در یکی از آن دسته‌ها هستید، پیش از آنکه چیزی را کوتاه کنید، وضعیتِ خودتان را چک کنید.

لاگ‌هایی که Cloudflare یا CDNِ من نگه می‌دارد چه می‌شود؟

آن‌ها فرق نمی‌کند شما روی origin چه‌کاری بکنید وجود دارند، و کوتاه‌کردنِ فایلِ خودتان از میانِ edge به عقب نمی‌رسد. اگر Cloudflare برایتان TLS را terminate می‌کند، پیش از آنکه سرورِ شما اصلاً درگیر شود درخواستِ کامل و آدرسِ واقعیِ کلاینت را می‌بیند، روی برنامهٔ خودش نگهش می‌دارد، و به فرایندِ قانونیِ خودش پاسخ می‌دهد. همین برای هر WAF یا DDoS scrubberِ هاست‌شده هم صادق است.

دو اهرم دارید. چک کنید edge providerِ شما چه چیزی را و چه‌مدت نگه می‌دارد، و هر چیزی که اجازه می‌دهد کمترش کنید، کمترش کنید. یا یک edgeِ تحتِ کنترلِ خودتان اجرا کنید — یک VPSِ کوچک که TLS termination را انجام می‌دهد و از رویِ یک تونل به عقب پراکسی می‌کند — که در آن‌صورت هر چیزی در این راهنما اول روی آن باکس اعمال می‌شود، چون همان ماشینی است که آدرس‌های کامل واقعاً به آن می‌رسند.

آیا باید access logها را کلاً غیرفعال کنم؟

معمولاً نه، و دلیلش عملی است نه فلسفی. بدونِ access log نمی‌توانید به یک شکایتِ abuse جواب دهید، نمی‌توانید یک crawler را از یک حمله تشخیص دهید، نمی‌توانید اندازهٔ ماشین را تعیین کنید، و نمی‌توانید ببینید یک دیپلوی از یک ساعتِ پیش دارد 404 برمی‌گرداند. همچنین توانایی نشان‌دادنِ اینکه واقعاً چه اتفاقی افتاده را از دست می‌دهید اگر یک نفر ادعا کند سرورِ شما کاری کرده که نکرده.

حدِ وسط همان چیزی است که این راهنما می‌سازد: خط را نگه دارید، شناسه را حذف کنید، یک correlation ID اضافه کنید، و بگذارید تنها فایلی که آدرس‌های کامل را نگه می‌دارد در عرضِ یک روز منقضی شود. تنها جایی که access_log off کاملاً درست است استاتیک‌اسِت‌ها هستند — عکس‌ها، CSS، فونت‌ها — که حجمِ خالص‌اند و هیچی به شما نمی‌گویند که نتوانید از همان درخواستِ HTMLای که پیش از آن‌ها آمده به دست بیاورید. یادتان باشد لاگِ error در هر صورت همچنان آدرس‌های کلاینت را روی شکست‌ها ثبت می‌کند، که دلیلِ دیگری است برای دادنِ یک نگه‌داریِ کوتاه به آن، نه رفتن سراغِ کلیدِ خاموش.

آن را به عمل درآورید

VPS ظرف 15 min آنلاین، اختصاصی ظرف 2–12 h تحویل داده می‌شود. از $30.00 با کریپتو شارژ کنید — بدون هویت پیوست‌شده.

استقرار یک VPS