Merhabalar. Bugün sizlere Linux’ta bir systemd servis dosyası nasıl yazılır, kendi yazdığınız programı nasıl arka planda sürekli çalışacak bir servise dönüştürürsünüz anlatacağım. Eğer sunucunuzda bir Python betiğini, bir Node.js uygulamasını veya herhangi bir programı nohup ile arka planda çalıştırmaya uğraşıyorsanız, bu yazı tam size göre.
Systemd Nedir?
Systemd, modern Linux dağıtımlarının (Ubuntu, Debian, Fedora, Arch, CentOS ve daha fazlası) varsayılan başlatma (init) sistemidir. Sistemin açıldığı andan itibaren çalışan ilk süreçtir (PID 1) ve geri kalan tüm servisleri başlatır, yönetir ve izler.
Eski Linuxlarda servis yönetimi init.d betikleriyle yapılırdı — her servis için ayrı bash betiği yazılır, start, stop, restart argümanlarıyla çağrılırdı. Bu oldukça zahmetli ve hatalara açıktı. Systemd bu karmaşıklığı basit metin dosyalarıyla değiştirdi. Bir servis tanımlamak için .service uzantılı bir dosya yazmanız yeterli.
Neden Systemd Servisi Yazmelisiniz?
- Otomatik başlangıç: Sunucu yeniden başladığında servisiniz otomatik çalışır
- Otomatik yeniden başlatma: Servis çökerse systemd onu otomatik olarak tekrar başlatır
- Log yönetimi: Tüm loglar
journalctlile tek yerden sorgulanabilir - Bağımlılık yönetimi: Servisiniz başka bir servisten sonra başlatılabilir (mesela ağ hazır olduğunda)
- Güvenlik: Systemd’nin gömme güvenlik direktifleri ile servisinizi sandbox içine alabilirsiniz
1. Basit Bir Servis Dosyası
Başlayalım. Diyelim ki /opt/myapp/app.py adında bir Python uygulamamız var ve bunu sürekli çalıştıracağımız bir servis yapmak istiyoruz.
Servis dosyalarını /etc/systemd/system/ klasörüne kaydederiz. Bir dosya oluşturalım:
sudo nano /etc/systemd/system/myapp.service
İçine şunu yazalım:
[Unit]
Description=My Application Server
After=network.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Bu dosyada üç ana bölüm var:
[Unit]: Servisin genel bilgileri ve bağımlılıkları. Description servisin ne olduğunu açıklar. After=network.target ile bu servisin ağ hazır olduktan sonra başlatılmasını söylüyoruz.
[Service]: Servisin nasıl çalışacağı tanımlanır. Type=simple en yaygın türdür — servis ön planda çalışır, systemd bunu takip eder. ExecStart çalıştırılacak komuttur. Restart=on-failure servis hata ile çıkarsa tekrar başlatılır. RestartSec=5 ise yeniden başlatma arasındaki bekleme süresidir (saniye).
[Install]: Servisin ne zaman otomatik başlayacağını belirler. WantedBy=multi-user.target servisin çok kullanıcılı modda (normal açılışta) başlamasını sağlar.
2. Servisi Aktifleştirme
Dosyayı kaydettikten sonra systemd’nin bu yeni dosyayı görmesi için şu komutu çalıştırın:
sudo systemctl daemon-reload
Şimdi servisi başlatalım:
sudo systemctl start myapp
Durumunu kontrol edelim:
sudo systemctl status myapp
Çıktınız şuna benzer olmalı:
● myapp.service – My Application Server
Loaded: loaded (/etc/systemd/system/myapp.service; disabled)
Active: active (running) since Thu 2026-07-30 03:00:01 +03; 5s ago
Main PID: 12345 (python3)
Tasks: 2 (limit: 4915)
Memory: 24.5M
CPU: 120ms
CGroup: /system.slice/myapp.service
└─12345 /usr/bin/python3 /opt/myapp/app.py
active (running) görmek servisin çalıştığı anlamına gelir. disabled yazısı ise henüz açılışta otomatik başlamaya ayarlanmadığını gösterir. Bunu düzeltelim:
sudo systemctl enable myapp
Artık sunucu yeniden başladığında servis otomatik olarak çalışacak.
3. Logları İnceleme
Systemd’nin en güzel yanlarından biri log yönetimidir. Tüm servis logları journalctl aracıyla sorgulanabilir:
sudo journalctl -u myapp -f
-f bayrağı logları canlı akış olarak gösterir (tail -f gibi). Hata loglarını görmek için:
sudo journalctl -u myapp -p err
Son 100 satır log için:
sudo journalctl -u myapp -n 100
4. Service Türleri
Type= direktifi servisin nasıl başladığını systemd’ye söyler. Beş tür vardır:
Type=simple: En yaygın seçim. ExecStart ile belirtilen komut çalışır ve systemd bunu servisin ana süreci olarak takip eder. Uygulama ön planda kalmalıdır (fork yapmamalıdır).
Type=forking: Geleneksel daemon’lar için. Servis başlar, kendini fork eder (çocuk süreç oluşturup ana süreçten çıkar) ve sistemd ana sürecin çıktığını görür. Eğer uygulamanız --daemon bayrağıyla arka plana geçiyorsa bu türü kullanın. Bu durumda PIDFile= de tanımlamanız gerekir:
[Service]
Type=forking
PIDFile=/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon
Type=oneshot: Bir kez çalışıp biten betikler için. Bir kurulum betiği ya da yapılandırma değişikliği gibi. Servis çalışır, biter ve systemd inactive (dead) olarak gösterir.
Type=exec: 2025 itibariyle önerilen modern alternatif. simple ile benzer ama systemd ExecStart komutunun başarıyla başlatıldığını doğrular. Yeni servis yazıyorsanız exec kullanmanız önerilir.
Type=notify: Servis systemd’ye hazır olduğunu sinyal gönderir. Özellikle uzun başlayan servisler için uygundur — systemd servisin hazır olduğunu bildirim alana kadar “starting” durumunda bekler.
5. Çevresel Değişkenler
Uygulamanızın çevresel değişkenlere (environment variables) ihtiyacı varsa, birkaç yöntem vardır:
Doğrudan servis dosyasında:
[Service]
Environment="DATABASE_URL=postgresql://user:pass@localhost:5432/db"
Environment="API_KEY=secret123"
Veya bir dosyadan okutarak:
[Service]
EnvironmentFile=/etc/myapp/env.conf
/etc/myapp/env.conf dosyasının içeriği şöyle olur:
DATABASE_URL=postgresql://user:pass@localhost:5432/db
API_KEY=secret123
Bu yöntem daha güvenlidir çünkü şifre obürü servis dosyasında görünmez ve systemctl show myapp komutuyla servis detayları sorgulansa bile şifreler maskingelenir.
6. Servisi Güvenlikle Sertleştirme (Hardening)
Systemd’nin en güçlü ama az bilinen özelliklerinden biri gömme güvenlik direktifleridir. Bir servis saldırıya uğradığında zararı sınırlamak için servisi sandbox içine alabilirsiniz. Bu direktifler servisin dosya sistemine, ağa ve sistem çağrılarına erişimini kısıtlar.
Hardening eklemek için servis dosyanızın [Service] bölümüne şu direktifleri ekleyin:
[Service]
# Kullanıcı değiştirmeyi engelle
NoNewPrivileges=yes
# /home ve /root'a erişimi engelle
ProtectHome=yes
# Tüm dosya sistemini salt okunur yap, sadece gerekli yerlere yazma izni ver
ProtectSystem=strict
ReadWritePaths=/var/log/myapp /var/lib/myapp
# Özel /tmp dizini ver (diger servislerle paylaşmaz)
PrivateTmp=yes
# /dev altındaki cihazlara erişimi kısıtla
PrivateDevices=yes
# Kernel parametrelerini değiştirmeyi engelle
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
# Sadece gerekli ağ adres ailelerine izin ver
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# SUID/SGID bitli dosyaları yok say
RestrictSUIDSGID=yes
# Sistem çağrılarını filtrele
SystemCallFilter=@system-service
SystemCallFilter=~@debug @mount @swap @reboot @privileged
SystemCallErrorNumber=EPERM
Bu direktiflerin ne yaptığını merak ediyorsanız, systemd-analyze security komutuyla her servisin güvenlik puanını görebilirsiniz:
sudo systemd-analyze security myapp
Çıktı şuna benzer:
NAME DESCRIPTION
myapp.service
Overall exposure score for myapp.service: 3.5 SAFE 😀
Puan 0.0 (çok güvenli) ile 10.0 (güvensiz) arasındadır. Çoğu varsayılan servis 7-9 arası puan alır, yani ciddi sertleştirme potansiyeli var. Yukarıdaki direktifleri eklediğinizde puanınız 3-4 civarına düşecektir.
7. Yaygın Hatalar ve Çözümleri
Hata: “Failed to start myapp.service: Unit not found”
daemon-reload komutunu çalıştırmayı unutmuş olabilirsiniz. Servis dosyasını her değiştirdiğinizde şu komutu çalıştırın:
sudo systemctl daemon-reload
Hata: “Status: inactive (dead)” — servis kısa sürede duruyor
Type=simple kullanıyorsanız ve uygulamanız kendini fork edip çıkıyorsa, systemd ana sürecin çıktığını görür ve servisi durdurulmuş sayar. Ya Type=forking kullanın ya da uygulamanızı ön planda çalışacak şekilde yapılandırın.
Hata: “Permission denied” — servis dosyasını çalıştıramıyor
ExecStart ile belirtilen dosyanın çalıştırılabilir (executable) olduğundan emin olun:
sudo chmod +x /opt/myapp/app.py
Hata: Servis logları boş — hiçbir şey göremiyorum
Eğer uygulamanız logları stdout/stderr yerine kendi dosyasına yazıyorsa journalctl göremez. Uygulamanızı logları stdout’a yazacak şekilde ayarlayın, ya da ExecStart komutunu şöyle düzenleyin:
ExecStart=/usr/bin/python3 /opt/myapp/app.py 2>&1
Hata: “Too many start requests too quickly”
Servis sürekli çöküp yeniden başlıyorsa systemd belirli bir süre sonra denemeyi bırakır. Restart=on-failure yerine Restart=always kullanın ve StartLimitIntervalSec ve StartLimitBurst değerlerini artırın:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=10
[Service]
Restart=always
RestartSec=10
İpuçları
systemctl editkullanın: Varsayılan servis dosyalarını doğrudan düzenlemeyin — paket güncellemesi geldiğinde üzerine yazılır. Bunun yerinesudo systemctl edit servisadikullanın, bu bir drop-in dosyası oluşturur ve sadece değiştirdiğiniz direktifleri geçersiz kılar.
systemctl catile içerik görüntüleyin: Bir servisin tam tanımını görmek içinsudo systemctl cat myappkullanın — hem ana dosyayı hem de tüm drop-in’leri gösterir.
- Önce test edin: Servisi aktifleştirmeden önce
ExecStartkomutunu manuel olarak çalıştırın. Çalışıyorsa sisteme yükleyin. En sık yapılan hata, servis dosyasında yanlış yol ya da yanlış kullanıcı belirtmektir.
Type=execkullanın: Eğer servisiniz 2025 sonrası bir Linux’ta çalışacaksaType=simpleyerineType=execkullanın. Sistemd komutun gerçekten başladığını doğrular, bu yüzden “exec failed” gibi hataları erken yakalarsınız.
Sonuç
Systemd ile servis yazmak göründüğünden çok daha basittir. Temelde bir metin dosyasına birkaç satır yazıyorsunuz ve uygulamanız bir Linux servisine dönüşüyor — otomatik başlıyor, çökerse yeniden başlatılıyor ve logları tek yerden sorgulanabiliyor. Güvenlik direktiflerini ekleyerek servisinizi saldırıya uğradığında bile zararını sınırlayacak şekilde sandbox içine alabilirsiniz. Okuduğunuz için teşekkür ederim!