systemd'de unit dosyası: bölümler, yeniden başlatma ve bağımlılık

systemd'de unit dosyası yapısının ayrıntılı kılavuzu: Unit, Service ve Install bölümleri, Restart politikaları, bağımlılık sıralaması ve servisi sessizce devre dışı bırakan yaygın hatalar.

7 dk Güncellendi 1 Oct 2026

systemctl start ile ayağa kalkan ama birkaç dakika sonra sessizce ölen bir servisin sorunu neredeyse her zaman uygulamanın kendisinde değil, unit dosyasındadır. Belirtisi de nettir: systemctl status inactive (dead) durumunu gösterir ve journalctl -u myservice hiçbir açık hata vermez. Yani süreç sıfır çıkış koduyla ya da bir sinyalle sonlanmıştır ve systemd de sizin belirlemediğiniz politikaya göre onu tekrar ayağa kaldırmamaya karar vermiştir. Bu makale işte o unit dosyasını parça parça açıyor.

Unit dosyası nerede durur ve hangi sürüm kazanır

systemd üç yolu öncelik sırasına göre okur: /etc/systemd/system/ en yüksek öncelik, sonra /run/systemd/system/ ve en son /usr/lib/systemd/system/. Aynı isimde bir dosya iki yolda varsa, önceliği yüksek olan kazanır ve alt sürüm tamamen yok sayılır. Bu şu anlama gelir: vendor dosyasını olduğu gibi bırakıp kendi sürümünüzü /etc içine yazabilirsiniz; paket güncellemesi artık değişikliklerinizi silmez.

Her değişiklikten sonra iki komut gerekir ve biri yeterli değildir:

systemctl daemon-reload
systemctl restart myservice

daemon-reload çalıştırmazsanız, systemd eski sürümü bellekte tutar ve siz değişikliğinizin etkisi olmadığını düşünürsünüz. Burada hata yapılır: kullanıcı dosyayı düzenler, restart çeker, hiçbir şey olmaz ve sonra saatlerce uygulama kodunda hata arar. Belirtisi şudur: systemctl cat myservice az önce kaydettiğiniz dosyadan farklı bir şey gösterir.

Unit dosyasının üç bölümü ve gerçekten kullanılan anahtarlar

Her unit dosyası [Unit], [Service] ve [Install] bölümlerinden oluşur. Dördüncü bölüm [Unit] socket-activated servisler için vardır ki bu ayrı bir konudur.

[Unit] bölümü: kimlik ve sıralama

  • Description= systemctl status içinde görünen metin. Kısa ve ASCII dışı karakter olmadan yazın.
  • After=network-online.target bu servisin ağ hazır olduktan sonra çalıştırılması anlamına gelir. Dikkat edin After sadece sıralamadır, gereksinim değildir.
  • Requires= o unit ayağa kalkmazsa bunun da çalışmayacağı anlamına gelir. Wants= bunun daha zayıf sürümüdür: dener, ama başarısızlığı çalışmayı engellemez.
  • BindsTo= Requires'dan daha katıdır; karşı unit durursa bu servis de durur.

After ile Requires arasındaki farkı ciddiye alın. Çoğu kişi sadece After=mysql.service yazar ve MySQL ayakta değilken servisinin çalışmamasını bekler. Olmaz. After sadece "ikisi de kuyruktaysa, önce o" der. Gerçek bağımlılık için Requires ya da Wants'a da ihtiyacınız var.

[Service] bölümü: işin kalbi

Bu bölümün en önemli anahtarı Type='dır ve yanlış seçimi en çok kafa karışıklığının kaynağıdır:

TypeNe zaman kullanılırYanlış olduğunun belirtisi
simpleVarsayılan; süreç foreground'da kalırServis hemen active olur ama çalışmaz
forkingUygulama kendisi daemonize olursystemd ana süreci kaybeder ve servis dead olur
notifyUygulama sd_notify ile hazır olduğunu bildirirServis sonsuza kadar activating kalır
oneshotTek seferlik iş, başlatma betiği gibiServis iş bittikten sonra dead olur ki bu normaldir

Neredeyse her zaman gereken çalıştırma anahtarları:

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp/env

Bu blokta iki nokta var. Birincisi, EnvironmentFile eksi işaretiyle dosya yoksa hata verme demektir; o olmadan dosyanın olmaması servisin tamamen başarısız olmasına yol açar. İkincisi, LimitNOFILE'ı ciddiye alın: systemd'nin varsayılanı genellikle 1024'tür ve ağ uygulamaları gerçek yük altında Too many open files hatasıyla düşer. Sunucu yükü artmışsa ve nedenini bilmiyorsanız, bu kontrol etmeniz gereken yerlerden biridir; sunucu yüksek yükünün nedenini teşhis etme kılavuzu inceleme yolunu adım adım ele alıyor.

[Install] bölümü: ne zaman etkinleşsin

WantedBy=multi-user.target bu servisin normal önyüklemede etkinleşmesi anlamına gelir. Bu satır yoksa, systemctl enable çalışır ama uyarı verir ve servis önyüklemede ayağa kalkmaz. Timer'lar için WantedBy=timers.target yazarsınız.

Yeniden başlatma politikası: Restart= ve gerçekte ne olduğu

En çok kullanılan Restart= değerleri şunlardır: no (varsayılan), on-failure, always, on-abnormal ve on-abort. on-failure ile always arasındaki fark tek bir şeydedir: süreç sıfır çıkış koduyla sonlanırsa, on-failure bunu başarılı sayar ve tekrar çalıştırmaz, ama always çalıştırır. Her zaman canlı kalması gereken bir servis için always doğru seçimdir.

Ama burada az kişinin değindiği bir tuzak var: sınırsız Restart=always, bozuk servisi sonsuz döngüye sokar. systemd bunu varsayılan olarak StartLimitBurst ve StartLimitIntervalSec ile engeller; yaygın varsayılan 10 saniyede 5 kez başlatmadır. Bu sınırı aşarsa, servis failed durumuna girer ve artık denemez. Journal'daki tam mesaj şudur:

start request repeated too quickly for myservice.service

Bunu gördüğünüzde, systemctl reset-failed myservice durumu temizler, ama asıl sorunu çözmez. Önce çıkış nedenini bulun, sonra politikayı ayarlayın. RestartSec=5 de önemsiz değildir; çok küçük bir değer arıza zamanında kaynaklar üzerindeki baskıyı katlar.

Servisler arası bağımlılık: sıralama, gereksinim ve durdurma

Üç bağımsız eksen vardır ve yaygın hata bunları karıştırmaktır:

  1. Başlatma sırası: After= ve Before=. Sadece sırayı belirler.
  2. Gereksinim: Requires=, Wants=, BindsTo=. Çalışıp çalışmayacağını belirler.
  3. Durdurma yayılımı: PartOf= ve PropagatesReloadTo=. Ana servis yeniden başlatıldığında bunun da yeniden başlatılıp başlatılmayacağını belirler.

Veritabanına ihtiyaç duyan bir servis için doğru kalıp üçünün birleşimidir:

[Unit]
Description=My App
After=network-online.target mysql.service
Wants=network-online.target
Requires=mysql.service
PartOf=mysql.service

[Service]
Type=simple
ExecStart=/opt/myapp/bin/server
Restart=always
RestartSec=5

PartOf=mysql.service ile, MySQL her yeniden başlatıldığında uygulama da otomatik olarak yeniden başlatılır ve ölü bağlantılar kalmaz. Çoğu kişi bu satırı atlar ve her veritabanı yeniden başlatmasından sonra uygulama bayat bağlantılarla hata verir, ta ki biri onu elle ayağa kaldırana kadar.

Pratik bir uyarı: Requires çift yönlü değildir. MySQL'i elle stop ederseniz, servisiniz de durur, ama servisiniz çökerse MySQL olduğu gibi kalır. Çift yönlü davranış istiyorsanız, BindsTo'ya bakın; bunun bedeli, karşı servisteki geçici bir hatanın tüm zinciri düşürmesidir.

Pratikte sık görülen hatalar

Göreli yolla ExecStart. systemd mutlak yol ister; ExecStart=./server, dosya var olsa bile Exec format error ya da No such file or directory hatasıyla reddedilir. Her zaman tam yolu yazın.

User= eksikliği. O olmadan, servis root kullanıcısıyla çalışır. Bu sadece güvenlik meselesi değildir; servisin oluşturduğu dosyaların sahibi root olur ve sonradan uygulama kullanıcısı onları okuyamaz. Özel sunucu ya da bulut sunucu üzerinde birkaç servisi yan yana çalıştırıyorsanız, bu tuhaf erişim hatalarının en yaygın nedenlerinden biridir.

ExecReload'u yok saymak. Onu tanımlamazsanız, systemctl reload pratikte tam bir restart ile aynıdır ve aktif bağlantılar kesilir. Nginx ya da HAProxy gibi kesintisiz reload yapan servisler için bu fark önemlidir.

Ve konteyner servisleri hakkında bir nokta: uygulamanız Docker içinde çalışıyorsa, genellikle elle unit dosyasına ihtiyacınız yoktur ve Docker'ın kendi Restart=always'ı yeterlidir. VPS üzerinde Docker kurulumu kılavuzu bu iki katman arasındaki farkı açıklıyor; ikisini sebepsiz birleştirmek sadece sorun gidermeyi zorlaştırır.

Sık sorulan sorular

Unit dosyasını düzenledikten sonra neden değişiklikler uygulanmıyor?

Çünkü systemd dosyanın sürümünü bellekte önbelleğe alır ve systemctl daemon-reload çalıştırmadığınız sürece o eski sürümü çalıştırır. Reload'dan sonra servisi de yeniden başlatın; tek başına reload, çalışan süreci yeni ayarlarla ayağa kaldırmaz.

Unit dosyasında After ile Requires arasındaki fark nedir?

After sadece başlatma sırasını belirler ve karşı unit çalışmasa bile servisiniz yine de ayağa kalkar. Requires gerçek bağımlılık oluşturur; o unit başarısız olursa servisiniz de çalışmaz. Tam bağımlılık için ikisine birlikte ihtiyacınız var.

Restart=always olan bir servis neden failed durumuna giriyor?

Çünkü systemd yeniden başlatma döngüsünü engeller. Servis StartLimitIntervalSec aralığında StartLimitBurst'tan fazla başlatılırsa, durdurulur ve journal'a start request repeated too quickly mesajı gelir. systemctl reset-failed ile durum temizlenir, ama çıkış nedeni ayrıca giderilmelidir.

Unit dosyasını paket güncellemesiyle silinmeyecek şekilde nereye oluşturmalıyım?

/etc/systemd/system/ içine. /usr/lib/systemd/system/ dosyaları kurulu paketlere aittir ve güncellemede üzerine yazılabilir. Mevcut bir unit'in sadece bir kısmını değiştirmek istiyorsanız, tam kopya yerine systemctl edit myservice kullanın ki /etc/systemd/system/myservice.service.d/ içinde drop-in oluşturulsun.

Sonraki adım belli: şu anda sorun yaşayan serviste systemctl cat çalıştırın, [Service] bölümünü burada anlatılanlarla karşılaştırın ve önce Type= ile Restart='ı düzeltin. "Kendiliğinden ölen" servislerin çoğu sadece bu iki anahtarı yanlış yapıyor.

Bu sayfa yardımcı oldu mu?