آموزش ساخت و اجرای سرویس systemd در لینوکس
با یادگیری ساخت فایل unit در مسیر /etc/systemd/system برنامه خود را به یک سرویس دائمی تبدیل کنید. این راهنما نحوه مدیریت خودکار، لاگگیری و ایمنسازی سرویسها را توضیح میدهد.
سرویس systemd چیست و چرا به آن نیاز دارید
یک سرویس systemd یک فایل متنی کوچک است که به سرور شما میگوید چگونه یک برنامه را اجرا کند: آن را هنگام بوت شروع کند، در صورت کرش کردن دوباره راهاندازی کند و خروجی آن را به لاگ سیستم بفرستد. این تمام وظیفه آن است. برنامهای که بهصورت دستی در یک نشست SSH اجرا میکنید، لحظهای که از سیستم خارج میشوید یا سرور ریبوت میشود، متوقف میگردد. برنامهای که در یک سرویس systemd بستهبندی شده است به اجرا ادامه میدهد، زیرا مالکیت آن به جای shell شما، در اختیار خود سرور است.
systemd سیستم init در Ubuntu، Debian، Fedora و اکثر سرورهای مدرن لینوکسی است. این اولین فرآیندی است که شروع میشود و بر همه چیز نظارت میکند. همیشه اینطور نبوده است و مطالعه چگونگی جایگزینی اسکریپتهای init قدیمی توسط systemd پس از آنکه دانستید یک فایل unit چه کاری انجام میدهد، ارزشمند است. وقتی یک فایل سرویس مینویسید، برنامه خود را به آن ناظر میسپارید. این راهنما کوچکترین unit کاربردی، سه بخشی که هر unit دارد، نحوه فعالسازی و خواندن لاگهای آن، نحوه اجرای زمانبندیشده با استفاده از timer و نحوه محدود کردن دسترسیهای آن برای اجرا با کمترین سطح دسترسی ممکن را نشان میدهد.
کوچکترین سرویس کاربردی
یک فایل سرویس در مسیر /etc/systemd/system/ قرار میگیرد، با پسوند .service تمام میشود و تنها به چند خط نیاز دارد. برای برنامهای که در /usr/local/bin/myapp قرار دارد، یک فایل بسازید:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetاین یک unit کامل و عملیاتی است. ExecStart دستوری است که باید اجرا شود. WantedBy=multi-user.target به این معناست که سرویس پس از رسیدن سرور به حالت عادی multi-user اجرا شود؛ این همان چیزی است که باعث میشود سرویس در زمان بوت بالا بیاید. سایر موارد، صرفاً برای بهبود و تنظیمات دقیقتر هستند.
سه بخش اصلی و کاربرد هر یک
هر فایل unit به بخشهایی تقسیم میشود که در کروشه قرار دارند. یک سرویس از سه بخش استفاده میکند.
[Unit] سرویس و روابط آن را توصیف میکند. دو خطی که بیشترین استفاده را از آنها خواهید داشت:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription برچسبی انسانی است که در systemctl status مشاهده میکنید. After=network-online.target به systemd دستور میدهد تا زمانی که شبکه بالا نیامده است، برنامه شما را اجرا نکند؛ این موضوع برای هر برنامهای که پورتی را اشغال میکند یا اتصال خروجی برقرار میسازد، حیاتی است.
[Service] نحوه اجرای برنامه را مشخص میکند. اکثر تنظیمات شما در این بخش قرار میگیرند:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp برنامه را به جای root با یک حساب کاربری بدون امتیاز اجرا میکند که مهمترین خط برای امنیت است. در اینجا خط Type= وجود ندارد، بنابراین systemd به حالت پیشفرض simple بازمیگردد و فرض میکند که فرآیند ExecStart در پیشزمینه باقی میماند؛ برنامهای که خود را به پسزمینه منتقل میکند (fork)، به Type مناسب برای نحوه شروع آن نیاز دارد، در غیر این صورت unit وضعیت active را گزارش میدهد در حالی که daemon اصلی از قبل متوقف شده است. Restart=on-failure و RestartSec=5 بخش مخصوص به خود را در ادامه دارند، زیرا آنها دلیل اصلی نوشتن یک سرویس توسط اکثر کاربران هستند.
[Install] مشخص میکند که هنگام فعالسازی سرویس چه اتفاقی میافتد:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target همان چیزی است که هنگام اجرای systemctl enable، سرویس را به فرآیند بوت متصل میکند. بدون بخش [Install]، سرویس را میتوان بهصورت دستی اجرا کرد اما پس از reboot بهطور خودکار بالا نمیآید.
فعالسازی و نظارت بر سرویس
پس از نوشتن یا ویرایش هر فایل unit، باید systemd را reload کنید تا تغییرات اعمال شوند؛ سپس سرویس را در یک مرحله فعال و اجرا کنید:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload مرحلهای است که معمولاً فراموش میشود: systemd فایلهای unit را کش میکند، بنابراین تا زمانی که reload نکنید، ویرایشها اعمال نمیشوند. enable --now همزمان سرویس را برای اجرا در زمان بوت فعال کرده و بلافاصله آن را استارت میزند. وضعیت را بررسی کنید:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)خروجی Active: active (running) و enabled همان چیزی است که باید مشاهده کنید. برای خواندن خروجی برنامه، از journal بخواهید فقط لاگهای مربوط به همین unit را نمایش دهد:
sudo journalctl -u myapp.service -fسوئیچ -f خطوط جدید را به محض ورود دنبال میکند، مشابه عملکرد tail -f. هر چیزی که برنامه شما در standard output یا standard error بنویسد، بدون نیاز به تنظیمات لاگگیری اضافی، در اینجا ثبت میشود.
راهاندازی مجدد پس از شکست، دلیلی که اینجا هستید
مزیت اصلی یک سرویس این است که systemd برنامه شما را هنگام توقف غیرمنتظره، دوباره راهاندازی میکند. دو خط زیر این کار را انجام میدهند:
[Service]
Restart=on-failure
RestartSec=5عبارت Restart=on-failure برنامه را زمانی که با کد خروجی غیرصفر خارج شود یا به دلیل سیگنال کرش مانند SIGKILL یا SIGSEGV متوقف شود، دوباره راهاندازی میکند. خروج تمیز یا توقف توسط سیگنالهای SIGTERM، SIGINT، SIGHUP یا SIGPIPE باعث راهاندازی مجدد نمیشود. عبارت RestartSec=5 پنج ثانیه بین تلاشها وقفه ایجاد میکند تا برنامهای که بلافاصله کرش میکند، در یک حلقه تکرار سریع گیر نکند. این قابلیت را با کشتن پروسه و مشاهده بازگشت آن توسط systemd تأیید کنید. از SIGKILL استفاده کنید: سیگنال پیشفرض SIGTERM به عنوان یک توقف تمیز در نظر گرفته میشود، بنابراین on-failure سرویس را دوباره راهاندازی نخواهد کرد:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceدر عرض پنج ثانیه، وضعیت یک Main PID جدید و دوباره active (running) را نشان میدهد. این تمام ویژگی است و به همین دلیل است که استفاده از سرویس، بسیار بهتر از رها کردن برنامه در tmux یا screen است.
اجرای سرویس با کاربر بدون دسترسی ویژه و مقاومسازی آن
سرویسی که با کاربر root اجرا میشود، در صورت اکسپلویت شدن برنامه، میتواند هر کاری روی سرور شما انجام دهد. آن را با کاربر اختصاصی خود اجرا کنید و چند دستورالعمل به systemd بدهید تا آن را محدود کند. ابتدا یک حساب کاربری سیستمی بدون امکان ورود (login) و بدون دایرکتوری خانگی (home) ایجاد کنید:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappسپس User=myapp را تنظیم کرده و خطوط مقاومسازی را به [Service] اضافه کنید:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueهر خط، قابلیتی را که برنامه به آن نیاز ندارد حذف میکند. NoNewPrivileges=true از کسب امتیازات جدید توسط پردازش، حتی از طریق یک binary با قابلیت setuid، جلوگیری میکند. PrivateTmp=true یک /tmp خصوصی به آن میدهد که هیچ پردازش دیگری قادر به دیدن آن نیست. ProtectSystem=strict کل فایلسیستم را به حالت فقطخواندنی (read-only) در میآورد، مگر برای مسیرهای معدودی که با ReadWritePaths= مشخص میکنید. ProtectHome=true مسیر /home را بهطور کامل از دید آن پنهان میکند. این همان تفکر «حداقل دسترسی» است که در قرار دادن سرویس پشت فایروال به کار میرود: فقط آنچه را که نیاز دارد به آن بدهید. اگر راهنمای بستن شکاف فایروال IPv6 روی VPS را مطالعه کرده باشید، این بخشِ رویِ میزبان (on-host) از همان ایده است. برای سرویسی که در معرض اینترنت قرار دارد، این مقاومسازی را با Fail2ban در مقابل SSH و یک فایروال با سیاست پیشفرضِ deny ترکیب کنید.
بهجای تایپ دستی تمام این موارد و احتمال فراموشی یک دستورالعمل، یک unit کامل و مقاومسازیشده تولید کنید و آن را کپی نمایید:
تایمرها: جایگزین مدرن cron
یک تایمر systemd، سرویسی را طبق زمانبندی مشخص اجرا میکند و جایگزین مدرن برای cron job محسوب میشود. یک تایمر شامل دو فایل است: یک .service که کار را انجام میدهد و یک .timer که زمان اجرا را تعیین میکند. فرض کنید میخواهید هر روز ساعت 3 صبح یک نسخه پشتیبان تهیه کنید. سرویس، وظیفه را یکبار انجام داده و خارج میشود:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot به systemd میگوید که برنامه اجرا میشود، به پایان میرسد و تمام میشود، بهجای اینکه در حافظه باقی بماند. تایمر، زمانبندی آن را مدیریت میکند:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 به معنای ساعت 3 صبح هر روز است. هر عبارت تقویمی را با systemd-analyze calendar "*-*-* 03:00:00" تست کنید؛ این دستور تأیید میکند که عبارت بهدرستی تفسیر شده و زمانهای بعدی اجرا را نمایش میدهد. Persistent=true باعث میشود اگر سرور در ساعت 3 صبح خاموش بوده، کارِ ازدسترفته بلافاصله پس از روشن شدن سرور اجرا شود؛ کاری که cron قادر به انجام آن نیست. توجه داشته باشید که تایمر از طریق timers.target فعال میشود، نه multi-user.target. تایمر را فعال کنید، نه سرویس را:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers تمام تایمرها را به همراه زمان اجرای بعدی و قبلی آنها نشان میدهد، بنابراین میتوانید در یک نگاه ببینید که وظیفه شما چه زمانی اجرا میشود. مولد بالا هنگام فعالسازی حالت تایمر، .service و .timer جفتشده را برای شما میسازد. در مقایسه با یک خط cron، تایمر لاگهای واقعی در journal، همان دستورالعملهای امنیتی (hardening) سایر سرویسها و قابلیت جبران اجرای ازدسترفته که توسط Persistent=true فراهم میشود را در اختیار شما میگذارد. Cron همچنان برای کارهای ساده مناسب است؛ اما وقتی اهمیت کار بیشتر میشود، تایمر ابزار بهتری است.
FAQ
تفاوت بین یک systemd service و یک cron job چیست؟
یک سرویس، برنامهای را که طولانیمدت اجرا میشود زنده نگه میدارد: در زمان بوت شروع میشود، در صورت شکست دوباره راهاندازی میشود و لاگها را در journal ثبت میکند. یک cron job یک دستور کوتاه را طبق زمانبندی اجرا کرده و سپس خارج میشود. زمانی که به زمانبندی نیاز دارید اما در عین حال میخواهید از لاگهای journal، قابلیتهای امنیتی (hardening) و اجرای مجدد وظایف عقبمانده بهرهمند شوید، از یک systemd timer استفاده کنید. این ابزار یک زمانبندی .timer را با یک سرویس oneshot جفت میکند و برای اکثر وظایف سرور جایگزین cron میشود.
فایل systemd service خود را کجا قرار دهم؟
unitهای خود را در /etc/systemd/system/ قرار دهید، با نامی که به .service ختم شود. این دایرکتوری مخصوص unitهایی است که مدیر سیستم اضافه میکند و نسبت به unitهایی که توسط بستهها در /lib/systemd/system/ ارائه میشوند، اولویت دارد. پس از ایجاد یا ویرایش یک فایل در آنجا، دستور sudo systemctl daemon-reload را اجرا کنید تا systemd تغییرات را اعمال کند.
چگونه کاری کنم که سرویس در صورت کرش کردن دوباره راهاندازی شود؟
گزینههای Restart=on-failure و RestartSec=5 را به بخش [Service] اضافه کنید، سپس sudo systemctl daemon-reload را اجرا کرده و سرویس را restart کنید. systemd زمانی که برنامه با کدی غیر از صفر خارج شود یا به دلیل سیگنال کرش متوقف گردد، آن را دوباره راهاندازی میکند و بین هر تلاش 5 ثانیه صبر میکند. این مورد را با sudo systemctl kill -s SIGKILL myapp.service تست کنید؛ سیگنال SIGTERM که سیگنال پیشفرض است، به عنوان یک توقف تمیز در نظر گرفته میشود و باعث فعال شدن on-failure نمیشود. سپس مشاهده کنید که systemctl status ظرف چند ثانیه یک PID جدید را نشان میدهد.
چگونه یک systemd service را با کاربری غیر از root اجرا کنم؟
یک حساب کاربری سیستمی با sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp ایجاد کنید، سپس User=myapp را به بخش [Service] اضافه کنید. گزینههای NoNewPrivileges=true، PrivateTmp=true و ProtectSystem=strict را اضافه کنید تا پردازش با حداقل دسترسیهای مورد نیاز اجرا شود. اجرای سرویس با یک کاربر بدون امتیاز (unprivileged)، مهمترین تغییری است که میتوانید برای افزایش امنیت سرویس خود انجام دهید.
چرا سرویس من در شروع کار شکست میخورد؟
برای مشاهده خلاصه وضعیت، systemctl status myapp.service و برای مشاهده خروجی کامل، journalctl -u myapp.service را اجرا کنید. رایجترین دلایل عبارتند از: مسیر اشتباه در ExecStart، نبود یک WorkingDirectory، خطای مجوز به دلیل اینکه User= نمیتواند فایلی را بخواند، یا فراموش کردن sudo systemctl daemon-reload پس از ویرایش فایل. journal پیام خطای خودِ برنامه را نشان میدهد که معمولاً مستقیماً به مشکل اشاره میکند.