systemd service Type: simple, forking, notify का सही चुनाव
यदि आपकी unit active है लेकिन daemon बंद हो चुका है तो Type= सेटिंग गलत हो सकती है। simple, forking, notify और oneshot के बीच अंतर समझें और सही main PID की पहचान करना सीखें।
systemd किसी unit को active क्यों दिखाता है जबकि process मर चुकी होती है
एक systemd service unit तब तक active रहती है जब तक वह process जीवित है जिसे systemd मुख्य process मानता है, और Type=, [Service] section में यह तय करता है कि वह process कौन सी है। यदि आप गलत value चुनते हैं, तो systemd किसी shell wrapper या अल्पकालिक parent process की निगरानी करता रहता है, जबकि वह daemon जिसे आप चलाना चाहते हैं, उसी unit के भीतर समाप्त हो जाता है। unit आपको उस process के बारे में सही जानकारी दे रही है जिसकी निगरानी के लिए उसे कहा गया है।
restart policy बदलने से यहाँ कोई मदद नहीं मिलेगी। Restart= तब काम करता है जब मुख्य process exit होती है, इसलिए Restart=always कभी trigger नहीं होता जब तक main PID (process identifier) किसी ऐसी चीज़ से संबंधित है जो अभी भी चल रही है। पहले Type= को ठीक करें। मुख्य process के वास्तव में exit होने के बाद systemd क्या करता है, यह एक अलग निर्णय है, जिसे Restart= और RestartSec= के लिए गाइड में समझाया गया है।
Type= वास्तव में क्या निर्धारित करता है
प्रत्येक Type= मान एक साथ दो सवालों के जवाब देता है। systemd कब इस unit को 'started' मान सकता है, और मुख्य process कौन सी है।
पहला जवाब ordering को नियंत्रित करता है। जो unit After= में आपकी unit का नाम लेती है, वह तब तक प्रतीक्षा करती है जब तक systemd आपकी unit को 'started' घोषित न कर दे। यदि कोई Type= बहुत जल्दी "started" रिपोर्ट कर देता है, तो dependent units आपकी service के तैयार होने से पहले ही चल सकती हैं।
दूसरा जवाब supervision को नियंत्रित करता है। systemd एक unit द्वारा शुरू की गई हर process को cgroup (control group) में डालता है। यह kernel का एक feature है जो processes को एक समूह में रखता है ताकि उन्हें सीमित किया जा सके और एक साथ बंद किया जा सके। cgroup के जरिए ही systemctl stop सफाई करता है: KillMode= का default मान control-group है, इसलिए unit को stop करने पर उसके अंदर की हर process को signal भेजा जाता है। मुख्य PID का दायरा सीमित होता है। यह वह एकमात्र process है जिसके exit होने पर unit समाप्त मानी जाती है, और जिसका exit status unit का परिणाम बनता है। cgroup को मुख्य PID की तरह पढ़ना ही भ्रम का मुख्य कारण है।
Type=simple रिपोर्ट तब शुरू होती है जब बाइनरी रन होती है
जब ExecStart= सेट हो और न तो Type= मौजूद हो और न ही BusName=, तो Type=simple डिफ़ॉल्ट होता है। systemd प्रक्रिया बनाता है, यूनिट को तुरंत शुरू हुआ मानता है, और उस प्रक्रिया को मुख्य PID के रूप में देखता है। सर्विस बाइनरी के निष्पादित होने से पहले ही, फॉलो-अप यूनिट्स तुरंत शुरू हो जाती हैं।
यह अंतिम विवरण एक सामान्य आश्चर्य का कारण स्पष्ट करता है। ExecStart= पथ में टाइपिंग की गलती होने पर भी एक स्टार्ट जॉब सफल हो जाती है, और विफलता तब आती है जब निष्पादन विफल हो जाता है। systemd उस स्थिति को exit code 203 के साथ रिकॉर्ड करता है, जिसे उसकी अपनी तालिका में EXEC कहा गया है और इसे सर्विस बाइनरी को निष्पादित करने में विफलता के रूप में परिभाषित किया गया है। इसलिए, बिना किसी त्रुटि के systemctl start का वापस आना यह साबित नहीं करता है कि आपकी बाइनरी मौजूद है।
ऐसे प्रोग्राम के लिए simple का उपयोग करें जो फोरग्राउंड में रहता है और खुद को कभी बैकग्राउंड में नहीं ले जाता है। यह अधिकांश आधुनिक डेमन्स और आपके द्वारा लिखे गए लगभग किसी भी प्रोग्राम को कवर करता है।
Type=exec यह सुनिश्चित करता है कि प्रोग्राम वास्तव में शुरू हो गया है
Type=exec, simple का ही एक उन्नत संस्करण है जिसमें एक अतिरिक्त चरण शामिल है। systemd यूनिट को तभी शुरू हुआ मानता है जब fork और binary का निष्पादन (execution) दोनों सफल हो जाते हैं। यदि binary मौजूद नहीं है या कोई User= हल नहीं हो पा रहा है, तो यह start job को ही विफल कर देता है। इसके बजाय कि यह पहले सफलता की रिपोर्ट दे और बाद में चुपचाप विफल हो जाए।
Type=exec को systemd 240 में पेश किया गया था, इसलिए वर्तमान की सभी सर्वर distributions में यह उपलब्ध है। अगस्त 2026 तक, Ubuntu 24.04 में systemd 255 और Debian 13 में systemd 257 शामिल है। अपने सिस्टम का संस्करण systemctl --version के साथ जाँचें।
इसकी कीमत शुरू होने के समय एक अतिरिक्त सिंक्रोनाइज़ेशन चरण है। इसका लाभ systemctl start से मिलने वाला सटीक exit status है। foreground प्रोग्राम के लिए, simple के बजाय exec का उपयोग करना बेहतर है।
Type=forking, और मुख्य PID का खो जाना
Type=forking systemd को यह बताता है कि ExecStart= में दिया गया process एक child process बनाएगा और फिर जानबूझकर exit हो जाएगा। systemd उस पहले process के exit होने का इंतज़ार करता है और उसके बाद ही unit को started मानता है। पीछे बचा हुआ child process ही daemon होता है।
कठिनाई पहचान (identity) की है। जिस process को systemd ने launch किया था, वह अब मौजूद नहीं है, इसलिए systemd को यह पता लगाना पड़ता है कि बचा हुआ कौन सा process मुख्य है। PIDFile= को उस file पर set करें जिसे daemon लिखता है, जो आमतौर पर /run के अंतर्गत एक path होता है, और systemd उससे PID पढ़ लेता है। systemd यह भी जाँचता है कि उस file में मौजूद PID उसी service से संबंधित process का है या नहीं, ताकि किसी unrelated process के पुराने file नाम को स्वीकार करने के बजाय उसे reject किया जा सके।
PIDFile= के बिना, GuessMainPID= लागू होता है, और यह default रूप से yes पर होता है। यह अनुमान तभी विश्वसनीय होता है जब service एक ही process में स्थिर हो जाए। manual में इसकी सीमा स्पष्ट रूप से दी गई है: यदि daemon एक से अधिक processes से बना है, तो अनुमान गलत हो सकता है, और failure detection काम करना बंद कर देता है। एक unit का मुख्य PID 0 भी हो सकता है, जिसका अर्थ है कि systemd के पास supervise करने के लिए कुछ भी नहीं है।
fork करने वाले अधिकांश daemons में एक switch होता है जो उन्हें foreground में रखता है। Type=exec के साथ उस switch का उपयोग करें और PIDFile= line को हटा दें। कम moving parts का मतलब है PID खोने की कम संभावना।
Type=oneshot ऐसे कार्यों के लिए जो समाप्त हो जाते हैं
Type=oneshot यह अपेक्षा करता है कि प्रक्रिया चले और समाप्त हो जाए। systemd यूनिट को तभी started मानता है जब वह समाप्त हो जाती है, जो oneshot को किसी भी ऐसी चीज़ के लिए सही स्वरूप बनाता है जिसका अन्य यूनिट्स को इंतज़ार करना चाहिए। जब कोई यूनिट न तो Type= और न ही ExecStart= निर्दिष्ट करती है, तो यह निहित डिफ़ॉल्ट भी है।
दो व्यवहार विशेष रूप से oneshot के लिए हैं। यह एकमात्र प्रकार है जो एक से अधिक ExecStart= लाइनों को स्वीकार करता है, और वे लाइनें क्रम में चलती हैं। इसका start timeout भी डिफ़ॉल्ट रूप से अक्षम होता है, इसलिए यदि कोई oneshot हैंग हो जाता है, तो वह तब तक इंतज़ार करेगा जब तक आप स्वयं TimeoutStartSec= सेट नहीं करते।
प्रक्रिया समाप्त होने के बाद, यूनिट inactive स्थिति में लौट आती है। RemainAfterExit=yes इसे बिना किसी चल रही प्रक्रिया के active बनाए रखता है। यह इस पृष्ठ के शीर्ष पर वर्णित लक्षण का जानबूझकर किया गया संस्करण है, और यह तब सही होता है जब यूनिट का कार्य कुछ चलाने के बजाय स्थिति (state) को पीछे छोड़ना हो: जैसे firewall ruleset लोड करना, या container stack को चालू करना। यह reboot के बाद वापस आने वाले Docker Compose stack के पीछे का पैटर्न है, जहाँ यूनिट compose कमांड चलाती है, समाप्त हो जाती है, और active बनी रहती है क्योंकि इसके द्वारा शुरू किए गए containers इसके बाद भी चलते रहते हैं। एक oneshot यूनिट वह भी है जिसे एक schedule ट्रिगर करता है, जो cron के बजाय systemd timer पर जॉब चलाने का दूसरा भाग है।
Type=notify का उपयोग करके service यह बता सकती है कि वह कब तैयार है
Type=notify निर्णय लेने की जिम्मेदारी service पर डाल देता है। systemd तब तक start job को खुला रखता है जब तक कि process एक Unix socket के माध्यम से READY=1 नहीं भेज देती, जिसका path उसे NOTIFY_SOCKET environment variable में प्राप्त होता है। इसके लिए C interface sd_notify(3) है, और कई servers पहले से ही इसका समर्थन करते हैं।
यह "क्या यह शुरू हो गया है" प्रश्न का सटीक उत्तर है। simple और exec तब 'started' रिपोर्ट करते हैं जब service ने अपनी configuration पढ़ी भी नहीं होती या अपना listening socket खोला भी नहीं होता है, इसलिए कोई dependent unit बहुत जल्दी शुरू हो सकती है और अपना पहला connection विफल कर सकती है। notify उस क्षण 'started' रिपोर्ट करता है जब service स्वयं यह बताती है कि वह तैयार है।
systemd केवल main process से ही वह संदेश स्वीकार करता है, जिसका अर्थ NotifyAccess=main है, और Type=notify इसे सूचित करता है। यदि संदेश किसी child या helper process से आता है, तो NotifyAccess=all सेट करें। एक shell script systemd-notify --ready को call कर सकती है, लेकिन वह एक अलग अल्पकालिक process के रूप में चलती है, इसलिए उसे NotifyAccess=all की आवश्यकता होती है और systemd उस संदेश को attribute करने में सक्षम नहीं हो सकता है जिसका sender पहले ही exit हो चुका है। जो service स्वयं protocol का पालन करती है, वह अधिक विश्वसनीय होती है।
दो संबंधित settings जानने योग्य हैं। Type=notify-reload, जो systemd 253 से उपलब्ध है, उसी handshake को reloads तक बढ़ा देता है, इसलिए systemctl reload तब return होता है जब service यह रिपोर्ट करती है कि reload पूरा हो गया है, न कि तब जब signal भेजा गया था। WatchdogSec= एक notifying service को एक निश्चित अंतराल पर keep-alive संदेश भेजने के लिए कहता है, और systemd समय सीमा चूकने को विफलता मानता है।
Type=dbus और Type=idle
Type=dbus तब तक प्रतीक्षा करता है जब तक कि service D-Bus पर कोई नाम register नहीं कर लेती। D-Bus वह message bus है जिसका उपयोग system और desktop services एक-दूसरे से संवाद करने के लिए करती हैं। इसके लिए BusName= की आवश्यकता होती है, और जैसे ही BusName= सेट किया जाता है, यह default बन जाता है। इसका उपयोग केवल उसी service के लिए करें जो वास्तव में bus name register करती है।
Type=idle, simple की तरह व्यवहार करता है, लेकिन यह program को चलाने में तब तक देरी करता है जब तक कि queued jobs dispatch न हो जाएं। इसमें पांच सेकंड की समय सीमा (cap) होती है। यह इसलिए मौजूद है ताकि boot के समय console output, status messages के साथ न मिल जाए। यह एक ordering tool नहीं है, और इसे सामान्य service पर उपयोग नहीं किया जाना चाहिए।
Wrapper script क्यों systemd को गलत PID पर छोड़ देता है
यहाँ वह संरचना दी गई है जो मूल समस्या उत्पन्न करती है।
[Service]
Type=simple
ExecStart=/opt/app/run.sh#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101systemd शेल को मुख्य PID के रूप में रिकॉर्ड करता है। जब exporter foreground में चलता है, तो शेल जीवित रहता है। यदि server बंद हो जाता है, तो शेल को इसका पता नहीं चलता है, इसलिए मुख्य PID अभी भी जीवित रहता है, यूनिट अभी भी active स्थिति में रहती है, और Restart= के पास कार्रवाई करने के लिए कुछ नहीं होता है। दोनों प्रक्रियाएं पूरे समय यूनिट के cgroup में रहती हैं, इसलिए systemctl stop अभी भी सही ढंग से cleanup करता है। यहाँ supervision खराब हुआ है, cleanup नहीं।
इसका समाधान इस बात पर निर्भर करता है कि यूनिट में वास्तव में कितनी लंबी चलने वाली प्रक्रियाएं हैं।
यदि केवल एक प्रक्रिया है, तो शेल को उससे बदल दें।
#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yamlexec शेल को नामित प्रोग्राम से बदल देता है और वही PID रखता है, इसलिए systemd द्वारा रिकॉर्ड किया गया PID अब daemon का होता है। इससे भी बेहतर यह है कि wrapper को हटा दें। Environment= और EnvironmentFile= variables को ले जाते हैं, और ExecStartPre= setup चरण को संभालता है, इसलिए systemd सीधे daemon को लॉन्च कर सकता है और निर्माण के आधार पर उसका PID जान सकता है।
यदि दो प्रक्रियाएं हैं, तो कोई भी एक PID यूनिट का प्रतिनिधित्व नहीं करता है। उन्हें दो यूनिट्स में विभाजित करें और After= तथा Wants= के साथ उनका क्रम निर्धारित करें। प्रति प्रक्रिया एक यूनिट वह व्यवस्था है जिसे systemd अच्छी तरह से supervise करता है, और यही एकमात्र तरीका है जिससे प्रत्येक प्रक्रिया को अपना स्वयं का restart व्यवहार मिलता है।
ExitType=cgroup में क्या बदलाव आए हैं
ExitType= को systemd 250 में जोड़ा गया था। इसका डिफ़ॉल्ट मान main है: जब मुख्य प्रक्रिया (main process) समाप्त हो जाती है, तो यूनिट को बंद माना जाता है। ExitType=cgroup के साथ, यूनिट तब तक चलती हुई मानी जाती है जब तक कि उसके cgroup में कोई भी प्रक्रिया जीवित रहती है।
[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcherयह एक विशिष्ट समस्या का समाधान करता है। ExitType=main के तहत, एक लॉन्चर जो वास्तविक कार्य शुरू करने के बाद बाहर निकल जाता है, वह systemd को यह सोचने पर मजबूर कर देता है कि यूनिट बंद हो गई है और वह बची हुई प्रक्रियाओं को समाप्त कर देता है। ExitType=cgroup के साथ, यूनिट पूरे समूह का अनुसरण करती है।
यह स्पष्ट रहें कि यह क्या हल नहीं करता है। ExitType=cgroup यूनिट को तब तक सक्रिय रखता है जब तक कम से कम एक प्रक्रिया जीवित रहती है, इसलिए दो डेमन्स (daemons) वाली यूनिट उनमें से एक के मरने के बाद भी सक्रिय रहती है। यह लॉन्चर वाली समस्या को ठीक करता है। यह एक यूनिट को कई स्वतंत्र प्रक्रियाओं के पर्यवेक्षक (supervisor) में नहीं बदलता है। ExitType= को Type=oneshot के साथ भी नहीं जोड़ा जा सकता है।
cgroup वह स्थान है जहाँ संसाधन लेखांकन (resource accounting) भी होता है, इसलिए MemoryMax= और CPUQuota= जैसी सीमाएं यूनिट द्वारा उत्पन्न हर प्रक्रिया पर लागू होती हैं, चाहे Type= मुख्य PID के बारे में कुछ भी कहे। इसका वह पहलू systemd के साथ सर्विस की मेमोरी और CPU को सीमित करना में दिया गया है।
systemd वास्तव में किस process को monitor कर रहा है, यह कैसे पता करें
Debugging के दौरान अपने unit पर क्रमानुसार कार्य करें। पहले यह पढ़ें कि systemd ने क्या load किया है, फिर यह देखें कि वह क्या track कर रहा है, और अंत में इसकी तुलना process table से करें।
systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.servicesystemctl cat unit file को उन सभी drop-in files के साथ print करता है जो उस पर लागू होती हैं, ताकि आप वह पढ़ सकें जो systemd ने load किया है, न कि वह file जिसे आपने edit किया था। systemctl show प्रभावी values को print करता है, जिसमें वे defaults भी शामिल हैं जिन्हें आपने कभी नहीं लिखा था। आगे बढ़ने से पहले MainPID की value नोट कर लें।
systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"systemd-cgls unit के cgroup में मौजूद हर process को list करता है। ps line उस एकमात्र process का वर्णन करती है जिसे systemd supervise करता है। इन दोनों को एक साथ पढ़ें। MainPID का 0 होना यह दर्शाता है कि systemd के पास watch करने के लिए कोई process नहीं है। यदि cgroup में आपका daemon मौजूद है और MainPID एक shell को resolve करता है, तो यह ऊपर बताया गया wrapper case है। यदि cgroup में आपकी अपेक्षा से अधिक processes हैं, तो इसका मतलब है कि कोई launcher या forking daemon शामिल है।
systemctl status app.service
journalctl -u app.service -bsystemctl status state line और cgroup tree को एक साथ print करता है, इसलिए यह अक्सर दोनों सवालों के जवाब एक साथ दे देता है। -b के साथ current boot तक सीमित journalctl -u उन start और stop events को दिखाता है जिन्हें systemd ने unit के लिए record किया है, साथ ही उन exit codes को भी जिन्हें उसने देखा। यदि daemon journal के बजाय अपनी log file में लिखता है, तो उस file को भी पढ़ें, क्योंकि systemd केवल उसी को record कर सकता है जो उस तक पहुँचा है।
जब आप Type= बदलते हैं, तो reload और restart करें।
systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.servicesystemd-analyze verify file को parse करता है और उन settings की रिपोर्ट करता है जिन्हें वह स्वीकार नहीं कर सकता। daemon-reload systemd को disk से unit files को फिर से read करने के लिए कहता है। बदला हुआ Type= पहले से चल रहे unit पर लागू नहीं होता है, इसलिए restart अनिवार्य है, वैकल्पिक नहीं।
फिर बदलाव का परीक्षण करें। systemd-cgls से उस process का PID लें जिसकी आपको वास्तव में परवाह है और उसे kill कर दें। उसके तुरंत बाद systemctl is-active app.service चलाएँ। यदि Type= सही है, तो unit active state छोड़ देगा। यदि यह active रहता है, तो systemd अभी भी किसी और चीज़ को watch कर रहा है।
आपको systemd service का कौन सा Type= उपयोग करना चाहिए
- ऐसा प्रोग्राम जो foreground में रहता है:
Type=exec। - ऐसा प्रोग्राम जो readiness notification को सपोर्ट करता है:
Type=notify, और यदि यह reloads की भी पुष्टि करता है तोnotify-reload। - ऐसा daemon जो background में जाने पर जोर देता है:
Type=forkingके साथPIDFile=, याType=execके साथ इसका foreground switch। - ऐसी script जो काम पूरा करके exit हो जाती है:
Type=oneshot, और यदि उद्देश्य state को पीछे छोड़ना हो तो साथ मेंRemainAfterExit=yes। - ऐसा launcher जो exit हो जाता है जबकि उसके children चलते रहते हैं:
Type=simpleके साथExitType=cgroup।
यदि आप सुनिश्चित नहीं हैं कि किसी third party daemon को किसकी आवश्यकता है, तो पहले उसकी packaged unit file पढ़ें। distribution द्वारा प्रदान की गई unit पर systemctl cat चलाने से वह Type= दिखता है जिसे upstream ने चुना है, और उस विकल्प को आपसे कहीं अधिक लोगों द्वारा परखा गया है।
FAQ
जब process बंद हो जाता है तो मेरा systemd unit active क्यों रहता है?
क्योंकि वह process जिसे systemd मुख्य (main) मानता है, अभी भी जीवित है। systemd हर service के लिए केवल एक PID की निगरानी करता है, जिसे Type= के अनुसार चुना जाता है, न कि unit के cgroup में मौजूद हर process की। Type=simple के साथ शुरू की गई wrapper script इसका सामान्य कारण है: shell ही मुख्य PID होता है, इसलिए जब shell द्वारा background में शुरू किया गया daemon exit होता है, तब भी unit active ही रहता है। systemctl show -p MainPID app.service चलाएँ, फिर systemd-cgls --unit=app.service के साथ unit का cgroup list करें, और दोनों की तुलना करें।
Type=simple और Type=exec के बीच क्या अंतर है?
Type=simple unit को तब शुरू हुआ मानता है जैसे ही systemd ने process बना लिया हो, binary के execute होने से पहले। इसलिए ExecStart= में गलत path होने पर भी start job सफल दिखाई देता है और उसके बाद failure आता है। Type=exec तब तक प्रतीक्षा करता है जब तक execution सफल न हो जाए, ताकि failure की सूचना start job द्वारा ही मिल जाए। दोनों ही एक ही process को मुख्य PID मानते हैं। Type=exec के लिए systemd 240 या उससे नया version चाहिए।
क्या मुझे Type=forking के साथ अभी भी PIDFile= की आवश्यकता है?
हाँ, जब भी daemon इसे लिखता है। इसके बिना, systemd GuessMainPID= पर निर्भर हो जाता है, जो केवल एक अनुमान है और केवल उन services के लिए विश्वसनीय है जो एक ही process में स्थिर हो जाती हैं। जब यह अनुमान गलत या असंभव होता है, तो उस unit के लिए failure detection और automatic restarting काम करना बंद कर देते हैं। PIDFile= को उस सटीक path पर point करें जिसे daemon लिखता है, जो सामान्यतः /run के अंतर्गत होता है।
मुझे RemainAfterExit=yes का उपयोग कब करना चाहिए?
जब unit का उद्देश्य system state को बदलना हो, न कि किसी process को चालू रखना। एक Type=oneshot unit जो firewall rules load करती है या container stack शुरू करती है, अपना काम पूरा होते ही exit हो जाती है। RemainAfterExit=yes के बिना unit inactive हो जाती है, जिससे systemctl stop के पास रोकने के लिए कुछ नहीं बचता और न ही ExecStop= cleanup चलाने का कोई तरीका रहता है। इसके साथ, unit बिना किसी process के active बनी रहती है, और यहाँ यही अपेक्षित है।
क्या Type= बदलने के लिए daemon-reload की आवश्यकता होती है?
हाँ, और unit को restart करने की भी। systemctl daemon-reload systemd को disk पर मौजूद unit files को फिर से पढ़ने के लिए कहता है, लेकिन एक चल रहा instance उसी Type= को बनाए रखता है जिसके साथ वह शुरू हुआ था। परीक्षण से पहले sudo systemctl daemon-reload और फिर sudo systemctl restart app.service चलाएँ, अन्यथा आप अभी भी पुराने supervision व्यवहार को ही देख रहे होंगे।