Sabah dokuzda yedek dizinine bakıyorsunuz ve gece üçte oluşması gereken dosya orada yok. Betiği kendiniz çalıştırıyorsunuz: on iki saniyede bitiyor, dosya oluşuyor, echo $? sıfır dönüyor. Crontab satırında da gözle görülür bir yanlış yok. Yine de o iş ya hiç başlamadı ya da başladı ve kimseye haber vermeden çuvalladı.
Cron'un "kararsız" ya da "güvenilmez" olduğu yönündeki yaygın kanı neredeyse her zaman yanlıştır. Cron ne yaptığını gayet net söyler; sorun, varsayılan ayarlarla bunu kimsenin bakmadığı bir yere söylemesidir. Üstelik cron, görevinizi sizin terminalinizdekiyle aynı ortamda değil, kasıtlı olarak çıplak bırakılmış bir ortamda başlatır: kısa bir PATH, /bin/sh kabuğu, okunmamış bir .bashrc ve ev dizininizde başlayan bir çalışma dizini.
Bu yazıda "elde çalışıyor, cron'da çalışmıyor" tablosunun sekiz gerçek nedenini tek tek ele alacağız. Her nedenin yanında, o nedenin sizin sunucunuzda gerçekten geçerli olup olmadığını gösteren tek satırlık bir doğrulama komutu var. Sonunda da bir daha aynı belirsizliği yaşamamak için, ne yaptığını kendi kendine log'a yazan bir cron şablonu kuracağız.
Cron Hiç Çalışmadı mı, Çalıştı da Başarısız mı Oldu?#
Diğer her şeyden önce bu ayrımı yapın; çünkü iki durumun çözüm yolları taban tabana zıttır. Cron, başlattığı her komut için sistem günlüğüne bir satır yazar:
# Debian / Ubuntu
sudo journalctl -u cron.service --since "today" | tail -n 30
grep CRON /var/log/syslog | tail -n 30
# AlmaLinux / Rocky / RHEL
sudo journalctl -u crond.service --since "today" | tail -n 30
sudo tail -n 30 /var/log/cron
Aradığınız satır şuna benzer:
Aug 18 03:00:01 web01 CRON[24817]: (deploy) CMD (/usr/local/bin/gece-yedek.sh)
Aug 18 03:00:01 web01 CRON[24816]: (CRON) info (No MTA installed, discarding output)
Birinci satırdaki CMD ifadesi kesin bilgidir: cron o dakikada komutu başlatmıştır. Bu satır varsa sorun cron'da değil, betiğinizin cron ortamındaki davranışındadır; doğrudan birinci nedenden itibaren ilerleyin. İkinci satır ise ayrı bir uyarıdır ve beşinci nedenin habercisidir: iş bir şeyler yazdı, sunucuda posta aktarım aracı olmadığı için o çıktı çöpe gitti.
CMD satırı hiç yoksa komut hiç başlatılmamış demektir. O zaman şu üç şeyi sırayla kontrol edin:
systemctl is-active cron # RHEL ailesinde: crond
crontab -l | grep -v '^\s*#' # kendi crontab'ınız
sudo crontab -l -u deploy # işi gerçekten hangi kullanıcı taşıyor?
Debian ve Ubuntu'da cron kayıtları varsayılan olarak /var/log/syslog içinde diğer her şeyle karışır. Ayrı bir dosya istiyorsanız /etc/rsyslog.d/50-default.conf içindeki cron.* satırının yorumunu kaldırıp sudo systemctl restart rsyslog çalıştırın. Günlükleri filtrelemenin pratik yolları için journalctl ile log yönetimi rehberi işinizi kolaylaştırır.
Neden 1: PATH Minimaldir, Komut Bulunamıyor#
Kullanıcı crontab'ları çalışırken PATH genellikle yalnızca /usr/bin:/bin değerindedir. Terminalde /usr/local/bin ve /snap/bin gibi dizinler de aramaya dahil olduğu için composer, node, certbot gibi komutlar orada bulunur, cron'da bulunmaz. Sonuç, çıktıyı yönlendirmediyseniz hiçbir yerde göremediğiniz bir command not found satırıdır.
Doğrulaması çok basittir. Geçici bir satır ekleyip cron'un gerçek ortamını diske döktürün:
* * * * * env > /tmp/cron-ortam.txt 2>&1
Bir dakika sonra kendi ortamınızla karşılaştırın:
diff <(env | sort) <(sort /tmp/cron-ortam.txt)
Kalıcı çözüm için ya komutları mutlak yolla yazın (command -v php ile öğrenirsiniz) ya da crontab'ın en üstüne kendi PATH tanımınızı koyun. Tanımın kendisinden sonraki satırlar için geçerli olduğunu unutmayın.
Bir uyarı: nvm, pyenv, rbenv gibi sürüm yöneticileri komutları gerçek ikili dosyalar yerine kabuk işlevleriyle çözer. Bunlarda PATH satırı eklemek yetmez; ikinci nedendeki yaklaşım gerekir.
Neden 2: Kabuk /bin/sh'tir ve .bashrc Hiç Okunmaz#
Burada aslında iki ayrı tuzak iç içe geçer.
Birincisi kabuğun kendisidir. Cron, SHELL tanımlamadıysanız komutu /bin/sh ile çalıştırır ve Debian ile Ubuntu'da /bin/sh, bash değil dash'tir. [[ ]], source, diziler ve ${degisken,,} gibi bash'e özgü yapılar dash'te sözdizimi hatası verir. Betiğinizin dash ile uyumlu olup olmadığını çalıştırmadan sınayabilirsiniz:
dash -n /usr/local/bin/gece-yedek.sh
İkincisi ortam dosyalarıdır. Cron ne giriş kabuğu (login shell) ne de etkileşimli kabuk açar. ~/.profile hiç okunmaz; ~/.bashrc ise ilk satırlarındaki "etkileşimli değilse geri dön" koruması nedeniyle kendini kapatır. Dolayısıyla orada tanımladığınız PATH eklemeleri, takma adlar ve dışa aktarılmış değişkenler cron için yoktur.
Doğru çözüm, betiğin ihtiyaç duyduğu ortamı kendi içinde açıkça kurmasıdır:
#!/usr/bin/env bash
set -Eeuo pipefail
# Surum yoneticisini elle yukle
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# .env dosyasindaki degiskenleri otomatik disa aktararak yukle
set -a
. /var/www/site/.env
set +a
Betiği doğrudan çalıştırılabilir kılıp shebang satırı koyduğunuzda, shebang crontab'taki SHELL değerini geçersiz kılar. Yine de tek satırlık komutlar için crontab'ın başına SHELL=/bin/bash yazmak sürprizi baştan keser. Betik yazımının temelleri için bash betik yazma temelleri yazısına göz atabilirsiniz.
Neden 3: Kaçırılmamış Yüzde İşareti Satırı Ortadan Kesiyor#
Bu, cron'un en sinsi kuralıdır ve belgelerde yazılı olmasına rağmen neredeyse herkesi bir kez yakalar: crontab satırında kaçırılmamış her yüzde karakteri yeni satıra dönüşür ve ilk yüzde işaretinden sonraki her şey komuta standart girdi olarak verilir. Yani komutunuz tam o noktada biter.
# YANLIS - dosya adi "db-.sql" olur, tarih hic eklenmez
0 3 * * * /usr/bin/mysqldump uygulama > /yedek/db-$(date +%F).sql
# DOGRU - yuzde isareti ters boluyle kacirilmis
0 3 * * * /usr/bin/mysqldump uygulama > /yedek/db-$(date +\%F).sql
Şüpheli satırları tek komutla listeleyin:
crontab -l | grep -n '%' | grep -v '\\%'
Kalıcı çözüm ise kuralı ezberlemek yerine ondan kaçınmaktır: tarih biçimlendirmesi, boru hatları ve tırnaklı ifadeler crontab satırında değil, bir betiğin içinde yaşasın. Betik dosyasının içinde yüzde karakterinin hiçbir özel anlamı yoktur. Crontab satırı yalnızca betiği çağırsın.
Neden 4: Çalışma Dizini Ev Dizininizdir, Göreli Yollar Kayar#
Cron, görevi kullanıcının /etc/passwd içinde tanımlı ev dizininde başlatır; projenizin kökünde değil. Terminalde proje dizinindeyken çalışan ./bin/isle, config/ayar.yml veya logs/uygulama.log gibi her göreli yol, cron altında başka bir yeri işaret eder ya da hiçbir yeri.
* * * * * pwd > /tmp/cron-dizin.txt
Crontab satırında cd ile başlamak en kısa çözümdür, ama bağlacı && yapmaya dikkat edin; dizin yoksa komutun hiç çalışmaması, yanlış dizinde çalışmasından iyidir:
0 2 * * * cd /var/www/site && /usr/bin/php artisan schedule:run >> /var/log/site-cron.log 2>&1
Betiği taşınabilir kılmak isterseniz, betiğin kendi konumunu bulup oraya geçmesi daha sağlamdır:
cd "$(dirname "$(readlink -f "$0")")" || exit 1
Neden 5: Çıktı Yutuluyor, Hata Var Ama Kimse Görmüyor#
İnternetteki örneklerin çoğu satır sonuna > /dev/null 2>&1 ekler. Bu ekleme yalnızca gereksiz posta bildirimlerini değil, hata mesajını da susturur. İş her gece başarısız olur, log boş kalır, kimsenin haberi olmaz.
Cron varsayılan olarak çıktıyı görevin sahibine e-posta ile göndermeye çalışır. Sunucuda bir posta aktarım aracı kurulu değilse günlükte gördüğünüz No MTA installed, discarding output satırı tam olarak bunun kanıtıdır. Doğru kurulum, postayı kapatıp çıktıyı dosyaya yazmaktır:
MAILTO=""
0 3 * * * /usr/local/bin/gece-yedek.sh >> /var/log/gece-yedek.log 2>&1
2>&1 ifadesinin yönlendirmeden sonra gelmesi zorunludur; başa yazarsanız hata çıktısı hâlâ eski hedefe gider. Log dosyalarının zamanla diski doldurmaması için de bir döndürme kuralı tanımlayın; ayrıntılar logrotate ile log yönetimi yazısında.
Neden 6: İzinler, Sahiplik ve Yanlış Kullanıcının Crontab'ı#
Bu başlık altında birkaç ayrı hata toplanır ve hepsinin belirtisi aynıdır: cron CMD satırını yazar, iş bir şey yapmaz.
- Çalıştırma bayrağı yok.
chmod +x /usr/local/bin/gece-yedek.shçalıştırılmamışsaPermission deniedalırsınız.ls -lile bir saniyede görürsünüz. - Yanlış kullanıcının crontab'ı.
crontab -esizin,sudo crontab -eroot'un crontab'ını düzenler. İşinwww-dataolarak çalışması gerekiyorsasudo crontab -e -u www-datakullanın. - Hedef dosyaya yazma izni yok. Betik
/var/log/altına yazmaya çalışıyor ama ayrıcalıksız bir kullanıcı olarak koşuyorsa engellenir. Dosya sahipliği ve mod mantığı için Linux dosya izinleri yazısı iyi bir başvuru kaynağıdır. /etc/cron.dkuralları. Buradaki dosyalar kullanıcı crontab'ından farklı davranır ve kurallara uymayan dosya sessizce yok sayılır.
| Kural | Yanlış | Doğru |
|---|---|---|
| Alan sayısı | 5 alan, kullanıcı yok | 6 alan: 0 3 * * * root /yol/betik.sh |
| Dosya adı | yedek.sh, yedek.conf | yedek (nokta ve uzantı yok) |
| Sahiplik | Sıradan kullanıcıya ait | root:root |
| İzin | 0666 gibi yazılabilir mod | 0644 |
/etc/cron.daily ve kardeşlerindeki dosyalar run-parts ile çalıştırıldığı için aynı adlandırma kuralına tabidir. Hangi dosyaların gerçekten çalışacağını çalıştırmadan görebilirsiniz:
sudo run-parts --test /etc/cron.daily
RHEL ailesinde bir ihtimal daha vardır: SELinux, cron'un alışılmadık bir konumdaki betiği çalıştırmasını engelleyebilir. sudo getenforce çıktısı Enforcing diyorsa sudo ausearch -m avc -ts recent sonuçlarına bakın.
Neden 7: Cron'un Sessizce Yok Saydığı Söz Dizimi Hataları#
Cron hatalı bir satırı size bildirmek yerine çoğu zaman atlar. En sık görülen üç durum şunlardır.
Alan sayısı karışıklığı. Kullanıcı crontab'ı beş zaman alanı bekler; /etc/crontab ve /etc/cron.d ise araya kullanıcı adını alarak altı alan bekler. Kendi crontab'ınıza alışkanlıkla root yazarsanız cron root adında bir komut aramaya kalkar.
Son satırda yeni satır karakteri olmaması. Crontab'ı bir dosyadan yüklerken missing newline before EOF uyarısıyla karşılaşırsınız ve satır kurulmaz. Kontrolü şöyle yapabilirsiniz:
crontab -l | tail -n 2 | cat -A
Her satırın sonunda bir dolar işareti görmelisiniz; yoksa dosya eksik bitmiştir.
Ayın günü ile haftanın günü arasındaki "veya" kuralı. Bu, belgelerde yazılı olduğu hâlde en çok şaşırtan davranıştır: iki alanın ikisi de yıldız değilse, cron işi iki koşuldan herhangi biri tuttuğunda çalıştırır.
# Ayin 13'unde VE ayrica her cuma calisir - sadece 13'une denk gelen cuma degil
0 0 13 * 5 /usr/local/bin/rapor.sh
İki koşulun aynı anda sağlanmasını istiyorsanız kontrolü betiğe taşıyın: cron her cuma tetiklesin, betik ayın 13'ü olup olmadığını kendi içinde sınasın.
Neden 8: Saat Dilimi ve Sunucunun Kapalı Geçtiği Zaman#
Yeni kurulan sunucuların büyük bölümü UTC ile gelir. Türkiye kalıcı olarak UTC+3 kullandığı için, crontab'a yazdığınız 0 3 * * * satırı aslında Türkiye saatiyle 06:00'da çalışır. Önce gerçeği görün:
timedatectl
date
İki yol var. Yalnızca cron'un saatini kaydırmak için crontab'ın başına dilim tanımı koyabilirsiniz:
CRON_TZ=Europe/Istanbul
0 3 * * * /usr/local/bin/gece-yedek.sh >> /var/log/gece-yedek.log 2>&1
Ya da sunucunun tamamını yerel saate alabilirsiniz. Bu tercih log damgalarını ve uygulama davranışını da etkiler, o yüzden bilinçli verilmelidir:
sudo timedatectl set-timezone Europe/Istanbul
sudo systemctl restart cron
İkinci mesele kaçırılan çalıştırmalardır. Cron telafi yapmaz: sunucu gece üçte kapalıysa ya da tam o anda yeniden başlatılıyorsa o çalıştırma tamamen kaybolur. Telafi davranışı istiyorsanız anacron ya da Persistent=true ayarlı systemd zamanlayıcıları doğru araçtır; karşılaştırma için systemd timer ile zamanlanmış görevler yazısına bakın.
Teşhisi Kalıcı Kılan Şablon: Kendini Loglayan Cron Sarmalayıcısı#
Yukarıdaki sekiz nedeni her seferinde tek tek elemek yerine, işi baştan kendini anlatan biçimde kurmak çok daha ucuzdur. Aşağıdaki betik; başlangıcı, kullanıcıyı, çalışma dizinini, süreyi ve çıkış kodunu zaman damgasıyla log'a yazar:
#!/usr/bin/env bash
set -Eeuo pipefail
IS="gece-yedek"
LOG="/var/log/${IS}.log"
log() { printf '%s [%s] %s\n' "$(date '+%F %T')" "$IS" "$*" >> "$LOG"; }
trap 'log "BEKLENMEYEN HATA: satir $LINENO"' ERR
log "basladi - kullanici=$(id -un) dizin=$PWD"
cd /var/www/site || { log "calisma dizini yok, cikiliyor"; exit 1; }
BAS=$SECONDS
if /usr/bin/php artisan backup:run >> "$LOG" 2>&1; then
log "tamamlandi - sure $((SECONDS - BAS)) sn"
else
log "BASARISIZ - cikis kodu $?"
exit 1
fi
Crontab tarafı ise tek satır kalır. flock eklemesi, önceki çalıştırma hâlâ sürüyorsa yenisinin başlamasını engeller; uzun süren yedeklerde üst üste binen işler diski ve veritabanını kilitleyen klasik nedenlerden biridir:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
CRON_TZ=Europe/Istanbul
0 3 * * * /usr/bin/flock -n /var/lock/gece-yedek.lock /usr/local/bin/gece-yedek.sh
Bir adım ötesi, işin çalışmadığını da fark etmektir. Betiğin başarıyla bittiği yere bir izleme ucuna istek atan tek satır koyarsanız, o istek gelmediğinde uyarı alırsınız. Log'a bakmayı hatırlamak zorunda kalmamanın en pratik yolu budur; temel cron söz dizimini tazelemek isterseniz Linux cron görevleri yazısı başlangıç noktanız olsun.
Hızlı Teşhis Tablosu#
| Belirti | Muhtemel neden | Doğrulama komutu |
|---|---|---|
Günlükte CMD satırı yok | Servis kapalı ya da yanlış kullanıcının crontab'ı | systemctl is-active cron |
command not found | Kısa PATH | * * * * * env > /tmp/cron-ortam.txt |
Syntax error: unexpected | Kabuk /bin/sh yani dash | dash -n betik.sh |
| Dosya adında tarih eksik | Kaçırılmamış yüzde işareti | crontab -l çıktısında yüzde arayın |
No such file or directory | Çalışma dizini ev dizini | * * * * * pwd > /tmp/cron-dizin.txt |
| Hiç hata görünmüyor | Çıktı /dev/null hedefine gidiyor | crontab -l içinde dev/null arayın |
Permission denied | Çalıştırma bayrağı veya yazma izni | ls -l /usr/local/bin/betik.sh |
| İş üç saat geç çalışıyor | Sunucu UTC dilimindedir | timedatectl |
Sıkça Sorulan Sorular#
Cron'un çalıştığını nasıl kesin olarak doğrularım?#
Sistem günlüğüne bakın. Debian ve Ubuntu'da grep CRON /var/log/syslog, systemd tarafında journalctl -u cron.service --since today, RHEL ailesinde ise /var/log/cron dosyası cron'un başlattığı her komutu CMD etiketiyle kaydeder. Bu satır varsa cron görevini yapmıştır ve sorun betiğin kendisindedir. Satır yoksa görev hiç tetiklenmemiştir; servis durumunu ve doğru kullanıcının crontab'ını kontrol edin.
Betiğim terminalde çalışıyor ama cron'da çalışmıyor, ilk neye bakmalıyım?#
Ortam farkına bakın. Cron çok kısa bir arama yolu ve /bin/sh kabuğuyla, sizin ev dizininizde başlar; ~/.bashrc ile ~/.profile hiç okunmaz. Geçici bir görevle ortam değişkenlerini bir dosyaya döküp kendi çıktınızla karşılaştırmak farkı saniyeler içinde ortaya koyar. Vakaların büyük çoğunluğu mutlak yol kullanmak ve gerekli değişkenleri betiğin içinde tanımlamakla çözülür.
Cron'dan neden e-posta gelmiyor?#
Çünkü çoğu sunucuda kurulu bir posta aktarım aracı yoktur. Cron çıktıyı göndermeye çalışır, başaramaz ve günlüğe No MTA installed, discarding output satırını yazar. Postaya güvenmek yerine çıktıyı bir log dosyasına yönlendirin ve MAILTO değerini boş bırakarak posta denemesini tamamen kapatın. Böylece hem gürültü biter hem de hatalar kalıcı olarak kayıt altına alınır.
Crontab satırındaki yüzde işaretini neden kaçırmam gerekiyor?#
Cron, kaçırılmamış her yüzde karakterini yeni satır olarak yorumlar ve ilk yüzde işaretinden sonraki metni komuta standart girdi olarak verir. date +%F gibi bir kullanım bu yüzden satırı ortasından keser ve komut eksik parametreyle çalışır. Ters bölü ile kaçırmak sorunu çözer, ancak daha sağlam yöntem tarih ve boru hatlarını bir betiğe taşımaktır; betik içinde yüzde işaretinin özel bir anlamı yoktur.
Aynı görevin iki kopyasının üst üste çalışmasını nasıl engellerim?#
flock kullanın. Crontab satırını /usr/bin/flock -n /var/lock/isim.lock /usr/local/bin/betik.sh biçiminde yazdığınızda, önceki çalıştırma hâlâ sürüyorsa yeni kopya hemen çıkar. Bu, uzun süren yedekleme ve içe aktarma işlerinde diskin dolmasını, veritabanı kilitlerini ve iki kez gönderilen bildirimleri engeller. Kilit dosyasının yeniden başlatmada silinmeyen kalıcı bir dizinde olmasına dikkat edin.
Cron yerine systemd timer kullanmalı mıyım?#
Sunucu kapalıyken kaçan çalıştırmaların telafi edilmesi, çalışma sürelerinin ve çıkış kodlarının merkezî günlükte tutulması ya da kaynak sınırı konulması gerekiyorsa systemd zamanlayıcıları belirgin biçimde üstündür. Buna karşılık cron daha kısa, daha taşınabilir ve paylaşımlı barındırma dâhil hemen her yerde çalışır. Basit ve sık tekrarlayan işler için cron yeterlidir; kritik ve sıralı işler için zamanlayıcıya geçmek mantıklıdır.