SSD Nodes Learn 🎉 VPS $5.50/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor

systemd Type ayarları: simple, forking ve notify farkları

systemd biriminiz aktif görünmesine rağmen daemon sonlanmışsa Type ayarınız hatalı olabilir. simple, forking, notify ve oneshot seçeneklerini doğru yapılandırmayı öğ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; Type= kısmındaki [Service] ayarı, bu sürecin hangisi olduğunu belirler. Yanlış 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, kendisine izlemesi söylenen süreç hakkında doğru bilgiyi vermektedir.

Yeniden başlatma politikasını değiştirmek bu durumda yardımcı olmaz. 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ılavuzunda ele alınmıştır.

Type= parametresi neyi belirler

Her bir Type= değeri aynı anda iki soruya yanıt verir. systemd bir birimin ne zaman başlatılmış kabul edileceğini ve ana sürecin hangisi olduğunu belirler.

İlk yanıt, sıralamayı kontrol eder. Kendi birimini After= içerisinde belirten 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 yanıt veremeden ç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 biriminin 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 (süreç kimliği) daha dar kapsamlıdır. Bu, çıkış yapması birimi sonlandıran ve çıkış durumu birimin sonucu haline gelen tek süreçtir. cgroup'u ana PID gibi okumak, kafa karışıklığının başladığı noktadır.

Type=simple, ikili dosya çalışmadan önce başlatıldı bildirimi yapar

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, yaygın bir şaşkınlığı açıklar. ExecStart= yolundaki bir yazım hatası, başarılı görünen bir başlatma işi üretir; hata ise yürütme başarısız olduğunda kısa bir süre sonra ortaya çıkar. systemd bu durumu, kendi tablosunda EXEC olarak adlandırdığı ve servis ikili dosyasının çalıştırılamaması 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 kendisini asla arka plana almayan programlar için simple kullanın. Bu, çoğu modern daemon ve kendi yazdığınız hemen hemen her şey için geçerlidir.

Type=exec, programın gerçekten başlamasını bekler

Type=exec, simple ile aynıdır ancak bir adım daha fazlası vardır. systemd, birimi yalnızca hem fork işlemi hem de binary dosyasının çalıştırılması başarıyla tamamlandığında başlatılmış kabul eder. Eksik bir binary dosyası veya çözümlenemeyen bir User=, artık başlatma işleminin sessizce başarılı rapor edilip kısa süre sonra hata vermesi yerine, doğrudan başlatma işinin başarısız olmasına neden olur.

Type=exec, systemd 240 ile 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 bir program 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ç oluşturacağını (fork) 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.

Buradaki zorluk kimlik tespitidir. systemd tarafından başlatılan süreç artık mevcut olmadığından, systemd hangi hayatta kalan sürecin ana süreç olduğunu belirlemek zorundadır. PIDFile= değerini, daemon'ın PID'sini yazdığı dosyaya (genellikle /run altında bir yol) ayarlayın; systemd PID'yi bu dosyadan okuyacaktır. systemd ayrıca bu dosyadaki PID'nin ilgili servise 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üvenilir bulunmayarak reddedilir.

PIDFile= belirtilmediğinde GuessMainPID= uygulanır ve varsayılan değeri yes'tir. Bu tahmin, yalnızca servis tek bir süreç halinde kararlı hale geldiğinde güvenilirdir. Kılavuz, sınırlamayı açıkça belirtir: daemon birden fazla süreçten oluşuyorsa tahmin hatalı olabilir ve hata tespiti çalışmayı durdurur. Bir birim, ana PID'si 0 olacak şekilde de sonuçlanabilir; bu, systemd'nin denetleyecek hiçbir süreci kalmadığı anlamına gelir.

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çin

Type=oneshot, sürecin çalışıp sonlanmasını bekler. systemd, birimi ancak süreç sonlandıktan sonra başlatılmış kabul eder; bu da oneshot yapısını, başka bir birimin beklemesi gereken her türlü işlem için uygun kılar. Ayrıca, bir birimde Type= veya ExecStart= belirtilmediğinde varsayılan olarak kabul edilen değerdir.

oneshot türüne özgü iki davranış mevcuttur. 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 kendiniz TimeoutStartSec= değerini ayarlamadığınız sürece sonsuza kadar bekler.

Süreç sonlandıktan sonra birim, inaktif durumuna döner. RemainAfterExit=yes, hiçbir süreç çalışmadığı halde birimi active durumunda tutar. Bu, sayfanın başındaki belirtinin bilinçli olarak uygulanan halidir ve birimin görevi bir şeyi çalışır durumda tutmak 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ında 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 yolunu aldığı bir Unix soketi üzerinden gönderene kadar başlatma işini açık tutar. 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 önce başlatıldığını rapor eder; bu nedenle bağımlı bir birim çok erken başlayabilir ve ilk bağlantı denemesinde başarısız olabilir. notify ise başlatılma durumunu, servisin bizzat hazır olduğunu bildirdiği anda rapor eder.

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 programdan geliyorsa NotifyAccess=all ayarlanmalıdır. Bir kabuk betiği systemd-notify --ready komutunu çağırabilir ancak bu ayrı ve kısa ömürlü bir süreç olarak çalışır; bu yüzden NotifyAccess=all gerektirir ve systemd, göndericisi ç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 beri mevcut olan 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 ve systemd, kaçırılan bir süreyi başarısızlık 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 servis bir isim alana kadar bekler. Bu tür, BusName= gerektirir ve BusName= ayarlandığı anda varsayılan değer haline gelir. Yalnızca gerçekten bir veri yolu ismi kaydeden servisler için kullanılmalıdır.

Type=idle, simple ile benzer şekilde çalışır ancak programın çalışmasını, kuyruktaki işler gönderilene kadar beş saniyelik bir üst sınırla geciktirir. Bu türün varlık amacı, önyükleme sırasındaki konsol çıktılarının durum mesajlarıyla birbirine karışmasını önlemektir. 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 9101

systemd, 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 tüm süre boyunca birimin cgroup'u içinde kalır, bu nedenle 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 bir tane varsa, kabuğu onunla değiştirin.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec, kabuğu belirtilen programla değiştirir ve aynı PID'yi korur; böylece systemd'nin kaydettiği PID artık arka plan sürecine (daemon) 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 arka plan sürecini doğrudan başlatabilir ve PID'sini yapısı gereği bilir.

Eğer iki tane 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 durdurulmuş kabul edilir. ExitType=cgroup ile birim, cgroup içindeki herhangi bir süreç yaşadığı sürece çalışıyor kabul edilir.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Bu ayar belirli bir sorunu çözer. Asıl işi başlatan ve ardından sonlanan bir başlatıcı (launcher), ExitType=main altında systemd'nin birimi durdurulmuş saymasına ve hayatta kalan süreçleri öldürmesine neden olur. ExitType=cgroup ile birim, tüm grubu takip eder.

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 denetleyicisi haline getirmez. ExitType= ayrıca Type=oneshot ile birlikte kullanılamaz.

Cgroup, kaynak muhasebesinin de yapıldığı yerdir; bu nedenle MemoryMax= ve CPUQuota= gibi sınırlar, Type= ana PID hakkında ne söylerse söylesin, birimin oluşturduğu her sürece uygulanır. Konunun bu tarafı systemd ile bir servisin bellek ve CPU kullanımını sınırlama başlığında ele alınmıştır.

systemd'nin gerçekte izlediği süreç nasıl bulunur

Hata ayıklaması 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.service

systemctl cat, birim dosyasını ve ona uygulanan tüm drop-in dosyalarını yazdırır; böylece düzenlediğinizi sandığınız dosyayı değil, systemd'nin gerçekten yüklediği yapılandırmayı okumuş olursunuz. systemctl show, hiçbir zaman yazmadığınız varsayılanlar 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 MainPID değerinin bir shell'e işaret etmesi, yukarıda bahsedilen wrapper durumudur. Beklediğinizden daha fazla süreç içeren bir cgroup, bir başlatıcı (launcher) veya forking yapan bir daemon'ın devrede olduğunu gösterir.

systemctl status app.service
journalctl -u app.service -b

systemctl status, durum satırını ve cgroup ağacını birlikte yazdırır; bu nedenle genellikle her iki soruya da aynı anda yanıt verir. journalctl -u ile -b kullanılarak mevcut önyükleme (boot) ile sınırlandırılan çıktı, systemd'nin birim için kaydettiği başlatma ve durdurma olaylarını ve gördüğü çıkış kodlarını gösterir. Daemon, loglarını journal yerine kendi log 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.service

systemd-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 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 çıktısından 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ğeri kullanılmalıdır

  • Ön planda çalışan bir program için: Type=exec.
  • Hazır olma bildirimini destekleyen bir program için: Type=notify; yeniden yükleme işlemlerini de doğruluyorsa notify-reload.
  • Arka plana geçmekte ısrar eden bir daemon için: PIDFile= ile birlikte Type=forking veya ilgili ön plan anahtarı ile Type=exec.
  • İşini yapıp sonlanan bir betik için: Type=oneshot; durum bilgisi bırakılması hedefleniyorsa buna ek olarak RemainAfterExit=yes.
  • Alt süreçleri çalışmaya devam ederken kendisi sonlanan bir başlatıcı için: ExitType=cgroup ile birlikte Type=simple.

Üçüncü taraf bir daemon için hangisinin gerekli olduğundan emin değilseniz, öncelikle paketle birlikte 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

Süreç sonlandığı halde systemd birimim neden aktif kalıyor?

Çünkü systemd'nin ana süreç olarak kabul ettiği süreç hala hayattadır. systemd, birim içindeki her süreci değil, Type= ayarına göre seçilen tek bir PID'yi izler. Type=simple ile başlatılan bir sarmalayıcı (wrapper) betiği genellikle buna neden olur: kabuk (shell) ana PID'dir, bu nedenle kabuğun arka planda başlattığı bir daemon kapansa bile birim aktif kalmaya devam eder. 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şinin ardından bir hata ile sonuçlanır. Type=exec, 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 hala PIDFile= kullanmam gerekiyor mu?

Evet, daemon bir dosya oluşturuyorsa gereklidir. Bu olmadan systemd, GuessMainPID= yöntemine geri döner; bu bir tahmindir ve yalnızca tek bir sürece yerleşen servisler için güvenilirdir. Tahmin yanlış olduğunda veya imkansız olduğunda, o birim için hata tespiti ve otomatik yeniden başlatma çalışmaz. PIDFile= değerini, daemon'un yazdığı tam yola, genellikle /run altındaki bir konuma yönlendirin.

RemainAfterExit=yes ayarını ne zaman kullanmalıyım?

Birimin amacı bir süreci çalışır durumda tutmak 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 inaktif duruma düşer; bu da systemctl stop için durdurulacak bir şey kalmaması ve ExecStop= temizlik işleminin çalıştırılamaması anlamına gelir. Bu ayar ile 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.