نحوه ساخت و اجرای سرویس systemd در لینوکس
آموزش کامل ساخت فایل .service برای اجرای خودکار برنامهها در VPS، تنظیم قابلیت restart در صورت crash و مدیریت لاگها با استفاده از journalctl در لینوکس.
سرویس systemd چیست و چرا به آن نیاز دارید
یک سرویس systemd یک فایل متنی کوچک است که به سرور شما میگوید چگونه یک برنامه را اجرا کند: آن را هنگام بالا آمدن سیستم (boot) اجرا کند، در صورت کرش کردن آن را دوباره راهاندازی کند و خروجی آن را به log سیستم ارسال کند. وظیفه آن فقط همین است. برنامهای که به صورت دستی در یک session SSH اجرا میکنید، بلافاصله پس از log out کردن یا ریبوت شدن سرور، متوقف میشود. اما برنامهای که در قالب یک سرویس systemd قرار گرفته باشد، به اجرا ادامه میدهد؛ زیرا سرور به جای shell شما، مالک آن برنامه است.
systemd سیستم init در Ubuntu، Debian، Fedora و اکثر سرورهای مدرن Linux است. این سیستم اولین فرآیندی است که اجرا میشود و بر تمام فرآیندهای دیگر نظارت میکند. وقتی یک فایل سرویس مینویسید، در واقع برنامه خود را به این ناظر میسپارید. این راهنما کوچکترین واحد قابل اجرا، سه بخش موجود در هر unit، نحوه فعالسازی و خواندن logهای آن، نحوه اجرای برنامه با استفاده از یک 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این یک واحد کامل و عملیاتی است. ExecStart دستور اجرا است. WantedBy=multi-user.target به این معناست که سرویس پس از رسیدن سرور به حالت عادی multi-user اجرا شود، که باعث بالا آمدن آن در هنگام boot میشود. سایر موارد صرفاً برای بهینهسازی هستند.
سه بخش و کاربرد هر یک
هر فایل unit به بخشهایی تقسیم میشود که در داخل براکت قرار دارند. یک service از سه بخش استفاده میکند.
[Unit] ویژگیهای service و روابط آن را توصیف میکند. دو خطی که بیشترین استفاده را خواهند داشت:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription برچسبی است که انسانها در systemctl status مشاهده میکنند. After=network-online.target به systemd دستور میدهد که برنامه شما را تا زمان بالا آمدن شبکه اجرا نکند؛ این مورد برای هر برنامهای که یک port را اشغال میکند یا اتصال خروجی برقرار میکند، حیاتی است.
[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، با یک حساب کاربری بدون امتیاز (unprivileged) اجرا میکند که مهمترین خط برای امنیت است. Restart=on-failure و RestartSec=5 بخش اختصاصی خود را در ادامه دارند، زیرا دلیل اصلی اکثر افراد برای نوشتن یک service هستند.
[Install] اتفاقی است که هنگام فعالسازی (enable) service رخ میدهد:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target سرویس را هنگام اجرای systemctl enable به فرآیند boot متصل میکند. بدون بخش [Install]، یک service میتواند به صورت دستی اجرا شود اما پس از reboot به طور خودکار بالا نمیآید.
فعالسازی و مشاهده عملکرد
پس از نوشتن یا ویرایش هر فایل unit، دستور reload systemd را اجرا کنید تا تغییرات اعمال شوند. سپس سرویس را در یک مرحله enable و start کنید:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.serviceمرحله daemon-reload مرحلهای است که معمولاً فراموش میشود: systemd فایلهای unit را کش میکند، بنابراین تا زمانی که دستور reload را اجرا نکنید، تغییرات اعمال نمیشوند. دستور enable --now سرویس را برای اجرا در هنگام بوت فعال کرده و بلافاصله آن را start میکند. وضعیت را بررسی کنید:
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 بنویسد، بدون نیاز به هیچ تنظیمات لاگگیری از سمت شما، در اینجا ثبت میشود.
Restart on failure, the reason you are here
مزیت اصلی یک service این است که systemd هنگام از کار افتادن، برنامه شما را مجدداً اجرا میکند. دو خط زیر این کار را انجام میدهند:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure برنامه را زمانی که با کد خروج غیر صفر بسته شود یا بر اثر سیگنالهای کرش مانند SIGKILL یا SIGSEGV از کار بیفتد، مجدداً اجرا میکند. خروج سالم، یا توقف توسط SIGTERM، SIGINT، SIGHUP یا SIGPIPE، باعث اجرای مجدد نمیشود. RestartSec=5 مدت 5 ثانیه بین تلاشها صبر میکند تا برنامهای که بلافاصله کرش میکند، در یک حلقه تکرار بیپایان قرار نگیرد. این ویژگی را با کشتن (kill) فرآیند و مشاهده بازگرداندن آن توسط systemd تایید کنید. از SIGKILL استفاده کنید: از آنجایی که SIGTERM پیشفرض به عنوان توقف سالم محسوب میشود، on-failure سرویس را مجدداً اجرا نمیکند:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceظرف 5 ثانیه، وضعیت (status) یک Main PID و active (running) جدید را نشان میدهد. این تمام ویژگی مذکور است و دلیل برتری یک service نسبت به اجرای یک برنامه در tmux یا screen است.
آن را به عنوان یک کاربر بدون امتیاز اجرا و آن را مستحکم کنید
اگر برنامهای مورد سوءاستفاده قرار گیرد، سرویسی که با سطح دسترسی root اجرا میشود میتواند هر کاری با سرور شما انجام دهد. آن را با کاربر مخصوص به خود اجرا کنید و دستورات محدودی را به systemd اضافه کنید تا آن را در چارچوب نگه دارد. ابتدا یک حساب کاربری سیستمی بدون قابلیت login و بدون home ایجاد کنید:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappسپس User=myapp را تنظیم کرده و خطوط مستحکمسازی (hardening) را به [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 را کاملاً از دید آن پنهان میکند. این دقیقاً همان مفهوم «کمترین امتیاز» (least-privilege) است که در قرار دادن یک سرویس پشت دیوار آتش (firewall) استفاده میشود: فقط آنچه را که نیاز دارد به آن بدهید. اگر راهنمای بستن شکاف دیوار آتش IPv6 در یک VPS را خواندهاید، این بخش مربوط به روی خودِ هاست از همان ایده است. برای سرویسی که با اینترنت در ارتباط است، این مستحکمسازی را با Fail2ban در مقابل SSH و یک دیوار آتش با سیاست default-deny ترکیب کنید.
به جای تایپ دستی تمام این موارد که ممکن است باعث اشتباه در دستورات شود، یک unit کامل و مستحکمسازی شده تولید و آن را کپی کنید:
Timers: the modern cron
یک systemd timer یک service را طبق یک برنامه زمانی اجرا میکند و جایگزین مدرن برای cron job است. یک timer شامل دو فایل است: یک .service که کار را انجام میدهد و یک .timer که زمان اجرا را تعیین میکند. فرض کنید میخواهید هر روز ساعت 3am یک backup تهیه کنید. service وظیفه را یک بار انجام داده و خارج میشود:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot به systemd اطلاع میدهد که برنامه اجرا، تکمیل و پایان مییابد، به جای اینکه در حافظه باقی بماند. timer زمانبندی آن را انجام میدهد:
# /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 به معنای ساعت 3am در هر روز است. هر عبارت تقویمی را با systemd-analyze calendar "*-*-* 03:00:00" تست کنید؛ این دستور تایید میکند که عبارت به درستی تجزیه شده و زمانهای اجرای بعدی را چاپ میکند. اگر سرور در ساعت 3am خاموش بوده باشد، Persistent=true وظایف از دست رفته را بلافاصله پس از بالا آمدن سرور اجرا میکند، کاری که cron قادر به انجام آن نیست. دقت کنید که یک timer از طریق timers.target فعال میشود، نه multi-user.target. timer را فعال کنید، نه service را:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers تمام timerها را به همراه زمان اجرای بعدی و آخرین اجرای آنها نشان میدهد، بنابراین میتوانید در یک نگاه ببینید که وظیفه شما چه زمانی اجرا میشود. generator بالا، زمانی که timer mode را روشن میکنید، جفتهای .service و .timer را برای شما میسازد. در مقایسه با یک خط cron، یک timer لاگهای واقعی در journal، همان دستورات امنیتی (hardening) موجود در هر service و قابلیت جبران اجراهای از دست رفته را که توسط Persistent=true فراهم میشود، در اختیار شما قرار میدهد. cron برای کارهای ساده همچنان مناسب است؛ اما زمانی که اهمیت وظیفه بالا میرود، timer ابزار بهتری است.
FAQ
تفاوت بین یک systemd service و یک cron job چیست؟
یک service یک برنامه در حال اجرا را زنده نگه میدارد: در هنگام boot اجرا میشود، در صورت بروز خطا restart میشود و لاگها را در journal ثبت میکند. یک cron job یک دستور کوتاه را در یک زمانبندی مشخص اجرا کرده و سپس خارج میشود. اگر به زمانبندی نیاز دارید اما همزمان لاگهای journal، قابلیت hardening و قابلیت جبران اجراهای انجام نشده (catch-up) را نیز میخواهید، از یک systemd timer استفاده کنید؛ این ابزار یک .timer را با یک oneshot جفت میکند و برای اکثر وظایف سرور، جایگزین cron میشود.
فایل systemd service من را کجا قرار دهم؟
unitهای اختصاصی خود را در /etc/systemd/system/ قرار دهید و نام آنها را به .service ختم کنید. آن دایرکتوری برای unitهایی است که administrator اضافه میکند و نسبت به unitهای موجود در /lib/systemd/system/ اولویت دارد. پس از ایجاد یا ویرایش یک فایل در آنجا، دستور sudo systemctl daemon-reload را اجرا کنید تا systemd تغییرات را شناسایی کند.
چگونه باعث بازگشت (restart) خودکار یک service در صورت کرش کردن آن شوم؟
بخشهای Restart=on-failure و RestartSec=5 را به بخش [Service] اضافه کنید، سپس sudo systemctl daemon-reload را اجرا کرده و service را restart کنید. systemd برنامه را زمانی که با یک کد غیر صفر خارج شود یا بر اثر یک crash signal از کار بیفتد، مجدداً اجرا میکند و بین تلاشها 5 ثانیه صبر میکند. آن را با sudo systemctl kill -s SIGKILL myapp.service تست کنید — سیگنال SIGTERM که سیگنال پیشفرض است، به عنوان یک توقف تمیز (clean stop) محسوب شده و باعث تحریک 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 user)، مهمترین تغییر واحدی است که میتوانید برای امنیت یک service انجام دهید.
چرا سرویس من در اجرا شکست خورد؟
برای مشاهده خلاصه از systemctl status myapp.service و برای مشاهده خروجی کامل از journalctl -u myapp.service استفاده کنید. رایجترین دلایل عبارتند از: مسیر اشتباه در ExecStart، نبود WorkingDirectory، خطای دسترسی به دلیل عدم توانایی User= در خواندن یک فایل، یا فراموش کردن sudo systemctl daemon-reload پس از ویرایش. journal پیام خطای خودِ برنامه را نشان میدهد که معمولاً مستقیماً نام مشکل را ذکر میکند.