توی این درس یاد میگیری وقتی چیزی خراب میشود، جواب تقریباً همیشه در لاگ است. لاگهای متنی در /var/log هستند و لاگ یکپارچهی systemd در journal. با journalctl (فیلتر زمان با --since "1 hour ago"، سطح با -p err و سرویس با -u)، tail -f /var/log/syslog (دنبالکردن زنده)، dmesg -T (پیامهای هسته) و logger (نوشتن لاگ خودت) کار میکنی؛ میفهمی لاگها با logrotate چرخانده میشوند تا دیسک را پر نکنند؛ و یک روش منظم قدمبهقدم برای عیبیابی یاد میگیری. تمرین: یک سرویس را عمداً خراب کن و علتش را از لاگ پیدا کن.
مسئله: «کار نمیکند» بهتنهایی اطلاعات نیست
Section titled “مسئله: «کار نمیکند» بهتنهایی اطلاعات نیست”کسی میگوید «سایت باز نمیشود». این جمله علت نیست؛ فقط یک نشانه است. بدون یک روش، آدم بهجای فهمیدن، حدس میزند و هر چیزی را عوض میکند و اوضاع بدتر میشود. لاگها ثبت دقیق آنچه واقعاً رخ داد هستند: چه برنامهای، چه ساعتی، چه چیزی گفت. فرق یک مدیر سرور حرفهای با یک تازهکار، بیشتر در این است که اول لاگ را میخواند.
تشبیه: جعبهی سیاه هواپیما
Section titled “تشبیه: جعبهی سیاه هواپیما”سرور هم مثل هواپیما یک جعبهی سیاه دارد: هر برنامه و خود هسته، هر اتفاق مهمی را با ساعت دقیق در آن مینویسند. وقتی چیزی خراب میشود، تو بازرس سقوط هستی: از روی ترتیب و زمان رویدادها پیدا میکنی کدام اولین چیز اشتباه بود (که معمولاً علت است، نه آخرین پیام خطا که فقط پیامد است).
مثالهای عملی
Section titled “مثالهای عملی”این درس روی ماشین systemdدار آزمایشی (Ubuntu، با journald، rsyslog و nginx واقعی) اجرا شده است و من در آن root هستم (روی سرور خودت اگر کاربر عادی هستی sudo بگذار).
مثال ۱: /var/log، لاگهای متنی
Section titled “مثال ۱: /var/log، لاگهای متنی”ls -l /var/logecho "--- شکل چند خط syslog (۳ خط آخرِ غیر از پیامهای kernel):"grep -v ' kernel: ' /var/log/syslog | tail -n 3 | cut -c1-140total 5360lrwxrwxrwx 1 root root 39 Oct 3 12:33 README -> ../../usr/share/doc/systemd/README.logs-rw-r--r-- 1 root root 18904 Oct 3 19:31 alternatives.logdrwxr-xr-x 1 root root 4096 Oct 3 21:58 apt-rw-r----- 1 syslog adm 159119 Oct 4 08:09 auth.log-rw-r--r-- 1 root root 61237 Sep 17 02:10 bootstrap.log-rw-rw---- 1 root utmp 800 Oct 4 08:06 btmp-rw-r----- 1 root adm 1247273 Oct 3 21:15 dmesg-rw-r----- 1 root adm 1097519 Oct 3 21:14 dmesg.0-rw-r----- 1 root adm 116218 Oct 3 21:09 dmesg.1.gz-rw-r----- 1 root adm 18316 Oct 3 12:34 dmesg.2.gz-rw-r--r-- 1 root root 282202 Oct 3 21:58 dpkg.log-rw-r--r-- 1 root root 0 Sep 17 02:10 faillogdrwxr-sr-x+ 1 root systemd-journal 4096 Oct 3 12:34 journal-rw-r----- 1 syslog adm 1116361 Oct 4 07:54 kern.log-rw-rw-r-- 1 root utmp 297184 Oct 4 08:07 lastlogdrwxr-xr-x 1 root adm 4096 Oct 3 12:33 nginxdrwx------ 2 root root 4096 Oct 3 12:33 private-rw-r----- 1 syslog adm 1285360 Oct 4 08:10 syslog-rw-rw-r-- 1 root utmp 26800 Oct 4 07:56 wtmp--- شکل چند خط syslog (۳ خط آخرِ غیر از پیامهای kernel):2026-10-04T08:10:25.207646+00:00 server nightly: منبع /srv/data وجود ندارد؛ بکاپ انجام نشد2026-10-04T08:10:26.235677+00:00 server nightly: بکاپ سالم ساخته شد: /var/backups/nightly/data-2026-10-04_0810.tar.gz2026-10-04T08:10:32.629987+00:00 server crontab[17803]: (root) LIST (root)هر خط زمان، نام ماشین، برنامه[PID] و پیام دارد. چند فایل مهم:
| فایل | چه چیزی |
|---|---|
/var/log/syslog |
تقریباً همهچیز سیستم (Debian/Ubuntu؛ روی Red Hat /var/log/messages) |
/var/log/auth.log |
ورودها: ssh، sudo، su (روی Red Hat /var/log/secure) |
/var/log/kern.log و dmesg |
پیامهای هسته |
/var/log/dpkg.log و apt/ |
نصب و حذف بستهها |
/var/log/nginx/ |
access.log و error.log وبسرور |
/var/log/journal/ |
journal (باینری؛ با journalctl خوانده میشود) |
/var/log/wtmp و btmp |
تاریخچهی ورودها (last) و ورودهای ناموفق (lastb) |
به دسترسیها دقت کن: syslog و auth.log برای کاربر syslog و گروه adm خواندنیاند (-rw-r-----)؛ کاربر عادی نمیتواند بخواندشان (اشتباهات رایج).
مثال ۲: نوشتن و دنبالکردن لاگ، logger و tail -f
Section titled “مثال ۲: نوشتن و دنبالکردن لاگ، logger و tail -f”برای تمرین لازم نیست منتظر یک خطا بمانیم: logger پیام دلخواه را به سیستم لاگ میدهد (برنامهها همین کار را میکنند). -t یک برچسب (نام برنامه) و -p سطح را تعیین میکند:
logger -t myapp "برنامه شروع شد"logger -t myapp -p user.err "اتصال به دیتابیس شکست خورد"sleep 1echo "--- در فایل syslog (با grep):"grep myapp /var/log/syslog | cut -c1-110echo "--- همانها در journal (با -t برچسب):"journalctl -t myapp --no-pager | tail -2echo "--- فقط خطاها (-p err):"journalctl -t myapp -p err --no-pager -o cat--- در فایل syslog (با grep):2026-10-04T07:54:17.253735+00:00 server myapp: برنامه شروع شد2026-10-04T07:54:17.254724+00:00 server myapp: اتصال به دیتابیس شکست خورد2026-10-04T07:54:19.312502+00:00 server myapp: رویداد زندهی شماره 12026-10-04T07:54:20.324316+00:00 server myapp: رویداد زندهی شماره 22026-10-04T07:54:21.345506+00:00 server myapp: رویداد زندهی شماره 32026-10-04T07:54:39.567211+00:00 server myapp: آزمایش خطا2026-10-04T08:06:38.168357+00:00 server myapp: برنامه شروع شد2026-10-04T08:06:38.171838+00:00 server myapp: اتصال به دیتابیس شکست خورد2026-10-04T08:06:40.206604+00:00 server myapp: رویداد زندهی شماره 12026-10-04T08:06:41.214250+00:00 server myapp: رویداد زندهی شماره 22026-10-04T08:06:42.221035+00:00 server myapp: رویداد زندهی شماره 32026-10-04T08:06:46.284238+00:00 server myapp: disk usage at 91 percent2026-10-04T08:10:33.788921+00:00 server myapp: برنامه شروع شد2026-10-04T08:10:33.790238+00:00 server myapp: اتصال به دیتابیس شکست خورد--- همانها در journal (با -t برچسب):Oct 04 08:10:33 server myapp[17831]: برنامه شروع شدOct 04 08:10:33 server myapp[17832]: اتصال به دیتابیس شکست خورد--- فقط خطاها (-p err):اتصال به دیتابیس شکست خوردآزمایش خطااتصال به دیتابیس شکست خورداتصال به دیتابیس شکست خوردهر پیام هم در فایل متنی هست و هم در journal (همان مسیر شکل بالا). و -p err فقط پیامهای سطح خطا و جدیتر را میآورد. سطحها (priority) از ۰ تا ۷ هستند:
| عدد | نام | معنی |
|---|---|---|
| 0 | emerg |
سیستم از کار افتاده |
| 1 | alert |
فوراً اقدام کن |
| 2 | crit |
حالت بحرانی |
| 3 | err |
خطا |
| 4 | warning |
هشدار |
| 5 | notice |
رویداد عادی ولی مهم |
| 6 | info |
اطلاعرسانی |
| 7 | debug |
جزئیات اشکالزدایی |
-p err یعنی ۳ و کوچکتر (emerg تا err). حالا زنده دنبالکردن: tail -f انتهای فایل را باز نگه میدارد و هر خط تازه را همان لحظه چاپ میکند (در ترمینال واقعی تا Ctrl+C). من ۶ ثانیه دنبالش میکنم و در همین فاصله سه پیام مینویسم:
timeout 6 tail -n 0 -f /var/log/syslog > /tmp/loglab-tail.out &sleep 1for i in 1 2 3; do logger -t myapp "رویداد زندهی شماره $i"; sleep 1; donewaitecho "--- آنچه tail -f هنگام دنبالکردن دید:"cut -c1-110 /tmp/loglab-tail.out--- آنچه tail -f هنگام دنبالکردن دید:2026-10-04T08:10:35.836344+00:00 server myapp: رویداد زندهی شماره 12026-10-04T08:10:36.851116+00:00 server myapp: رویداد زندهی شماره 22026-10-04T08:10:37.857514+00:00 server myapp: رویداد زندهی شماره 3-n 0 یعنی از خطهای قدیمی چیزی نشان نده و فقط تازهها. معادل journal: journalctl -f (مثال ۳).
کدام دستور فایل لاگ را زنده دنبال میکند و هر خط تازه را همان لحظه نشان میدهد؟
tail -f انتهای فایل را باز نگه میدارد و خطهای جدید را چاپ میکند؛ با Ctrl+C قطع میشود. (برای لاگهایی که چرخانده میشوند tail -F بهتر است؛ مثال ۷.)
مثال ۳: journalctl، فیلتر با زمان، سطح و سرویس
Section titled “مثال ۳: journalctl، فیلتر با زمان، سطح و سرویس”journalctl بهتنهایی کل journal را چاپ میکند (دهها هزار خط!). مهارت اصلی فیلتر کردن است:
echo "--- ۳ خط آخر (-n 3):"journalctl -n 3 --no-pager | cut -c1-130echo "--- فقط یک سرویس (-u) و فقط از امروز (--since today):"journalctl -u cron --since today --no-pager | tail -2 | cut -c1-130echo "--- بازهی زمانی: بین ۱۰ دقیقه و ۱ دقیقهی پیش، چند خط؟"journalctl --since "10 minutes ago" --until "1 minute ago" --no-pager | wc -l--- ۳ خط آخر (-n 3):Oct 04 08:10:35 server myapp[17842]: رویداد زندهی شماره 1Oct 04 08:10:36 server myapp[17844]: رویداد زندهی شماره 2Oct 04 08:10:37 server myapp[17846]: رویداد زندهی شماره 3--- فقط یک سرویس (-u) و فقط از امروز (--since today):Oct 04 08:09:01 server nightly[17749]: بکاپ سالم ساخته شد: /var/backups/nightly/data-2026-10-04_0809.tar.gzOct 04 08:09:01 server CRON[17737]: pam_unix(cron:session): session closed for user root--- بازهی زمانی: بین ۱۰ دقیقه و ۱ دقیقهی پیش، چند خط؟198بالا سرویس و زمان را فیلتر کردی؛ فیلتر مهم دیگر سطح (-p) است. پنج پیام با پنج سطح مختلف مینویسم و با فیلترهای مختلف میخوانم:
for p in debug info warning err crit; do logger -t prio-demo -p user.$p "پیام با سطح $p"; donesleep 1echo "--- همهی پیامها (بدون فیلتر):"journalctl -t prio-demo --no-pager -o catecho "--- فقط warning و جدیتر (-p warning):"journalctl -t prio-demo -p warning --no-pager -o catecho "--- فقط err و جدیتر (-p err):"journalctl -t prio-demo -p err --no-pager -o cat--- همهی پیامها (بدون فیلتر):پیام با سطح debugپیام با سطح infoپیام با سطح warningپیام با سطح errپیام با سطح critپیام با سطح debugپیام با سطح infoپیام با سطح warningپیام با سطح errپیام با سطح crit--- فقط warning و جدیتر (-p warning):پیام با سطح warningپیام با سطح errپیام با سطح critپیام با سطح warningپیام با سطح errپیام با سطح crit--- فقط err و جدیتر (-p err):پیام با سطح errپیام با سطح critپیام با سطح errپیام با سطح critهر -p همهی سطحهای همان و جدیتر را نشان میدهد؛ برای همین -p err فقط err و crit را آورد. فیلترهای پرکاربرد:
| گزینه | کار |
|---|---|
-u نام |
فقط یک سرویس (-u nginx -u ssh چند تا) |
--since "1 hour ago" |
از یک زمان؛ فرمتها: "2026-10-04 07:00"، today، yesterday، "30 min ago" |
--until "..." |
تا یک زمان |
-p err |
سطح خطا و جدیتر (-p warning..err برای بازه) |
-b / -b -1 |
همین بوت / بوت قبلی (--list-boots همه) |
-k |
فقط پیامهای هسته |
-n 50 |
۵۰ خط آخر |
-f |
زنده دنبال کن |
-r |
از جدید به قدیم |
-o cat |
فقط متن پیام، بدون ساعت و نام |
-g عبارت |
جستجوی عبارت (--grep) |
-t برچسب |
بر اساس برچسب برنامه (SYSLOG_IDENTIFIER) |
و اطلاعات خود journal:
echo "--- بوتهایی که journal دارد:"journalctl --list-boots --no-pager | tail -3echo "--- حجم journal:"journalctl --disk-usagelogger -t myapp "disk usage at 91 percent"sleep 1echo "--- جستجوی یک عبارت داخل پیامها (-g، فقط ۵ دقیقهی اخیر):"journalctl --since "5 minutes ago" -g 'percent' -o cat --no-pager--- بوتهایی که journal دارد:IDX BOOT ID FIRST ENTRY LAST ENTRY 0 3d4eccadb48542a4888f8ac6fbea5081 Sat 2026-10-03 12:34:09 UTC Sun 2026-10-04 08:10:40 UTC--- حجم journal:Archived and active journals take up 83.4M in the file system.--- جستجوی یک عبارت داخل پیامها (-g، فقط ۵ دقیقهی اخیر):disk usage at 91 percentdisk usage at 91 percentمثال ۴: dmesg، پیامهای هسته
Section titled “مثال ۴: dmesg، پیامهای هسته”هسته پیامهایش (سختافزار، درایورها، دیسک، حافظه، شبکه) را در یک بافر حلقوی نگه میدارد و dmesg آن را میخواند. مثلاً وقتی یک دیسک خطا میدهد یا پروسهای بهخاطر کمبود حافظه کشته میشود (OOM killer)، اول اینجا ثبت میشود:
echo "--- چند خط اول بافر هسته (زمان بهصورت ثانیه از شروع بوت):"dmesg | head -3 | cut -c1-110echo "--- با زمان خوانا (-T) و فقط هشدار و خطا (--level):"dmesg -T --level=err,warn | tail -3 | cut -c1-130echo "--- نشانههای رایج مشکل (اگر باشند):"dmesg | grep -iE 'out of memory|oom-kill|oom_reaper|i/o error|segfault' | tail -2 || trueecho "(بالا خالی یعنی چنین مشکلی ثبت نشده)"--- چند خط اول بافر هسته (زمان بهصورت ثانیه از شروع بوت):[86954.638718] br-fb2628254424: port 1(vetha31ea25) entered blocking state[86954.639370] br-fb2628254424: port 1(vetha31ea25) entered disabled state[86954.639397] vetha31ea25: entered allmulticast mode--- با زمان خوانا (-T) و فقط هشدار و خطا (--level):[Sat Oct 3 22:34:54 2026] overlayfs: lowerdir is in-use as upperdir/workdir of another mount, accessing files from both mounts wi[Sat Oct 3 23:10:12 2026] overlayfs: lowerdir is in-use as upperdir/workdir of another mount, accessing files from both mounts wi--- نشانههای رایج مشکل (اگر باشند):(بالا خالی یعنی چنین مشکلی ثبت نشده)-T زمان را به ساعت خوانا تبدیل میکند (روی ماشینهایی که خوابیدهاند ممکن است کمی نادقیق باشد) و --level سطح را فیلتر میکند. پیامهایی که اینجا میبینی مال داکری هستند که این ماشین آزمایشی روی آن اجرا میشود (هسته با کانتینر مشترک است)؛ روی سرور خودت پیامهای سختافزار و دیسک واقعی را میبینی. عبارتهای مهم برای جستجو: Out of memory، I/O error، segfault، EXT4-fs error.
مثال ۵: لاگ ورود و امنیت، auth.log
Section titled “مثال ۵: لاگ ورود و امنیت، auth.log”هر تلاش ورود با ssh (موفق یا ناموفق) و هر sudo ثبت میشود. یک تلاش ناموفق میسازم (کاربری که وجود ندارد) و دنبالش را در لاگ میگیرم:
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new nosuchuser@localhost true 2>&1 | tail -1su - ali -c 'sudo whoami' > /dev/nullsleep 1echo "--- در auth.log:"grep -E 'Invalid user|Failed' /var/log/auth.log | tail -2 | cut -c1-130echo "--- و فعالیتهای sudo:"grep 'sudo:' /var/log/auth.log | grep COMMAND | tail -2 | cut -c1-140echo "--- ورودهای موفق اخیر (last):"last -n 3 | head -3nosuchuser@localhost: Permission denied (publickey,password).--- در auth.log:2026-10-04T08:06:47.454405+00:00 server sshd[17448]: Invalid user nosuchuser from ::1 port 547622026-10-04T08:10:43.112733+00:00 server sshd[17882]: Invalid user nosuchuser from ::1 port 60168--- و فعالیتهای sudo:2026-10-04T08:06:47.468325+00:00 server sudo: ali : PWD=/home/ali ; USER=root ; COMMAND=/usr/bin/whoami2026-10-04T08:10:43.126274+00:00 server sudo: ali : PWD=/home/ali ; USER=root ; COMMAND=/usr/bin/whoami--- ورودهای موفق اخیر (last):root pts/0 tmux(16895).%0 Sun Oct 4 07:55 - 07:56 (00:00)root pts/0 tmux(15989).%0 Sun Oct 4 07:41 - 07:41 (00:00)root pts/0 tmux(15434).%0 Sun Oct 4 07:30 - 07:31 (00:00)Invalid user nosuchuser from آدرس port N: این همان الگوی حملههای حدس رمز است که بهمحض باز شدن پورت ۲۲ روی اینترنت میبینی (و fail2ban در پروژهی پایانی از همین لاگ برای بستن مهاجمها استفاده میکند). last تاریخچهی ورودها را از wtmp میخواند و lastb ورودهای ناموفق را (به root نیاز دارد).
مثال ۶: یک سرویس را عمداً خراب کن و علتش را پیدا کن (تمرین اصلی)
Section titled “مثال ۶: یک سرویس را عمداً خراب کن و علتش را پیدا کن (تمرین اصلی)”بهترین راه یاد گرفتن عیبیابی این است که خودت یک خرابی بسازی. فایل تنظیمات nginx را با یک غلط تایپی خراب میکنم (یک directive که وجود ندارد) و آن را ریستارت میکنم:
cp /etc/nginx/nginx.conf /tmp/loglab-nginx.conf.baksed -i '0,/^events {/s//foo_bar on;\nevents {/' /etc/nginx/nginx.confecho "--- ریستارت سرویس:"systemctl restart nginxecho "کد خروج: $?"--- ریستارت سرویس:Job for nginx.service failed because the control process exited with error code.See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.کد خروج: 1سرویس بالا نیامد و systemctl فقط یک راهنمایی کلی داد. قدم اول: وضعیت سرویس. قدم دوم: لاگ خود آن سرویس:
echo "--- ۱) systemctl status:"systemctl status nginx --no-pager | head -9echo "--- ۲) لاگ سرویس (journalctl -u):"journalctl -u nginx --no-pager -n 6 -o catecho "--- ۳) خود برنامه میتواند تنظیمات را آزمایش کند (nginx -t):"nginx -t 2>&1--- ۱) systemctl status:× nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: enabled) Active: failed (Result: exit-code) since Sun 2026-10-04 08:10:44 UTC; 10ms ago Docs: man:nginx(8) Process: 17901 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE) CPU: 4ms
Oct 04 08:10:44 server systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...Oct 04 08:10:44 server nginx[17901]: 2026/10/04 08:10:44 [emerg] 17901#17901: unknown directive "foo_bar" in /etc/nginx/nginx.conf:7--- ۲) لاگ سرویس (journalctl -u):Starting nginx.service - A high performance web server and a reverse proxy server...2026/10/04 08:10:44 [emerg] 17901#17901: unknown directive "foo_bar" in /etc/nginx/nginx.conf:7nginx: configuration file /etc/nginx/nginx.conf test failednginx.service: Control process exited, code=exited, status=1/FAILUREnginx.service: Failed with result 'exit-code'.Failed to start nginx.service - A high performance web server and a reverse proxy server.--- ۳) خود برنامه میتواند تنظیمات را آزمایش کند (nginx -t):2026/10/04 08:10:44 [emerg] 17906#17906: unknown directive "foo_bar" in /etc/nginx/nginx.conf:7nginx: configuration file /etc/nginx/nginx.conf test failedعلت پیدا شد: unknown directive "foo_bar" in /etc/nginx/nginx.conf:N با شمارهی خط. دقت کن آخرین خط لاگ («Failed to start…») فقط پیامد است؛ اولین خط خطا ([emerg] unknown directive) علت واقعی. حالا درست میکنم و بررسی:
cp /tmp/loglab-nginx.conf.bak /etc/nginx/nginx.confnginx -t 2>&1systemctl restart nginx && echo "سرویس بالا آمد"curl -sI http://localhost | head -1nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successfulسرویس بالا آمدHTTP/1.1 200 OKیک نوع خرابی دیگر: سرویس بالا میآید ولی سایت خطا میدهد. اینجا لاگ خود برنامه (/var/log/nginx/error.log) راه است. با گرفتن دسترسی فایل صفحه (درس دسترسیها):
chmod 000 /var/www/html/index.nginx-debian.htmlecho "--- پاسخ سایت:"curl -sI http://localhost | head -1echo "--- لاگ خطای nginx:"tail -n 1 /var/log/nginx/error.log | cut -c1-170chmod 644 /var/www/html/index.nginx-debian.htmlecho "--- بعد از درستکردن دسترسی:"curl -sI http://localhost | head -1--- پاسخ سایت:HTTP/1.1 403 Forbidden--- لاگ خطای nginx:2026/10/04 08:10:44 [error] 17915#17915: *2 open() "/var/www/html/index.nginx-debian.html" failed (13: Permission denied), client: ::1, server: _, request: "HEAD / HTTP/1--- بعد از درستکردن دسترسی:HTTP/1.1 200 OK403 Forbidden و در error.log دلیلش: open() ... failed (13: Permission denied). دو خرابی متفاوت، دو جای متفاوت برای لاگ: اگر سرویس بالا نمیآید journalctl -u، اگر بالاست ولی کار نمیکند لاگهای خود برنامه (/var/log/نام/). و یک مورد سوم که از درس پورتها میشناسی، پورت اشغالشده:
systemctl stop nginxpython3 -m http.server 80 --bind 0.0.0.0 > /dev/null 2>&1 &sleep 1systemctl restart nginxecho "کد خروج: $?"journalctl -u nginx --no-pager -n 12 -o cat | grep -E 'bind\(\)' | head -2echo "--- چه کسی پورت 80 را گرفته؟"ss -tlnp 'sport = :80' | awk 'NR>1 {print $4, $6}' | cut -c1-70pkill -f '[h]ttp.server 80'sleep 1systemctl restart nginx && echo "بعد از آزاد شدن پورت: بالا آمد"Job for nginx.service failed because the control process exited with error code.See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.کد خروج: 1nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)--- چه کسی پورت 80 را گرفته؟0.0.0.0:80 users:(("python3",pid=17930,fd=3))بعد از آزاد شدن پورت: بالا آمدمثال ۷: چرخش لاگ با logrotate
Section titled “مثال ۷: چرخش لاگ با logrotate”لاگها بینهایت بزرگ میشوند و دیسک را پر میکنند (درس منابع). logrotate (که روزانه با یک timer اجرا میشود) فایلهای بزرگ را «میچرخاند»: app.log را app.log.1 میکند، قبلیها را فشرده میکند (.2.gz)، از قدیمیها فقط تعداد مشخصی نگه میدارد و یک فایل خالی تازه میسازد. برای یک برنامهی نمونه یک قاعده مینویسم (در /etc/logrotate.d/). بهجای «روزانه»، برای آزمایش گفتهام وقتی از ۱ کیلوبایت گذشت بچرخان:
cat > /etc/logrotate.d/lx-app <<'EOF'/var/log/lx-app.log { su root adm size 1k rotate 3 compress delaycompress missingok notifempty create 640 root adm}EOFfor i in $(seq 1 60); do echo "$(date +%T) خط شمارهی $i از لاگ برنامه ........................" >> /var/log/lx-app.log; doneecho "--- قبل:"ls -l /var/log/lx-app.log* | awk '{print $5, $9}'echo "--- logrotate -d (فقط توضیح میدهد چه میکرد، چیزی عوض نمیشود):"logrotate -d /etc/logrotate.d/lx-app 2>&1 | grep -E 'considering|log needs|renaming /var/log/lx-app.log to'--- قبل:4971 /var/log/lx-app.log--- logrotate -d (فقط توضیح میدهد چه میکرد، چیزی عوض نمیشود):considering log /var/log/lx-app.log log needs rotatingrenaming /var/log/lx-app.log to /var/log/lx-app.log.1معنی هر خط قاعده:
| خط | معنی |
|---|---|
su root adm |
با چه کاربر و گروهی بچرخان. روی Ubuntu لازم است (اشتباهات رایج ۷) |
size 1k |
وقتی بزرگتر از ۱ کیلوبایت شد. (در واقعیت: daily یا weekly یا size 100M) |
rotate 3 |
۳ نسخهی قدیمی نگه دار؛ قدیمیتر پاک میشود |
compress |
نسخههای قدیمی را با gzip فشرده کن |
delaycompress |
فشردهسازی را یک دور عقب بینداز: .1 متنی میماند تا برنامهای که هنوز به آن مینویسد گیر نکند |
missingok |
اگر فایل لاگ نبود خطا نده |
notifempty |
فایل خالی را نچرخان |
create 640 root adm |
بعد از چرخش یک فایل خالی تازه با این مالک و دسترسی بساز |
حالا tail -f و tail -F را هر دو روی این فایل باز میکنم، لاگ را میچرخانم و یک خط تازه مینویسم تا فرق ببینی. (tail -F بعد از چرخش چند ثانیه طول میکشد تا فایل تازه را ببیند، پس اجازه میدهم ۹ ثانیه کار کنند.)
timeout 9 tail -n 0 -f /var/log/lx-app.log > /tmp/loglab-f.out 2>&1 &timeout 9 tail -n 0 -F /var/log/lx-app.log > /tmp/loglab-F.out 2>&1 &sleep 1echo "خط قبل از چرخش" >> /var/log/lx-app.logsleep 1logrotate -f /etc/logrotate.d/lx-appecho "--- بعد از چرخش:"ls -l /var/log/lx-app.log* | awk '{print $5, $9}'echo "خط بعد از چرخش" >> /var/log/lx-app.logwaitecho "--- tail -f (به فایل قدیمی چسبیده ماند):"cat /tmp/loglab-f.outecho "--- tail -F (فایل تازه را پیدا کرد و دنبال کرد):"cat /tmp/loglab-F.out--- بعد از چرخش:0 /var/log/lx-app.log4997 /var/log/lx-app.log.1--- tail -f (به فایل قدیمی چسبیده ماند):خط قبل از چرخش--- tail -F (فایل تازه را پیدا کرد و دنبال کرد):خط قبل از چرخشtail: '/var/log/lx-app.log' has been replaced; following new fileخط بعد از چرخشبعد از چرخش app.log به app.log.1 رفت و یک app.log خالی تازه ساخته شد. tail -f به فایل قدیمی (که حالا .1 است) چسبیده ماند و «خط بعد از چرخش» را ندید؛ tail -F (حرف بزرگ؛ follow by name, retry) متوجه شد فایل با آن اسم عوض شده (has been replaced; following new file) و دنبال فایل تازه رفت. برای هر لاگی که چرخانده میشود همیشه tail -F بزن. و قاعدههای چرخش برنامههای سیستم:
rm -f /var/log/lx-app.log* /etc/logrotate.d/lx-appsed -i '/lx-app/d' /var/lib/logrotate/statusecho "--- قاعدههای logrotate سیستم:"ls /etc/logrotate.decho "--- تنظیمات عمومی (بدون توضیح):"grep -v '^#' /etc/logrotate.conf | grep -v '^$' | head -8echo "--- کی اجرا میشود؟"systemctl list-timers logrotate.timer --no-pager | cut -c1-110--- قاعدههای logrotate سیستم:alternativesaptbtmpdpkgnginxrsyslogufwwtmp--- تنظیمات عمومی (بدون توضیح):weeklysu root admrotate 4createinclude /etc/logrotate.d--- کی اجرا میشود؟NEXT LEFT LAST PASSED UNIT ACTIVATESMon 2026-10-05 00:00:00 UTC 15h Sun 2026-10-04 00:00:01 UTC 6h ago logrotate.timer logrotate.service
1 timers listed.Pass --all to see loaded but inactive timers, too.مثال ۸: ده دقیقهی اول عیبیابی، یک اسکریپت چک
Section titled “مثال ۸: ده دقیقهی اول عیبیابی، یک اسکریپت چک”وقتی به یک سرور مشکوک وصل میشوی، همیشه یک مجموعهی ثابت از چیزها را اول نگاه کن. میشود اینها را در یک اسکریپت گذاشت:
#!/bin/bash# یک نگاه سریع به سلامت سرورecho "== ۱) بار و مدت روشنبودن:"; uptimeecho "== ۲) دیسک (ریشه):"; df -h / | tail -1echo "== ۳) حافظه (MB):"; free -m | awk 'NR==2 {print "کل="$2" مصرف="$3" آزاد="$4" available="$7}'echo "== ۴) سرویسهای شکستخورده:"; systemctl --failed --no-legend | cut -c1-80 || trueecho "== ۵) آخرین خطاهای journal (این بوت، ۳ خط):"; journalctl -p err -b --no-pager -n 3 -o cat 2>/dev/null | cut -c1-110echo "== ۶) پورتهای در حال گوشدادن:"; ss -tlnp | awk 'NR>1 {print $4}' | sort -u | tr '\n' ' '; echochmod +x /usr/local/bin/lx-diag.sh/usr/local/bin/lx-diag.sh== ۱) بار و مدت روشنبودن: 08:10:57 up 1 day, 11:33, 0 user, load average: 0.06, 0.10, 0.14== ۲) دیسک (ریشه):overlay 453G 22G 408G 6% /== ۳) حافظه (MB):کل=3916 مصرف=1294 آزاد=1373 available=2621== ۴) سرویسهای شکستخورده:== ۵) آخرین خطاهای journal (این بوت، ۳ خط):پیام با سطح critFailed to start nginx.service - A high performance web server and a reverse proxy server.Failed to start nginx.service - A high performance web server and a reverse proxy server.== ۶) پورتهای در حال گوشدادن:0.0.0.0:22 0.0.0.0:80 [::]:22 [::]:80این شش نگاه، بیشتر مشکلها را در یک دقیقه دستهبندی میکند: بار زیاد (CPU)، دیسک یا حافظهی پر، یک سرویس شکستخورده، خطای تازهی journal، یا یک پورت که باید باز باشد و نیست.
پشت پرده: syslog، journald و rsyslog
Section titled “پشت پرده: syslog، journald و rsyslog”دو سیستم لاگ کنار هم کار میکنند. journald پیامها را از همهجا (stdout سرویسها، syslog، هسته) میگیرد و در یک پایگاه دادهی ساختیافته نگه میدارد: هر پیام کلی فیلد دارد، نه فقط یک خط متن. ببین یک پیام ساده چند فیلد دارد:
journalctl -t myapp -n 1 -o verbose --no-pager | grep -E '^ (_PID|_UID|_COMM|_HOSTNAME|SYSLOG_IDENTIFIER|PRIORITY|MESSAGE|_TRANSPORT)=' | cut -c1-90 _UID=0 _HOSTNAME=server _TRANSPORT=syslog PRIORITY=5 SYSLOG_IDENTIFIER=myapp _COMM=logger MESSAGE=disk usage at 91 percent _PID=17868برای همین میشود بر اساس _PID، _UID، PRIORITY، _SYSTEMD_UNIT و… دقیق فیلتر کرد (journalctl _PID=123). rsyslog هم یک زمانبندی کلاسیک است که طبق قاعدههایش (facility.priority → فایل) نسخهی متنی را در /var/log مینویسد:
echo "--- قاعدههای rsyslog (facility.priority ← فایل):"grep -v '^#' /etc/rsyslog.d/50-default.conf | grep -v '^$' | head -5echo "--- journal کجا ذخیره میشود؟ (دائمی یا فقط RAM)"ls -d /var/log/journal /run/log/journal 2>&1--- قاعدههای rsyslog (facility.priority ← فایل):auth,authpriv.* /var/log/auth.log*.*;auth,authpriv.none -/var/log/syslogkern.* -/var/log/kern.logmail.* -/var/log/mail.logmail.err /var/log/mail.err--- journal کجا ذخیره میشود؟ (دائمی یا فقط RAM)/run/log/journal/var/log/journal- facility نوع منبع:
kern،user،mail،auth،daemon… و priority همان ۰ تا ۷ بالا.auth,authpriv.*یعنی همهی سطحهای ورود و احراز هویت ←/var/log/auth.log. - اگر
/var/log/journalوجود داشته باشد journal دائمی است (بعد از ریبوت میماند)؛ وگرنه فقط در/run/log/journal(RAM) است و با ریبوت پاک میشود، که برای بررسی «چرا ریبوت شد؟» فاجعه است.
روش عیبیابی قدمبهقدم
Section titled “روش عیبیابی قدمبهقدم”بعد از همهی ابزارها، مهمتر از آنها روش است:
- نشانه را دقیق بنویس: چه چیزی کار نمیکند؟ از چه ساعتی؟ برای همه یا یک نفر؟ چه چیزی تغییر کرده بود (دیپلوی، تنظیمات، بهروزرسانی)؟
- از بیرون به داخل بسنج: آیا ماشین جواب میدهد (
ping)؟ پورت باز است (nc -zv)؟ سرویس بالاست (systemctl status)؟ (درس شبکه و پورتها.) - لاگ را بخوان، با کران زمانی:
journalctl -u سرویس --since "30 min ago"؛ لاگ برنامه در/var/log/. اولین خطای نزدیک زمان مشکل را پیدا کن، نه آخرین. - منابع را ببین:
df -h(دیسک پر؟)،free -h،uptime،ss -tlnp. - یک فرضیه، یک تغییر: هر بار فقط یک چیز را عوض کن و بعد آزمایش کن؛ اگر چند چیز را با هم عوض کنی نمیفهمی کدام درست کرد (یا خرابتر کرد).
- قبل از تغییر پشتیبان بگیر (
cp فایل فایل.bak) و بعد از تغییر آزمایش کن (nginx -t،sshd -t). - ثبت کن: چه شد، چرا شد، چه کردی. دفعهی بعد (یا همکارت) همان را میخواهد.
جدولهای مرجع
Section titled “جدولهای مرجع”| دستور | کار |
|---|---|
tail -n 50 /var/log/syslog |
۵۰ خط آخر |
tail -F /var/log/app.log |
دنبالکردن زنده (حتی بعد از چرخش) |
grep -i error /var/log/app.log |
جستجو (بدون حساسیت به حروف) |
journalctl -u نام -n 50 |
لاگ یک سرویس |
journalctl -u نام -f |
زنده |
journalctl --since "1 hour ago" -p err |
خطاهای یک ساعت اخیر |
journalctl -b -1 |
بوت قبلی |
journalctl -k / dmesg -T |
پیامهای هسته |
logger -t برچسب -p user.err "پیام" |
نوشتن لاگ |
last / lastb |
تاریخچهی ورودها / ورودهای ناموفق |
logrotate -d فایل / -f فایل |
آزمایش / اجبار چرخش |
journalctl --disk-usage |
حجم journal |
systemctl --failed |
سرویسهای شکستخورده |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) خواندن لاگ با کاربر عادی
Section titled “۱) خواندن لاگ با کاربر عادی”useradd -m -s /bin/bash bobecho "--- bob: auth.log را میخواند:"su - bob -c 'cat /var/log/auth.log' 2>&1 | head -1echo "--- راهحل: bob را عضو گروه adm میکنم:"usermod -aG adm bobsu - bob -c 'tail -n 1 /var/log/auth.log | cut -c1-80'userdel -r bob 2>/dev/null--- bob: auth.log را میخواند:cat: /var/log/auth.log: Permission denied--- راهحل: bob را عضو گروه adm میکنم:2026-10-04T08:10:58.047953+00:00 server su[18082]: pam_unix(su-l:session): sessiلاگها برای همه خواندنی نیستند (ممکن است راز داشته باشند). راهحل: sudo یا عضو کردن کاربر در گروه adm (برای فایلهای /var/log) و systemd-journal (برای journal). کاربر عادی بدون این گروهها در journalctl فقط پیامهای خودش را میبیند؛ و journalctl در این حالت خودش یک یادآوری مینویسد که اعضای کدام گروهها همهچیز را میبینند.
۲) tail -f روی لاگی که چرخانده میشود
Section titled “۲) tail -f روی لاگی که چرخانده میشود”مثال ۷: بعد از چرخش tail -f به فایل قدیمی چسبیده میماند و ساکت میشود. راهحل: tail -F.
۳) خواندن همهچیز بدون کران
Section titled “۳) خواندن همهچیز بدون کران”journalctl بدون -u و --since دهها هزار خط میدهد و علت در انبوه گم میشود. راهحل: همیشه سرویس و بازهی زمانی را محدود کن (-u، --since، -n، -p).
۴) توجه به پیامد بهجای علت
Section titled “۴) توجه به پیامد بهجای علت”آخرین خط لاگ معمولاً «Failed to start» است؛ علت چند خط قبلتر است (مثال ۶). راهحل: از اولین نشانهی خطا (نزدیک زمان مشکل) به جلو بخوان.
۵) فراموشکردن منطقهی زمانی
Section titled “۵) فراموشکردن منطقهی زمانی”لاگها ممکن است به وقت UTC باشند و ساعت دیواری تو چیز دیگر. راهحل: date و journalctl --utc، و هنگام گزارش مشکل منطقه را هم بنویس.
۶) پر شدن دیسک با لاگ
Section titled “۶) پر شدن دیسک با لاگ”du -sh /var/log و journalctl --disk-usage را گاهی ببین؛ journal را با journalctl --vacuum-size=200M محدود کن و برای فایلهای لاگ برنامهها logrotate بنویس (مثال ۷):
echo "--- محدودکردن journal به 1G (اگر کوچکتر باشد چیزی پاک نمیشود):"journalctl --vacuum-size=1G 2>&1 | tail -1--- محدودکردن journal به 1G (اگر کوچکتر باشد چیزی پاک نمیشود):Vacuuming done, freed 0B of archived journals from /var/log/journal.۷) قاعدهی logrotate بدون su
Section titled “۷) قاعدهی logrotate بدون su”روی Ubuntu پوشهی /var/log برای گروه syslog قابلنوشتن است، و logrotate برای فایلهای داخل پوشهای که «ناامن» حساب میشود، چرخش را رد میکند:
cat > /etc/logrotate.d/lx-nosu <<'EOF'/var/log/lx-nosu.log { size 1k rotate 2}EOFseq 1 200 > /var/log/lx-nosu.loglogrotate -f /etc/logrotate.d/lx-nosu 2>&1 | cut -c1-120echo "کد خروج: ${PIPESTATUS[0]}"rm -f /etc/logrotate.d/lx-nosu /var/log/lx-nosu.log*sed -i '/lx-nosu/d' /var/lib/logrotate/statuserror: skipping "/var/log/lx-nosu.log" because parent directory has insecure permissions (It's world writable or writablکد خروج: 1skipping ... because parent directory has insecure permissions ... Set "su" directive: خود پیام راهحل را میگوید. راهحل: یک خط su root adm به قاعده اضافه کن (مثال ۷). نتیجهی بدتر: اگر این خطا را نبینی، لاگ هیچوقت نمیچرخد و یک شب دیسک پر میشود؛ پس بعد از نوشتن هر قاعدهی تازه حتماً یک بار با logrotate -d (و در صورت لزوم -f) آزمایشش کن.
در ۱۰ دقیقهی اخیر چند پیام سطح خطا (err) یا بدتر در journal ثبت شده؟ و آخرین ۲ تای آنها چیست (فقط متن)؟
دیدن جواب
echo "تعداد خطاها در ۱۰ دقیقهی اخیر: $(journalctl --since "10 minutes ago" -p err --no-pager -o cat | wc -l)"journalctl --since "10 minutes ago" -p err --no-pager -o cat | tail -2 | cut -c1-120تعداد خطاها در ۱۰ دقیقهی اخیر: 12Failed to start nginx.service - A high performance web server and a reverse proxy server.Failed to start nginx.service - A high performance web server and a reverse proxy server.با -p err فقط خطاها و بدتر؛ و با --since کران زمانی. این دو را همیشه با هم بزن.
تمرین اصلی: یک سرویس را عمداً خراب کن و علتش را از لاگ پیدا کن. در nginx، پورت listen را به یک مقدار بیمعنی (listen abc;) تغییر بده، سرویس را ریستارت کن، با journalctl -u nginx و nginx -t علت را پیدا کن، فایل را برگردان و سرویس را بالا بیاور.
دیدن جواب
cp /etc/nginx/sites-available/default /tmp/loglab-default.baksed -i '0,/listen 80 default_server;/s//listen abc;/' /etc/nginx/sites-available/defaultsystemctl restart nginx 2>&1 | head -2echo "--- علت از journal:"journalctl -u nginx --no-pager -n 6 -o cat | grep -iE 'emerg|invalid|error' | head -2echo "--- و با nginx -t:"nginx -t 2>&1 | head -2cp /tmp/loglab-default.bak /etc/nginx/sites-available/defaultnginx -t 2>&1 | tail -1systemctl restart nginx && echo "سرویس دوباره بالا آمد"Job for nginx.service failed because the control process exited with error code.See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.--- علت از journal:2026/10/04 08:10:58 [emerg] 18115#18115: host not found in "abc" of the "listen" directive in /etc/nginx/sites-enabled/default:22--- و با nginx -t:2026/10/04 08:10:58 [emerg] 18120#18120: host not found in "abc" of the "listen" directive in /etc/nginx/sites-enabled/default:22nginx: configuration file /etc/nginx/nginx.conf test failednginx: configuration file /etc/nginx/nginx.conf test is successfulسرویس دوباره بالا آمدیک «دیدهبان» ساده بنویس: فایل لاگی را با tail -F دنبال کن و هر وقت خطی با ERROR آمد، یک پیام هشدار چاپ کن. با یک حلقه چند خط (INFO و ERROR) به آن بنویس و ثابت کن فقط خطاها هشدار دادند.
دیدن جواب
: > /tmp/loglab-watch.log( timeout 6 tail -n 0 -F /tmp/loglab-watch.log | grep --line-buffered ERROR | while read -r line; do echo "هشدار! $line"; done ) &sleep 1echo "INFO شروع" >> /tmp/loglab-watch.logecho "ERROR دیسک پر است" >> /tmp/loglab-watch.logecho "INFO ادامه" >> /tmp/loglab-watch.logecho "ERROR اتصال قطع شد" >> /tmp/loglab-watch.logwaitهشدار! ERROR دیسک پر استهشدار! ERROR اتصال قطع شدآزمونک
Section titled “آزمونک”کدام دستور لاگهای خطا و بدتر یک سرویس را از یک ساعت اخیر نشان میدهد؟
-u سرویس، --since زمان و -p err سطح را فیلتر میکند.
سرویسی بالا نمیآید. اولین جایی که نگاه میکنی؟
وضعیت و لاگ خود سرویس؛ بعد لاگ برنامه در /var/log/نام.
تفاوت tail -f و tail -F برای لاگی که چرخانده میشود؟
برای لاگهای چرخان همیشه tail -F.
در لاگ، «آخرین» خط خطا یا «اولین» خط خطا به علت اصلی نزدیکتر است؟
«Failed to start» پیامد است؛ دلیل چند خط قبلتر (مثلاً unknown directive).
dmesg چه چیزی را نشان میدهد؟
برای خطاهای دیسک، OOM killer و درایورها اول dmesg را ببین.
logrotate چه میکند و چرا لازم است؟
با یک timer روزانه اجرا میشود؛ قاعدهها در /etc/logrotate.d/.
جمعبندی
Section titled “جمعبندی”- لاگها دو جا هستند: متنی در
/var/log(syslog،auth.log،nginx/،dpkg.log) و journal باjournalctl؛ dmesg برای هسته. فایلها برای گروهadmخواندنیاند. journalctl: همیشه فیلتر کن:-uسرویس،--since/--untilزمان،-p errسطح،-bبوت،-n،-fزنده،-o cat،-gجستجو.logger -t برچسب -p user.err "پیام"برای نوشتن لاگ.- سطحها:
emerg(0) تاdebug(7)؛-p errیعنی ۳ و کمتر. tail -fزنده؛ برای لاگ چرخانtail -F.logrotateفایلها را میچرخاند و فشرده میکند تا دیسک پر نشود.- عیبیابی: نشانهی دقیق ← از بیرون به داخل (
ping،nc -zv،systemctl status) ← لاگ با کران زمانی، اولین خطا ← منابع (df،free) ← یک فرضیه و یک تغییر ← پشتیبان و آزمایش (nginx -t) ← ثبت. - سرویس بالا نمیآید:
journalctl -u؛ بالاست ولی کار نمیکند: لاگ خود برنامه.
| دستور | کاری که میکند |
|---|---|
tail -n 50 /var/log/syslog / tail -F فایل | ۵۰ خط آخر / دنبالکردن زنده |
journalctl -u نام -n 50 --no-pager | لاگ یک سرویس |
journalctl -u نام -f | زنده |
journalctl --since "1 hour ago" -p err | خطاهای یک ساعت اخیر |
journalctl -b -1 / journalctl --list-boots | بوت قبلی / فهرست بوتها |
journalctl -g عبارت -o cat | جستجو، فقط متن |
dmesg -T --level=err,warn | هشدار و خطای هسته با زمان خوانا |
logger -t myapp -p user.err "پیام" | نوشتن لاگ |
grep -E "Invalid user|Failed" /var/log/auth.log | تلاشهای ورود ناموفق |
systemctl --failed | سرویسهای شکستخورده |
logrotate -d /etc/logrotate.d/app | آزمایش قاعدهی چرخش |
journalctl --disk-usage | حجم journal |