systemd Type= parametreleri: simple, forking, notify farkı
Servisiniz aktif görünmesine rağmen daemon sonlanıyorsa systemd Type ayarınız hatalı olabilir. Hangi Type değerinin hangi süreç modeline uygun olduğunu ve PID takibini öğrenin.
systemd neden süreç sonlandığı halde birimi aktif olarak raporlar
Bir systemd servis birimi, systemd'nin ana süreç olarak tanımladığı süreç hayatta olduğu sürece active durumunda kalır; [Service] bölümündeki Type= ayarı, hangi sürecin ana süreç olduğunu belirler. Yanlış bir değer seçildiğinde systemd, bir kabuk sarmalayıcıyı (shell wrapper) veya kısa ömürlü bir üst süreci izlerken, sizin asıl ilgilendiğiniz daemon aynı birim içinde sonlanabilir. Birim, izlemesi söylenen süreç hakkında size doğru bilgiyi vermektedir.
Yeniden başlatma politikasını değiştirmek burada işe yaramaz. Restart=, ana süreç sonlandığında devreye girer; bu nedenle ana PID (süreç tanımlayıcı) hala çalışmakta olan bir şeye ait olduğu sürece Restart=always asla tetiklenmez. Öncelikle Type= ayarını düzeltin. Ana süreç gerçekten sonlandıktan sonra systemd'nin ne yapacağı ayrı bir karardır ve Restart= ve RestartSec= kılavuzu içerisinde ele alınmıştır.
Type= parametresi neyi belirler
Her Type= değeri aynı anda iki soruya yanıt verir. systemd bir birimin ne zaman başlatılmış sayılacağını ve ana sürecin hangisi olduğunu belirler.
İlk yanıt sıralamayı kontrol eder. After= içerisinde sizin biriminizi referans gösteren bir birim, systemd sizinkini başlatılmış olarak işaretleyene kadar bekler. Çok erken "başlatıldı" raporu veren bir Type=, bağımlı birimlerin sizin servisiniz henüz hazır olmadan çalışmaya başlamasına neden olur.
İkinci yanıt denetimi kontrol eder. systemd, bir birimin oluşturduğu her süreci bir cgroup (kontrol grubu) içine yerleştirir; bu, süreçlerin sınırlandırılmasını ve birlikte sonlandırılmasını sağlayan bir çekirdek özelliğidir. cgroup, systemctl stop mekanizmasının temizlik yapma yöntemidir: KillMode= varsayılan olarak control-group değerindedir, bu nedenle bir birimi durdurmak, içindeki her sürece sinyal gönderir. Ana PID (Process ID) daha dar kapsamlıdır. Bu, çıkış yapması birimin sonlanmasına neden olan ve çıkış durumu birimin sonucu haline gelen tek süreçtir. cgroup yapısını ana PID gibi okumak, kafa karışıklığının başladığı noktadır.
Type=simple, ikili dosya çalışmadan önce başlatıldı raporu verir
Type=simple, ExecStart= ayarlandığında ve Type= ile BusName= mevcut olmadığında varsayılan değerdir. systemd süreci oluşturur, birimi hemen başlatılmış kabul eder ve bu süreci ana PID olarak değerlendirir. Takip eden birimler, servis ikili dosyası henüz çalıştırılmadan hemen başlar.
Bu son detay, sık karşılaşılan bir şaşkınlığı açıklar. ExecStart= yolundaki bir yazım hatası, başarılı görünen bir başlatma işi oluşturur; hata ise yürütme başarısız olduğunda bir an sonra ortaya çıkar. systemd bu durumu EXEC olarak adlandırdığı ve servis ikili dosyasını yürütme hatası olarak tanımladığı 203 çıkış koduyla kaydeder. Dolayısıyla systemctl start komutunun hatasız dönmesi, ikili dosyanızın var olduğunu kanıtlamaz.
Ön planda kalan ve kendini asla arka plana almayan bir program için simple kullanın. Bu, çoğu modern daemon'ı ve kendi yazdığınız hemen hemen her şeyi kapsar.
Type=exec, programın gerçekten başlamasını bekler
Type=exec, simple komutunun bir adım daha eklenmiş halidir. systemd, birimi yalnızca hem fork işlemi hem de ikili dosyanın çalıştırılması başarıyla tamamlandığında başlatılmış kabul eder. Eksik bir ikili dosya veya çözümlenemeyen bir User=, artık sessizce hata vermek yerine başlatma işleminin doğrudan başarısız olmasına neden olur.
Type=exec, systemd 240 sürümüyle gelmiştir, bu nedenle güncel tüm sunucu dağıtımlarında mevcuttur. Ağustos 2026 itibarıyla Ubuntu 24.04, systemd 255; Debian 13 ise systemd 257 sürümünü sunmaktadır. Kendi sürümünüzü systemctl --version ile kontrol edebilirsiniz.
Bunun maliyeti, başlatma sırasında fazladan bir senkronizasyon adımıdır. Kazancı ise systemctl start üzerinden alınan dürüst bir çıkış durumudur. Ön planda çalışan programlar için simple yerine exec tercih edilmelidir.
Type=forking ve ana PID'nin kaybolması
Type=forking, systemd'ye ExecStart= içindeki sürecin bir alt süreç (child) oluşturacağını ve ardından kasıtlı olarak sonlanacağını bildirir. systemd, bu ilk sürecin sonlanmasını bekler ve ancak o zaman birimi başlatılmış olarak işaretler. Geride kalan alt süreç ise daemon'dır. Bu alışkanlık, init betiği döndüğünde daemon'ı denetleyen hiçbir mekanizmanın bulunmadığı ve PID dosyasının çalışan sürecin tek kaydı olduğu SysV döneminden kalmadır; bu kısıtlama, systemd'nin neden init betiklerinin yerini aldığı konusunun merkezinde yer alır.
Buradaki zorluk kimlik tespitidir. systemd tarafından başlatılan süreç sonlandığı için, systemd hangi sürecin ana süreç olduğunu belirlemek zorundadır. PIDFile= değerini daemon'ın yazdığı dosyaya, genellikle /run altındaki bir yola ayarlayın; systemd PID'yi bu dosyadan okuyacaktır. systemd ayrıca dosyadaki PID'nin bu hizmete ait bir süreci işaret edip etmediğini kontrol eder; böylece alakasız bir süreci işaret eden eski bir dosya güvenilmek yerine reddedilir.
PIDFile= kullanılmadığında GuessMainPID= geçerli olur ve varsayılan değeri yes'tir. Bu tahmin, yalnızca hizmet tek bir süreçte kararlı hale geldiğinde güvenilirdir. Kılavuz, sınırı açıkça belirtir: daemon birden fazla süreçten oluşuyorsa tahmin yanlış olabilir ve hata tespiti çalışmayı durdurur. Bir birim, systemd'nin denetleyeceği hiçbir şeyin kalmadığı anlamına gelen 0 ana PID değerine de sahip olabilir.
Fork yapan çoğu daemon'ın, süreci ön planda tutan bir anahtarı bulunur. Bu anahtarı Type=exec ile kullanın ve PIDFile= satırını silin. Daha az hareketli parça, PID'yi kaybetmek için daha az olasılık demektir.
Type=oneshot, tamamlanan işler içindir
Type=oneshot, sürecin çalışıp sonlanmasını bekler. systemd, birimi ancak süreç sonlandıktan sonra başlatılmış kabul eder; bu durum oneshot yapısını, başka bir birimin beklemesi gereken her türlü işlem için en uygun seçenek haline getirir. Ayrıca, birim içerisinde Type= veya ExecStart= belirtilmediğinde varsayılan olarak kabul edilen değerdir.
oneshot için iki davranış özeldir. Birden fazla ExecStart= satırını kabul eden tek türdür ve bu satırlar sırayla çalıştırılır. Başlatma zaman aşımı varsayılan olarak devre dışıdır; bu nedenle, takılı kalan bir oneshot, siz TimeoutStartSec= değerini kendiniz ayarlamadığınız sürece sonsuza kadar bekler.
Süreç sonlandıktan sonra birim, inaktif durumuna döner. RemainAfterExit=yes, birimi, herhangi bir süreç çalışmıyor olmasına rağmen active durumunda tutar. Bu, sayfanın en üstünde belirtilen belirtinin bilinçli olarak uygulanan halidir ve birimin görevi bir şeyi çalıştırmaya devam etmek değil, geride bir durum bırakmak olduğunda (güvenlik duvarı kurallarını yüklemek veya bir container yığınını ayağa kaldırmak gibi) doğru olan yöntemdir. Bu, yeniden başlatma sonrası geri gelen bir Docker Compose yığını arkasındaki modeldir; birim compose komutunu çalıştırır, sonlanır ve başlattığı container'lar kendisinden daha uzun süre yaşadığı için aktif kalmaya devam eder. Bir oneshot birimi aynı zamanda bir zamanlayıcının tetiklediği şeydir; bu da cron yerine systemd timer üzerinde iş çalıştırma konusunun diğer yarısını oluşturur.
Type=notify, servisin hazır olduğunu bildirmesini sağlar
Type=notify, karar verme sorumluluğunu servise devreder. systemd, süreç READY=1 mesajını NOTIFY_SOCKET ortam değişkeninde yolu belirtilen Unix soketi üzerinden gönderene kadar başlatma işlemini bekletir. C arayüzü sd_notify(3) şeklindedir ve birçok sunucu bunu halihazırda desteklemektedir.
Bu, "başlatıldı mı" sorusuna verilen en doğru yanıttır. simple ve exec, servis henüz yapılandırmasını okumadan veya dinleme soketini açmadan başlatıldı raporu verir; bu nedenle bağımlı bir birim çok erken başlayabilir ve ilk bağlantı denemesinde hata alabilir. notify ise başlatıldı raporunu, servisin bizzat hazır olduğunu bildirdiği anda verir.
systemd, bu mesajı yalnızca ana süreçten kabul eder; NotifyAccess=main ifadesinin anlamı budur ve Type=notify bunu zorunlu kılar. Mesaj bir alt süreçten veya yardımcı bir süreçten geliyorsa NotifyAccess=all ayarını yapılandırın. Bir kabuk betiği systemd-notify --ready komutunu çağırabilir ancak bu ayrı ve kısa ömürlü bir süreç olarak çalıştığından NotifyAccess=all gerektirir; ayrıca systemd, göndereni çoktan sonlanmış bir mesajı ilişkilendiremeyebilir. Protokolü doğrudan kendisi yürüten bir servis daha güvenilirdir.
İlgili iki ayarın bilinmesinde fayda vardır. systemd 253 sürümünden itibaren kullanılabilen Type=notify-reload, aynı el sıkışma sürecini yeniden yükleme işlemlerine de genişletir; böylece systemctl reload, sinyal gönderildiğinde değil, servis yeniden yüklemenin tamamlandığını bildirdiğinde döner. WatchdogSec=, bildirim yapan bir servisten belirli aralıklarla bir canlılık mesajı göndermesini ister; systemd, kaçırılan bir süreyi hata olarak değerlendirir.
Type=dbus ve Type=idle
Type=dbus, sistem ve masaüstü servislerinin birbiriyle iletişim kurmak için kullandığı mesaj veri yolu olan D-Bus üzerinde servisin bir isim almasını bekler. BusName= gerektirir ve BusName= ayarlandığı anda varsayılan değer haline gelir. Bunu yalnızca gerçekten bir veri yolu ismi kaydeden servisler için kullanın.
Type=idle, simple ile benzer şekilde davranır ancak programın çalışmasını, kuyruktaki işler gönderilene kadar beş saniyelik bir üst sınırla geciktirir. Bu, önyükleme sırasındaki konsol çıktısının durum mesajlarıyla karışmaması için mevcuttur. Bir sıralama aracı değildir ve normal bir serviste kullanılmamalıdır.
Bir sarmalayıcı betiğin systemd'yi neden yanlış PID üzerinde bıraktığı
Orijinal soruna yol açan yapı şu şekildedir.
[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, kabuğu (shell) ana PID olarak kaydeder. exporter ön planda çalışırken kabuk hayatta kalmaya devam eder. Eğer server sonlanırsa, kabuk bunu fark etmez; dolayısıyla ana PID hala hayattadır, birim hala active durumundadır ve Restart= üzerinde işlem yapacak bir şey bulamaz. Her iki süreç de birimin cgroup'u içerisinde kalmaya devam ettiği için systemctl stop temizliği hala doğru şekilde yapar. Bozulan şey denetim mekanizmasıdır, temizlik değil.
Çözüm, birimin gerçekte kaç adet uzun süreli çalışan sürece sahip olduğuna bağlıdır.
Eğer tek bir süreç varsa, kabuğu onunla değiştirin.
#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yamlexec, kabuğu belirtilen programla değiştirir ve aynı PID'yi korur; böylece systemd'nin kaydettiği PID artık daemon'a aittir. Daha da iyisi, sarmalayıcıyı kaldırın. Environment= ve EnvironmentFile= değişkenleri taşır, ExecStartPre= ise kurulum adımını gerçekleştirir; böylece systemd daemon'ı doğrudan başlatabilir ve PID'sini yapısı gereği bilir.
Eğer iki süreç varsa, birimi temsil eden tek bir PID yoktur. Bunları iki ayrı birime bölün ve After= ile Wants= kullanarak sıralayın. Süreç başına bir birim, systemd'nin iyi denetlediği bir düzenlemedir ve her sürecin kendi yeniden başlatma davranışına sahip olmasının tek yolu budur.
ExitType=cgroup değişiklikleri
ExitType=, systemd 250 sürümünde eklenmiştir. Varsayılan değer main şeklindedir: ana süreç sonlandığında birim durmuş kabul edilir. ExitType=cgroup kullanıldığında ise, cgroup içerisindeki herhangi bir süreç hayatta kaldığı sürece birim çalışıyor kabul edilir.
[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcherBu ayar belirli bir sorunu çözer. Asıl işi başlatan ve ardından sonlanan bir başlatıcı, ExitType=main altında systemd tarafından durdurulmuş kabul edilir ve hayatta kalan süreçler öldürülür. ExitType=cgroup ile birim, tüm grubu takip eder.
Bu ayarın neyi çözmediği konusunda net olunmalıdır. ExitType=cgroup, en az bir süreç yaşadığı sürece birimi aktif tutar; bu nedenle iki daemon barındıran bir birim, bunlardan biri öldüğünde aktif kalmaya devam eder. Bu ayar yalnızca başlatıcı senaryosunu düzeltir. Bir birimi, birbirinden bağımsız birkaç sürecin yöneticisi haline getirmez. ExitType= ayrıca Type=oneshot ile birlikte kullanılamaz.
Kaynak muhasebesi de cgroup üzerinde gerçekleştiğinden, Type= ana PID hakkında ne derse desin, MemoryMax= ve CPUQuota= gibi limitler birimin oluşturduğu her sürece uygulanır. Bu konunun teknik detayları systemd ile bir servisin bellek ve CPU kullanımını sınırlama bölümünde yer almaktadır.
systemd'nin gerçekten izlediği süreç nasıl bulunur
Hata ayıklama yaptığınız birimde, sırasıyla aşağıdaki adımları izleyin. Önce systemd'nin neyi yüklediğini okuyun, ardından neyi takip ettiğini inceleyin ve son olarak bunu süreç tablosuyla karşılaştırın.
systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.servicesystemctl cat, birim dosyasını ve ona uygulanan tüm ek yapılandırmaları (drop-in) birlikte yazdırır; böylece düzenlediğinizi hatırladığınız dosyayı değil, systemd'nin gerçekten yüklediği içeriği okumuş olursunuz. systemctl show, yazmadığınız varsayılan değerler de dahil olmak üzere geçerli değerleri yazdırır. Devam etmeden önce MainPID değerini not edin.
systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"systemd-cgls, birimin cgroup'undaki her süreci listeler. ps satırı, systemd'nin denetlediği tek süreci tanımlar. İkisini birlikte okuyun. MainPID değerinin 0 olması, systemd'nin izleyecek bir süreci olmadığı anlamına gelir. cgroup içinde daemon'ınız bulunurken bir kabuğa (shell) yönlenen MainPID, yukarıda bahsedilen sarmalayıcı (wrapper) durumudur. Beklediğinizden daha fazla süreç içeren bir cgroup, bir başlatıcı veya çatallanan (forking) bir daemon'ın devrede olduğunu gösterir.
systemctl status app.service
journalctl -u app.service -bsystemctl status, durum satırını ve cgroup ağacını birlikte yazdırdığı için genellikle her iki soruya da aynı anda yanıt verir. -b ile mevcut önyükleme (boot) ile sınırlandırılmış journalctl -u, systemd'nin birim için kaydettiği başlatma ve durdurma olaylarını, gördüğü çıkış kodlarıyla birlikte gösterir. Eğer daemon, journal yerine kendi günlük dosyasına yazıyorsa, o dosyayı da okuyun; çünkü systemd yalnızca kendisine ulaşan verileri kaydedebilir.
Type= üzerinde değişiklik yaptığınızda, yeniden yükleyin ve yeniden başlatın.
systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.servicesystemd-analyze verify, dosyayı ayrıştırır ve kabul edemediği ayarları bildirir. daemon-reload, systemd'nin birim dosyalarını diskten yeniden okumasını sağlar. Değiştirilen bir Type=, halihazırda çalışan bir birime otomatik uygulanmaz; bu nedenle yeniden başlatma isteğe bağlı değil, zorunludur.
Ardından değişikliği test edin. Gerçekten ilgilendiğiniz sürecin PID değerini systemd-cgls üzerinden alın ve süreci sonlandırın. Hemen ardından systemctl is-active app.service komutunu çalıştırın. Eğer Type= doğru yapılandırılmışsa, birim aktif durumdan çıkar. Eğer aktif kalmaya devam ediyorsa, systemd hala başka bir şeyi izliyor demektir.
Hangi systemd servis Type= değerini kullanmalısınız
- Ön planda çalışan bir program için:
Type=exec. - Hazır olduğunu bildirme desteği olan bir program için:
Type=notify, yeniden yükleme işlemlerini de onaylıyorsanotify-reload. - Arka plana geçmekte ısrar eden bir daemon için:
PIDFile=ile birlikteType=forkingveya ilgili ön plan anahtarı ileType=exec. - İşini yapıp sonlanan bir betik için:
Type=oneshot, geride bir durum bırakması amaçlanıyorsa ek olarakRemainAfterExit=yes. - Kendisi sonlanıp çocuk süreçleri çalışmaya devam eden bir başlatıcı için:
ExitType=cgroupile birlikteType=simple.
Üçüncü taraf bir daemon için hangisinin gerekli olduğundan emin değilseniz, önce paketle gelen unit dosyasını okuyun. Dağıtımın sağladığı bir unit üzerinde systemctl cat komutunu çalıştırmak, geliştiricinin seçtiği Type= değerini gösterir; bu seçim sizinkinden daha fazla kişi tarafından test edilmiştir.
FAQ
systemd birimim, süreç sonlandığı halde neden aktif kalıyor?
Çünkü systemd'nin ana süreç olarak kabul ettiği süreç hala çalışmaktadır. systemd, birim içindeki her süreci değil, Type= ayarına göre belirlenen tek bir PID'yi izler. Type=simple ile başlatılan bir sarmalayıcı (wrapper) betik genellikle buna neden olur: kabuk (shell) ana PID'dir, bu yüzden kabuğun arka planda başlattığı bir daemon kapansa bile birim aktif kalır. systemctl show -p MainPID app.service komutunu çalıştırın, ardından systemd-cgls --unit=app.service ile birimin cgroup listesini alın ve ikisini karşılaştırın.
Type=simple ve Type=exec arasındaki fark nedir?
Type=simple, systemd süreci oluşturduğu anda, ikili dosya (binary) henüz çalıştırılmadan birimin başladığını kabul eder; bu nedenle ExecStart= içindeki hatalı bir yol, başarılı bir başlatma işi ve ardından gelen bir hata ile sonuçlanır. Type=exec ise yürütme başarılı olana kadar bekler, böylece hata doğrudan başlatma işi tarafından raporlanır. Her ikisi de aynı süreci ana PID olarak kabul eder. Type=exec, systemd 240 veya daha yeni bir sürüm gerektirir.
Type=forking ile birlikte hala PIDFile= kullanmam gerekiyor mu?
Evet, daemon bir dosya yazıyorsa gereklidir. Bu olmadan systemd, sadece tek bir sürece yerleşen servisler için güvenilir olan bir tahmin yöntemi olan GuessMainPID= seçeneğine geri döner. Tahmin yanlış olduğunda veya imkansız hale geldiğinde, o birim için hata tespiti ve otomatik yeniden başlatma çalışmaz hale gelir. PIDFile= değerini, daemon'ın dosyayı yazdığı tam yola, genellikle /run altındaki bir konuma yönlendirin.
RemainAfterExit=yes seçeneğini ne zaman kullanmalıyım?
Birimin amacı bir süreci çalıştırmaya devam etmek değil, sistem durumunu değiştirmek olduğunda kullanılmalıdır. Güvenlik duvarı kurallarını yükleyen veya bir container yığınını başlatan bir Type=oneshot birimi, işi biter bitmez sonlanır. RemainAfterExit=yes olmadan birim pasif duruma geçer; bu da systemctl stop için durdurulacak bir şey kalmaması ve ExecStop= temizlik işleminin çalıştırılamaması anlamına gelir. Bu seçenekle birim, süreç olmasa bile aktif kalır; amaçlanan davranış budur.
Type= değişikliği daemon-reload gerektirir mi?
Evet, ayrıca birimin yeniden başlatılması da gerekir. systemctl daemon-reload, systemd'nin diskteki birim dosyalarını yeniden okumasını sağlar ancak çalışan bir örnek, başladığı Type= ayarlarını korur. Test etmeden önce sudo systemctl daemon-reload ve ardından sudo systemctl restart app.service komutlarını çalıştırın; aksi takdirde eski denetim davranışını izlemeye devam edersiniz.