00 Giriş: sysvinit'ten systemd'ye
PID 1 olarak systemd, boot sürecini orkestra eder — sysvinit'in sıralı çalışma modelinin yerini parallel socket activation aldı.
Kernel, userspace'i başlatmak için ilk process olarak PID 1'i çalıştırır. Tarihsel olarak bu sysvinit'ti: /etc/init.d/ altındaki shell script'leri, S10-S99 gibi sayısal sıraya göre teker teker çalıştırıyordu. Her servis bir öncekinin bitmesini bekliyordu — sequential bir pipeline. 20 servis varsa, her biri 1 saniye sürse bile boot 20 saniyeydi.
systemd'nin çözümü socket activation'dır: servis başlamadan önce socket'i kernel açar. Bir servis başka bir servisi bekliyorsa, isteği socket'e gönderir; kernel tampon tutar; servis hazır olunca yanıt verir. Bu sayede bağımlı servisler bile parallel başlayabilir.
sysvinit: S10_udev → S20_network → S30_dbus → S40_ssh (sıralı, yavaş)
systemd: udev ─┬─ network ─┬─ dbus ─┬─ ssh (parallel, hızlı)
└─ syslog └─ ntp └─ crond
systemd-analyze blame
Hangi servisin boot'u yavaşlattığını görmek için:
systemd-analyze blame
# Örnek çıktı:
# 7.423s NetworkManager-wait-online.service
# 2.101s dev-sda1.device
# 1.832s initrd-switch-root.service
# 823ms systemd-journal-flush.service
# 612ms accounts-daemon.service
# 289ms ModemManager.service
systemd-analyze time
# Startup finished in 1.823s (kernel) + 4.201s (initrd) + 8.112s (userspace)
# = 14.136s
systemd tüm büyük dağıtımlarda varsayılan init sistemidir: Ubuntu 16.04+, Debian 8+, CentOS/RHEL 7+, Fedora 15+, Arch Linux. Embedded sistemlerde Buildroot ve Yocto ile de kullanılabilir; ancak çok kısıtlı hedeflerde busybox init veya s6 tercih edilebilir.
01 Unit türleri
systemd'de her yönetilen nesne bir "unit" — dosya uzantısı türü belirler.
| Uzantı | Ne yönetir | Örnek |
|---|---|---|
.service | Daemon veya tek seferlik process | nginx.service, sshd.service |
.timer | Zamana bağlı tetikleyici (cron yerine) | backup.timer, logrotate.timer |
.socket | IPC/network socket — on-demand activation | sshd.socket, cups.socket |
.target | Senkronizasyon noktası / grup | multi-user.target, network.target |
.mount | Filesystem mount noktası | home.mount, mnt-data.mount |
.path | Dosya sistemi değişikliği izleme | cups.path |
.device | udev tarafından expose edilen cihaz | sys-bus-usb.device |
.scope | Dışarıdan başlatılan process grubu | session-1.scope |
.slice | cgroup kaynak yönetim hiyerarşisi | user.slice, system.slice |
Unit listesi görüntüleme
# çalışan tüm service unit'leri
systemctl list-units --type=service
# aktif timer'lar
systemctl list-units --type=timer
# FAILED durumundaki tüm unit'ler
systemctl --failed
# yüklü ama çalışmayan dahil — tüm bilinenleri listele
systemctl list-unit-files --type=service
Unit dosya konumları
systemctl --user ile yönetilir.Öncelik sırası: /etc/systemd/system/ > /run/systemd/system/ > /usr/lib/systemd/system/. Aynı isimli unit birden fazla yerde varsa en yüksek öncelikteki kazanır.
02 [Service] anatomisi
Service unit dosyası üç bölümden oluşur: [Unit], [Service], [Install].
Minimal bir servis: hello.service
# [Unit] — metadata ve bağımlılıklar
[Unit]
Description=Hello World servisi
After=network.target
# [Service] — çalışma parametreleri
[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/hello
ExecStart=/opt/hello/hello --port 8080
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
# [Install] — enable/disable için hangi target'a bağlanacak
[Install]
WantedBy=multi-user.target
[Service] parametreleri
- koyarsan başarısız olsa da devam eder: ExecStartPre=-/bin/mkdir -p /var/run/myappExecReload=/bin/kill -HUP $MAINPIDEnvironment="PORT=8080" "DEBUG=false"EnvironmentFile=/etc/default/myapp. Dosya KEY=VALUE formatında olmalı.Type=notify örneği
Type=notify ile daemon, başlamaya hazır olduğunda systemd'ye bildirim gönderir. systemd bu bildirimi alana kadar servisi "starting" durumunda tutar — bağımlı servisler bekler.
/* gcc -o notify-daemon notify-daemon.c -lsystemd */
#include <systemd/sd-daemon.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
/* başlatma işlemleri — socket aç, DB bağlan, vs. */
sleep(2);
/* systemd'ye "hazırım" bildir */
sd_notify(0, "READY=1\nSTATUS=Listening on port 8080\n");
/* ana döngü */
while (1) {
sd_notify(0, "WATCHDOG=1"); /* watchdog ping */
sleep(30);
}
return 0;
}
[Unit]
Description=sd_notify örnek daemon
[Service]
Type=notify
ExecStart=/usr/local/bin/notify-daemon
WatchdogSec=60s
Restart=on-failure
[Install]
WantedBy=multi-user.target
Bu bölümde öğrendikleriniz
- Unit dosyası [Unit], [Service], [Install] üç bölümünden oluşur
- Type= değeri systemd'nin PID ve hazır olma durumunu nasıl takip ettiğini belirler
- Restart= ve RestartSec= ile servis otomatik yeniden başlatılabilir
- sd_notify() ile daemon, başlangıç tamamlandığında systemd'yi bilgilendirir
03 systemctl komutları
systemctl, systemd'yi yöneten CLI aracıdır — servis başlatmaktan override dosyası yazmaya kadar her şey buradan.
Temel lifecycle komutları
# servisi başlat / durdur / yeniden başlat
systemctl start nginx.service
systemctl stop nginx.service
systemctl restart nginx.service
# reload: process'i öldürmeden konfigürasyonu yeniden yükle (ExecReload= çalışır)
systemctl reload nginx.service
# restart gerekirse reload dene, olmazsa restart yap
systemctl reload-or-restart nginx.service
# durum — Active, Loaded, Main PID, CGroup, son log satırları
systemctl status nginx.service
# boot'ta otomatik başlat / devre dışı bırak
systemctl enable nginx.service
systemctl disable nginx.service
# enable + hemen başlat
systemctl enable --now nginx.service
# mask: enable/start çağrılarını engelle (kalıcı devre dışı)
systemctl mask nginx.service
systemctl unmask nginx.service
# çalışıyor mu? (exit 0 = evet, 3 = hayır — script'lerde kullan)
systemctl is-active nginx.service
systemctl is-enabled nginx.service
systemctl is-failed nginx.service
daemon-reload ne zaman gerekli?
Unit dosyasını disk üzerinde değiştirdiğinde, systemd'nin bellekteki tanımı hâlâ eskidir. daemon-reload tüm unit dosyalarını yeniden okur:
# /etc/systemd/system/myapp.service dosyasını düzenledikten sonra
systemctl daemon-reload
systemctl restart myapp.service
daemon-reload çalışan servisleri durdurmaz. Ama reload yapmadan restart ederseniz eski konfigürasyon çalışmaya devam eder. Unit dosyası her değiştikten sonra daemon-reload zorunlu.
systemctl edit — override.conf
Paket yöneticisinin unit dosyasını doğrudan düzenlemek yerine, drop-in override kullan — orijinal korunur, güncellemede kaybolmaz:
# editörü açar → /etc/systemd/system/nginx.service.d/override.conf oluşturur
systemctl edit nginx.service
# yoksa oluştur (--force), tam dosya düzenle (--full)
systemctl edit --force --full myapp.service
# override.conf örneği — sadece değiştirmek istediğin parametreler
[Service]
# Restart politikasını değiştir — orijinal paket dosyasına dokunma
Restart=always
RestartSec=3s
LimitNOFILE=65536
Tam lifecycle örneği
# 1. unit dosyasını yaz
vim /etc/systemd/system/myapp.service
# 2. systemd'ye bildir
systemctl daemon-reload
# 3. başlat ve durumu kontrol et
systemctl start myapp.service
systemctl status myapp.service
# 4. boot'ta otomatik başlat
systemctl enable myapp.service
# 5. konfigürasyon değiştikten sonra
systemctl daemon-reload
systemctl reload-or-restart myapp.service
# 6. servis yanıt vermiyorsa zorla durdur
systemctl kill -s SIGKILL myapp.service
# 7. kalıcı olarak devre dışı bırak
systemctl disable --now myapp.service
04 journald ve log yönetimi
journald, binary ve indexed yapıda log depolar — metadata ile sorgulama, syslog'un yapamadığı şeydir.
journald'ın syslog'a göre avantajları
| Özellik | syslog | journald |
|---|---|---|
| Format | Düz metin | Binary + structured |
| Metadata | Yok / sınırlı | PID, UID, GID, cgroup, unit adı, boot ID |
| Indexleme | Yok | Var — hızlı cursor tabanlı sorgulama |
| Tamper evidence | Yok | Forward-secure sealing (opsiyonel) |
| Filtreleme | grep | journalctl field= sorguları |
| Rate limiting | Yok | RateLimitBurst=, RateLimitInterval= |
journalctl temel kullanım
# servis loglarını takip et (tail -f gibi)
journalctl -u myapp.service -f
# son 50 satır
journalctl -u myapp.service -n 50
# son 1 saat içindeki loglar
journalctl -u myapp.service --since "1 hour ago"
# zaman aralığı
journalctl -u nginx.service --since "2026-04-12 00:00" --until "2026-04-12 06:00"
# sadece err ve üstü (emerg, alert, crit, err)
journalctl -u myapp.service -p err
# kernel mesajları (dmesg gibi)
journalctl -k
# context ile birlikte — hata öncesi/sonrası satırlar
journalctl -xe
# JSON formatında çıktı (log analizi pipeline için)
journalctl -u myapp.service -o json | jq '.'
# belirli bir boot'un logları
journalctl -b -1 # bir önceki boot
journalctl -b # bu boot
Log öncelikleri
-p err bu ve üstünü gösterir.Kalıcı log: journald.conf
Varsayılan olarak journald logları RAM'de (/run/log/journal/) tutar — reboot'ta kaybolur. Kalıcı yapmak için:
[Journal]
Storage=persistent # /var/log/journal/ dizinine yaz
Compress=yes # LZ4 sıkıştırma
SystemMaxUse=2G # /var/log/journal/ maksimum boyut
SystemKeepFree=512M # diskte bırakılacak minimum boş alan
MaxRetentionSec=1month # 1 aydan eski logları sil
RateLimitBurst=1000 # aralık başına maksimum mesaj
RateLimitInterval=30s
# journald.conf değişikliği sonrası yeniden başlat
systemctl restart systemd-journald
# log kullanım alanı
journalctl --disk-usage
# 1 GB üstünü temizle
journalctl --vacuum-size=1G
# 2 haftadan eski logları temizle
journalctl --vacuum-time=2weeks
05 Timer unit'leri
systemd timer, cron'un yapamadığı şeyleri yapar: kaçırılan görevleri yakalar, log tutar, bağımlılık yönetir.
cron vs systemd timer
| Özellik | cron | systemd timer |
|---|---|---|
| Log | stdout/stderr belirli değil | journald'a otomatik |
| Kaçırılan görev | Çalışmaz | Persistent=true ile boot'ta çalışır |
| Bağımlılık | Yok | Wants=, After= kullanılabilir |
| Kaynak limiti | Yok | CPUQuota=, MemoryLimit= gibi cgroup |
| Randomize delay | Yok | RandomizedDelaySec= |
| Durum takibi | Yok | systemctl status backup.timer |
backup.timer + backup.service ikilisi
[Unit]
Description=Günlük yedekleme
After=network.target
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Günlük yedekleme timer'ı
[Timer]
OnCalendar=*-*-* 03:00:00 # her gün 03:00
RandomizedDelaySec=300 # 0-5 dakika rastgele gecikme (çakışma önleme)
Persistent=true # kaçırılan çalışma boot'ta tetiklenir
Unit=backup.service
[Install]
WantedBy=timers.target
# timer'ı etkinleştir ve başlat
systemctl enable --now backup.timer
# bir sonraki tetikleme zamanını gör
systemctl list-timers backup.timer
# tüm aktif timer'lar
systemctl list-timers --all
OnCalendar sözdizimi
*-*-* 00:00:00 ile aynı.Monotonic timer'lar
# systemctl list-timers çıktısı örneği:
# NEXT LEFT LAST PASSED UNIT
# Sun 2026-04-13 03:04:22 UTC 23h left Sat 2026-04-12 03:01:15 UTC 58min ago backup.timer
# Sun 2026-04-13 00:00:00 UTC 20h left Sat 2026-04-12 00:00:00 UTC 4h ago logrotate.timer
# Sun 2026-04-13 00:00:00 UTC 20h left Sat 2026-04-12 00:00:00 UTC 4h ago man-db.timer
Persistent=true çok kritik: sistem kapalıyken geçen bir çalışma zamanı varsa, boot'tan sonra hemen tetiklenir. Yedekleme ve güncelleme görevlerinde her zaman açık olmalı.
06 User units
Root yetkisi gerektirmeyen servisler kullanıcı kapsamında çalıştırılabilir — geliştirici araçları, agent'lar, kişisel daemon'lar için idealdir.
User unit dosyası
[Unit]
Description=Kişisel uygulama
[Service]
Type=simple
ExecStart=%h/bin/myapp # %h = $HOME
Restart=on-failure
Environment="PORT=9090"
[Install]
WantedBy=default.target
# kullanıcı systemd instance'ı için daemon-reload
systemctl --user daemon-reload
# başlat / durdur / durum
systemctl --user start myapp.service
systemctl --user stop myapp.service
systemctl --user status myapp.service
# boot'ta başlat
systemctl --user enable myapp.service
# kullanıcı logları
journalctl --user -u myapp.service -f
loginctl enable-linger
Varsayılan olarak kullanıcı systemd instance'ı, kullanıcı login olduğunda başlar ve logout'ta durur. enable-linger ile kullanıcı login olmadan da servis çalışmaya devam eder:
# emirhan kullanıcısı için linger etkinleştir (root gerektirir)
loginctl enable-linger emirhan
# linger durumunu kontrol et
loginctl show-user emirhan | grep Linger
# devre dışı bırak
loginctl disable-linger emirhan
Önemli ortam değişkenleri
unix:path=/run/user/1000/bus formatında. D-Bus kullanan uygulamalar için gerekli.User unit'leri sistem unit'lerinden önce çalışır demek değildir. network.target gibi sistem target'larına After= ile bağımlılık tanımlamak user scope'ta çalışmaz. User unit'lerin network bağımlılığı için After=network-online.target yerine bağlantıyı servisin kendi içinde yönet.
07 Dependency management
Wants vs Requires, After vs Before — yanlış anlaşıldığında servisler yanlış sırada veya hiç başlamaz.
Wants vs Requires
shutdown.target servislerle çakışır.After ve Before — sıralama
After= ve Before= sadece başlama sırası belirler, bağımlılık oluşturmaz. After=network.target yazmak, network.target'ın başlatılacağını garanti etmez — sadece "eğer ikisi de başlatılacaksa, network önce başlasın" demektir. Bağımlılık için Wants= veya Requires= ile birlikte kullan.
[Unit]
Description=Uygulama servisi
# postgresql başlamadan çalışma, postgresql dursa dur
Requires=postgresql.service
After=postgresql.service
# redis opsiyonel — yoksa da çalış ama varsa sonra başla
Wants=redis.service
After=redis.service
[Service]
Type=notify
ExecStart=/opt/myapp/bin/server
[Install]
WantedBy=multi-user.target
Target zinciri
sysinit.target ── temel sistem bileşenleri: udev, journal, cryptsetup │ basic.target ── sockets, timers, paths hazır │ multi-user.target ── ağ, servisler — text mode login ekranı │ graphical.target ── X11/Wayland display manager
# servisin bağımlılık ağacını göster
systemctl list-dependencies myapp.service
# tersine: hangi unit'ler myapp'e bağımlı?
systemctl list-dependencies --reverse myapp.service
# tüm bağımlılıkları (özyinelemeli) göster
systemctl list-dependencies --all myapp.service
# hangi target'ta çalışıyor?
systemctl get-default
08 Sandboxing ve güvenlik
systemd, unit dosyasına eklenen direktiflerle servisler için Linux security primitive'leri (namespace, capability, seccomp) etkinleştirir.
Temel sandboxing direktifleri
strict en kapsamlı seçenek; full /usr ve /boot'u read-only yapar; true sadece /usr'u.CAP_NET_BIND_SERVICE — 1024 altı port'a bind için. Tüm capability'leri kaldırmak için: CapabilityBoundingSet= (boş).ReadOnlyPaths=/etc/myappReadWritePaths=/var/lib/myapp@system-service standart daemon'lar için güvenli bir preset'tir.Güvenli bir web servisi örneği
[Unit]
Description=Güvenli web servisi
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
ExecStart=/opt/web/server
# sandboxing
ProtectSystem=strict
ReadWritePaths=/var/lib/web /var/log/web
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
ProtectKernelTunables=true
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallFilter=@system-service
[Install]
WantedBy=multi-user.target
systemd-analyze security
systemd-analyze security secure-web.service
# Örnek çıktı:
# NAME DESCRIPTION EXPOSURE
# ✓ CapabilityBoundingSet=~CAP_SYS_ADMIN Service has no administrator privileges
# ✓ PrivateTmp=yes Service has no access to other software's temporary files
# ✓ NoNewPrivileges=yes Service processes cannot acquire new privileges
# ✗ MemoryDenyWriteExecute= Service may create writable executable memory mappings 0.1
# ✗ RestrictRealtime= Service may acquire realtime scheduling 0.1
# → Overall exposure level for secure-web.service: 2.1 OK
Sandboxing direktiflerini production'da bir anda ekleme. Her direktifi test ortamında tek tek ekle, servisin hâlâ çalıştığını doğrula. Özellikle ProtectSystem=strict + ReadWritePaths= kombinasyonu, atladığın bir path varsa servisi kırabilir. AuditType=yes veya strace ile erişilen path'leri önceden tespit et.
09 Debugging ve sorun giderme
Servis başlamıyorsa, doğru araçlarla sebebi dakikalar içinde bulunur.
İlk adım: systemctl status
systemctl status myapp.service
# Örnek çıktı:
# ● myapp.service - Uygulama servisi
# Loaded: loaded (/etc/systemd/system/myapp.service; enabled; vendor preset: enabled)
# Active: failed (Result: exit-code) since Sat 2026-04-12 03:14:22 UTC; 5min ago
# Process: 12483 ExecStart=/opt/myapp/bin/server (code=exited, status=1/FAILURE)
# Main PID: 12483 (code=exited, status=1/FAILURE)
# CPU: 102ms
#
# Apr 12 03:14:22 host myapp[12483]: FATAL: cannot open config file /etc/myapp/config.yaml
journalctl -xe — extended context
# hata öncesi/sonrası bağlam ile genişletilmiş log
journalctl -xe -u myapp.service
# bu boot'ta sadece bu servise ait loglar
journalctl -b -u myapp.service
# tüm systemd mesajları (PID 1)
journalctl _PID=1
systemd-analyze — boot analizi
# boot süresi özeti
systemd-analyze time
# en yavaş servisler
systemd-analyze blame
# kritik path — boot süresini belirleyen servis zinciri
systemd-analyze critical-chain
# belirli bir servisin critical chain'i
systemd-analyze critical-chain myapp.service
# tüm boot grafiğini SVG olarak dışa aktar
systemd-analyze plot > boot.svg
FAILED durumdan kurtarma
# failed bayrağını temizle (log'u silmez)
systemctl reset-failed myapp.service
# hepsini temizle
systemctl reset-failed
# başarısız servis listesi
systemctl --failed
Sık karşılaşılan hatalar
| Hata | Neden | Çözüm |
|---|---|---|
status=203/EXEC | ExecStart yolu bulunamadı veya çalıştırılabilir değil | Tam path doğrula, chmod +x kontrol et |
status=217/USER | User= alanında belirtilen kullanıcı yok | useradd ile kullanıcı oluştur |
status=218/CAPABILITIES | Capability talep edilemiyor | AmbientCapabilities= veya User=root |
start request repeated too quickly | Servis çok hızlı yeniden başlıyor — rate limit | StartLimitBurst= ve StartLimitIntervalSec= ayarla |
Failed to connect to bus | User unit için D-Bus erişimi yok | loginctl enable-linger veya session içinde çalıştır |
Permission denied | ProtectSystem veya ReadOnlyPaths kısıtlaması | ReadWritePaths= ile gerekli path'i ekle |
Supervising process ... which is not our child | Type=forking ama PIDFile= yazılmamış | PIDFile= ekle veya Type=simple kullan |
Unit doğrulama
# unit dosyasını sözdizimi açısından kontrol et
systemd-analyze verify /etc/systemd/system/myapp.service
# unit'in tam içeriğini (override'larla birlikte) göster
systemctl cat myapp.service
# unit property'lerini göster
systemctl show myapp.service
# belirli bir property
systemctl show myapp.service -p Restart
Bu bölümde öğrendikleriniz
- systemctl status, ilk bakışta exit code ve son log satırlarını gösterir
- journalctl -xe ile hata bağlamı genişletilir
- systemd-analyze critical-chain, boot süresini belirleyen zinciri gösterir
- reset-failed ile failed bayrağı temizlenip servis yeniden denenebilir
- systemd-analyze verify, deploy öncesi unit dosyalarını kontrol eder