Terminal cu o linie crontab langa un ceas, ilustrand cron si systemd timers
Linux

systemd timer vs cron: programarea corecta a sarcinilor Linux

Orice server ajunge, mai devreme sau mai tarziu, sa faca lucruri singur: un backup noaptea, o curatenie de fisiere vechi, un raport luni dimineata, un cache incalzit inainte sa se trezeasca clientii. Pe Linux, raspunsul clasic e cron, un planificator mai batran decat multi dintre cei care il folosesc. Raspunsul modern sunt timer-ele systemd, care ruleaza deja pe VPS-ul tau chiar acum, fie ca le-ai observat, fie ca nu. Amandoua functioneaza. Amandoua au si moduri tacute de a esua, iar cea mai rea sarcina programata e cea care s-a oprit discret acum trei saptamani si nimeni n-a observat pana in ziua in care a fost nevoie de backup.

In tutorialul asta luam un singur job realist, un backup nocturn pentru /etc si /var/www, si il configuram corect de doua ori. Intai ca job cron, cu logare, un mediu de rulare sanatos si protectie la rulari suprapuse. Apoi ca timer systemd, cu persistenta, intarziere aleatoare, sandbox si alerte la esec. La final vei intelege dilema systemd timer vs cron suficient de bine incat sa alegi unealta potrivita pentru fiecare job, in loc sa copiezi ce a folosit primul rezultat din cautare.

De ce ai nevoie

  • Un VPS cu Debian 13 sau Ubuntu 24.04 (un VPS DreamServer proaspat instalat e perfect), KVM sau LXC.
  • Un utilizator cu drepturi sudo si acces SSH.
  • Un editor de text in terminal. Noi folosim nano, dar merge orice.
  • Cam 30 de minute si putina familiaritate cu shell-ul (rulat comenzi, editat un fisier).

Totul se face ca utilizator obisnuit, cu sudo. Acolo unde Debian si Ubuntu se comporta diferit, spunem explicit.

Cron, systemd si timere pe intelesul tuturor

Cateva definitii la inceput, pentru ca restul articolului se sprijina pe ele.

Un daemon e un program care ruleaza permanent in fundal si asteapta sa aiba ceva de facut. cron e un daemon care se trezeste o data pe minut, citeste un set de tabele si porneste orice comanda a carei ora programata se potriveste cu minutul curent. Fiecare astfel de tabel se numeste crontab (cron table), iar fiecare linie din el e un job programat.

systemd e sistemul de init pe Debian si Ubuntu: primul proces pornit la boot (procesul cu ID-ul 1), care apoi porneste si supravegheaza tot restul. systemd descrie ce administreaza prin unitati (units), fisiere text mici in format INI. O unitate .service spune “ruleaza programul asta, ca utilizatorul asta, cu limitele astea”. O unitate .timer spune “porneste serviciul acela la orele astea”. Timer-ul e ceasul desteptator, serviciul e jobul.

Nu trebuie sa ne crezi pe cuvant ca timer-ele sunt deja folosite. Ruleaza:

systemctl list-timers --all --no-pager

Pe un Debian 13 sau Ubuntu 24.04 proaspat vei vedea intrari precum apt-daily.timer, apt-daily-upgrade.timer, logrotate.timer, man-db.timer, fstrim.timer si e2scrub_all.timer. Coloanele arata cand ruleaza fiecare data viitoare (NEXT), cand a rulat ultima data (LAST) si ce serviciu porneste (ACTIVATES). Distributia ta si-a mutat deja majoritatea sarcinilor de intretinere din cron in timere, ceea ce spune ceva despre directia in care merg lucrurile.

Diagrama de mai jos arata cum ajunge fiecare unealta de la “e ora 02:30” la “scriptul ruleaza”.

cron: o linie, mediu minimal, logarea o adaugi tulinie crontab30 2 * * *daemon cronverifica la minut/bin/shPATH=/usr/bin:/binscriptul tausite-backup.shoutputmail sau pierduttimer systemd: doua fisiere unit, logurile merg in jurnalunitate .timersite-backup.timersystemdPID 1, ultima rulareunitate .servicesite-backup.servicescriptul tauacelasi fisierjournaldjournalctl -uIncluse in timere: Persistent=true, RandomizedDelaySec, fara rulari suprapuse, sandboxLa cron le adaugi de mana: PATH, redirectare output, lock cu flock, sleep pentru dispersie
Acelasi script, doi planificatori: cron il ruleaza intr-un shell gol si ii arunca output-ul daca nu il redirectionezi, un timer systemd porneste un serviciu al carui output ajunge in jurnal.

Cum e construita o linie crontab

O linie crontab are cinci campuri de timp urmate de comanda. In fisierele de sistem (/etc/crontab si /etc/cron.d/*) mai exista un camp intre ele: utilizatorul sub care ruleaza comanda.

Anatomia unei linii din /etc/cron.d/site-backup302***rootflock ... site-backup.shminut0-59ora0-23zi1-31luna1-12zi sapt.0-7, 0=dumutilizatordoar cron.dcomandacai absolute, escape la %cand: zilnic la 02:30 (fusul orar al serverului)Operatori: * orice , lista (1,15) - interval (1-5) / pas (*/10)Crontab-urile de utilizator (crontab -e) nu au camp de utilizator: ruleaza ca proprietarul.
Cele cinci campuri de timp decid cand, campul de utilizator (doar in fisierele de sistem) decide cine, iar restul liniei e trimis shell-ului drept comanda.

Cateva programari pe care le vei folosi des:

ProgramareInseamna
*/5 * * * *la fiecare 5 minute
0 * * * *in fiecare ora, la minutul 0
30 2 * * *zilnic la 02:30
0 7 * * 1in fiecare luni la 07:00
0 4 1 * *pe 1 ale fiecarei luni la 04:00
@rebooto data, cand porneste cron dupa boot

Exista si scurtaturi precum @hourly, @daily si @weekly. O capcana clasica: daca completezi atat “ziua lunii”, cat si “ziua saptamanii”, cron ruleaza jobul cand se potriveste oricare dintre ele, nu cand se potrivesc amandoua. 0 4 1 * 1 inseamna “pe 1 ale lunii si, in plus, in fiecare luni”.

Unde stau joburile cron

  • Crontab-uri de utilizator, editate cu crontab -e si salvate in /var/spool/cron/crontabs/<utilizator>. Nu au camp de utilizator, jobul ruleaza ca tine.
  • /etc/crontab, tabelul de sistem. Are campul de utilizator. Mai bine il lasi in pace, pentru ca update-urile de pachete pot vrea sa-l modifice.
  • /etc/cron.d/, fisiere separate, in acelasi format ca /etc/crontab. E locul cel mai curat pentru joburi de sistem: un fisier per job, usor de sters.
  • /etc/cron.hourly, cron.daily, cron.weekly, cron.monthly, foldere cu scripturi simple rulate de run-parts. Atat pe Debian, cat si pe Ubuntu, /etc/crontab le ruleaza pe cele zilnice la 06:25, cu exceptia cazului in care e instalat anacron, care preia rularea si recupereaza zilele ratate.

O regula importanta de denumire pentru /etc/cron.d si folderele cron.* pe Debian si Ubuntu: numele de fisier pot contine doar litere, cifre, underscore si cratima. Un fisier numit backup.sh sau site.backup e ignorat in tacere. Multa lume se impiedica de asta.

In fine, verifica daca cron e instalat si porneste. Pe Debian si Ubuntu serviciul se numeste cron (pe sistemele de tip Red Hat se numeste crond):

systemctl status cron --no-pager

Vrei sa vezi active (running). Ubuntu 24.04 il aduce mereu. Unele imagini minimale de Debian 13 nu il au si primesti Unit cron.service could not be found. In cazul asta instaleaza-l:

sudo apt update && sudo apt install -y cron

Capcanele cron in care cade toata lumea

Inainte sa scriem jobul, iata motivele pentru care joburile cron “merg din terminal, dar nu din cron”:

  1. Un mediu minimal. Pe Debian si Ubuntu, cron porneste comenzile cu PATH=/usr/bin:/bin si SHELL=/bin/sh. Pe ambele distributii /bin/sh e dash, nu bash, deci sintaxa specifica bash se strica. .bashrc-ul tau, aliasurile, virtualenv-urile Python si tot ce e in /usr/local/bin sau /usr/sbin lipsesc.
  2. Caracterul % inseamna “linie noua”. Intr-o comanda crontab, un % fara escape termina comanda si transforma restul in input standard. date +%F trebuie scris date +\%F.
  3. Output-ul pleaca pe email. Orice afiseaza jobul e trimis pe mail proprietarului. Pe un VPS fara server de mail, cron logheaza (CRON) info (No MTA installed, discarding output), iar output-ul, inclusiv mesajele de eroare, dispare.
  4. Nimic nu previne suprapunerea. Daca un job programat la 5 minute dureaza uneori 7, ruleaza doua copii in acelasi timp si isi pot strica reciproc fisierele.
  5. Fusuri orare si ora de vara. cron foloseste fusul orar al serverului. In Romania ceasul se schimba intre 03:00 si 04:00 ora locala, asa ca un job la 03:30 e sarit intr-o noapte din martie si risca sa ruleze de doua ori intr-o noapte din octombrie. De aceea programam la 02:30.
  6. Rularile ratate sunt pur si simplu ratate. Daca serverul face reboot sau e oprit la 02:30, backup-ul din noaptea aia nu se mai face. cron nu recupereaza.

Prima parte a tutorialului dezamorseaza capcanele 1 pana la 5. La capcana 6 stralucesc timer-ele systemd.

Partea 1: un job cron pe care te poti baza

Iata toata procedura dintr-o privire. Pasii 1 pana la 4 construiesc varianta cron, pasii 5 pana la 8 transforma acelasi job intr-un timer systemd.

Partea 1: cron1Scrie scriptul2Testeaza in mediu cron3Adauga linia crontab4Verifica rulareaPartea 2: timer systemd5Creeaza .service6Creeaza .timer7Activeaza, scoate cron8Test, loguri, alerte
Cei 8 pasi ai tutorialului: pasii 1 pana la 4 construiesc un job cron de incredere, pasii 5 pana la 8 transforma acelasi job intr-un timer systemd.

Pasul 1: Scrie jobul ca script

Regula numarul unu a programarii sarcinilor: nu pune logica reala in linia crontab. Pune-o intr-un script cu nume clar, cai absolute si tratare corecta a erorilor. Programarea trebuie apoi doar sa apeleze un fisier, iar acelasi fisier poate fi refolosit mai tarziu de un timer systemd, fara modificari.

Creeaza folderul pentru backup si scriptul:

sudo mkdir -p /var/backups/site

sudo tee /usr/local/bin/site-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
# Nightly backup of /etc and /var/www into /var/backups/site
set -euo pipefail

DEST=/var/backups/site
KEEP_DAYS=14
SRC=()
for d in /etc /var/www; do
  if [ -d "$d" ]; then SRC+=("$d"); fi
done

mkdir -p "$DEST"
FILE="$DEST/site-$(date +%F_%H%M).tar.gz"

echo "$(date -Is) starting backup of: ${SRC[*]}"
tar -czf "$FILE" "${SRC[@]}"
find "$DEST" -name 'site-*.tar.gz' -mtime +"$KEEP_DAYS" -delete
echo "$(date -Is) done: $FILE ($(du -h "$FILE" | cut -f1))"
EOF

sudo chown root:root /usr/local/bin/site-backup.sh
sudo chmod 750 /usr/local/bin/site-backup.sh

Ce face: tee scrie in fisier textul dintre cele doua marcaje EOF (ghilimelele din 'EOF' impiedica shell-ul sa expandeze variabilele $ in timpul scrierii). In script, set -euo pipefail il opreste la prima comanda care esueaza, lucru important, pentru ca un backup esuat trebuie sa iasa cu eroare, nu sa pretinda ca totul e in regula. Include doar folderele care exista, scrie o arhiva cu data in nume, sterge arhivele mai vechi de 14 zile si afiseaza o linie de start si una de final, cu timestamp. Cele doua linii sunt logul nostru. Observa ca % e perfect in regula aici: regula de escape se aplica doar in linia crontab propriu-zisa.

Ultimele doua comenzi fac scriptul proprietatea lui root si nemodificabil de altcineva. Pentru ca va rula ca root, asta e o cerinta de securitate, nu un moft (detalii in sectiunea de securitate).

Pasul 2: Testeaza-l intr-un mediu ca al lui cron

Daca rulezi scriptul din shell-ul tau nu demonstrezi mare lucru, pentru ca shell-ul tau are un PATH bogat si un mediu complet. Simuleaza mai bine ce primeste de la cron: un mediu gol, cu doar cateva variabile.

sudo env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=/root LOGNAME=root \
  /bin/sh -c '/usr/local/bin/site-backup.sh'

env -i sterge toate variabilele, apoi setam doar ce seteaza si cron. Output-ul asteptat arata cam asa:

2026-11-10T10:15:02+02:00 starting backup of: /etc /var/www
tar: Removing leading `/' from member names
2026-11-10T10:15:04+02:00 done: /var/backups/site/site-2026-11-10_1015.tar.gz (1.3M)

Mesajul de la tar e un avertisment inofensiv pe stderr. Daca pe VPS-ul tau nu exista /var/www, apare doar /etc. Verifica arhiva:

ls -lh /var/backups/site

Daca testul pica cu command not found, scriptul depinde de ceva din afara /usr/bin:/bin. Fie folosesti calea completa (/usr/sbin/ceva), fie setezi PATH la inceputul scriptului. Repara aici, nu la 02:30 in productie.

Pasul 3: Adauga linia crontab cu logare

Pentru un job de sistem folosim un fisier in /etc/cron.d. Atentie la nume: fara puncte.

sudo tee /etc/cron.d/site-backup > /dev/null <<'EOF'
# Nightly backup, logic lives in /usr/local/bin/site-backup.sh
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# m  h  dom mon dow  user  command
30 2 * * * root flock -n /run/lock/site-backup.lock /usr/local/bin/site-backup.sh >> /var/log/site-backup.log 2>&1
EOF

sudo chmod 644 /etc/cron.d/site-backup

Pe rand:

  • SHELL, PATH si MAILTO sunt variabile care se aplica doar joburilor din acest fisier. Ii dam jobului un PATH normal si oprim mailul, pentru ca logam intr-un fisier.
  • 30 2 * * * root inseamna zilnic la 02:30, ca root.
  • flock -n /run/lock/site-backup.lock ia un fisier de lock inainte sa porneasca scriptul. Daca o rulare anterioara inca tine lock-ul, -n (non-blocking) face ca rularea noua sa iasa imediat, in loc sa se adune la coada. Asa dezamorsam capcana suprapunerii.
  • >> /var/log/site-backup.log 2>&1 adauga atat output-ul normal, cat si erorile intr-un fisier de log. Fara asta, tot ce afiseaza scriptul ar fi aruncat.

Fisierul trebuie sa se termine cu o linie noua (de asta se ocupa heredoc-ul), sa fie al lui root si sa nu poata fi scris de grup sau de altii, altfel cron il ignora. cron observa singur, in maximum un minut, fisierele noi sau modificate din /etc/cron.d, fara restart.

Un fisier de log care creste la nesfarsit e o problema in sine, asa ca lasa logrotate sa-l roteasca saptamanal (vine instalat implicit pe Ubuntu si pe instalarile standard de Debian, ruleaza sudo apt install -y logrotate daca lipseste):

sudo tee /etc/logrotate.d/site-backup > /dev/null <<'EOF'
/var/log/site-backup.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
}
EOF

Detaliu amuzant: pe ambele distributii, logrotate e pornit chiar de logrotate.timer, un timer systemd.

Joburi personale, fara root. Pentru un job care iti apartine, ruleaza crontab -e (prima data iti cere sa alegi un editor) si adauga linii ca acestea:

MAILTO=""
PATH=/usr/local/bin:/usr/bin:/bin
0 * * * * echo "$(date +\%F_\%H\%M) hourly check" >> "$HOME/cron-test.log" 2>&1

Aici vezi escape-ul pentru % in actiune: fiecare % din comanda e scris \%. Iti listezi crontab-ul cu crontab -l.

Dispersarea incarcarii cu cron. Daca multe servere sau joburi impart ora 02:30, poti adauga singur o pauza aleatoare. Pentru ca fisierul nostru seteaza SHELL=/bin/bash, $RANDOM e disponibil (nu exista in dash), si observa iar % cu escape:

30 2 * * * root sleep $((RANDOM \% 900)); flock -n /run/lock/site-backup.lock /usr/local/bin/site-backup.sh >> /var/log/site-backup.log 2>&1

Merge, dar e stangaci. Tine minte asta pentru pasul 6.

Pasul 4: Verifica daca cron l-a rulat

Sa astepti pana maine noapte ca sa afli nu e un plan. Schimba temporar programarea ca jobul sa ruleze la fiecare doua minute:

sudo nano /etc/cron.d/site-backup

Inlocuieste primele doua campuri 30 2 cu */2 *, salveaza (Ctrl+O, Enter) si iesi (Ctrl+X). Asteapta doua-trei minute, apoi uita-te in logul lui cron. Pe Debian 13, care nu instaleaza implicit un daemon syslog clasic, cron logheaza doar in jurnalul systemd:

sudo journalctl -u cron --since "10 min ago" --no-pager

Pe Ubuntu 24.04 merge aceeasi comanda, iar pentru ca rsyslog e instalat, poti folosi si grep CRON /var/log/syslog | tail. Ar trebui sa vezi o linie de genul:

CRON[23817]: (root) CMD (flock -n /run/lock/site-backup.lock /usr/local/bin/site-backup.sh >> /var/log/site-backup.log 2>&1)

Asta dovedeste ca cron a pornit jobul. Acum verifica ce a spus jobul insusi:

sudo tail -n 5 /var/log/site-backup.log

Ar trebui sa vezi liniile de “starting” si “done”. Dupa ce merge, pune programarea inapoi pe 30 2 * * *, altfel iti umpli discul cu cate un backup la fiecare doua minute.

Daca vezi linia CMD in jurnal, dar nimic in log, redirectarea e gresita. Daca nu vezi deloc linia CMD, cron nu ti-a citit fisierul: verifica numele fisierului, campul de utilizator si permisiunile (vezi depanarea de mai jos).

Partea 2: acelasi job ca timer systemd

Avem acum un job cron solid. Hai sa-l convertim si sa vedem ce castigam.

Pasul 5: Creeaza unitatea service

Unitatea service descrie ce ruleaza si cum. Nu stie nimic despre timp.

sudo tee /etc/systemd/system/site-backup.service > /dev/null <<'EOF'
[Unit]
Description=Nightly backup of /etc and /var/www

[Service]
Type=oneshot
ExecStart=/usr/local/bin/site-backup.sh
Nice=10
IOSchedulingClass=idle
# Hardening block: remove it if the service fails with 226/NAMESPACE
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/backups/site
PrivateTmp=yes
NoNewPrivileges=yes
EOF

Ce inseamna fiecare parte:

  • Type=oneshot ii spune lui systemd ca e o sarcina care ruleaza si se termina, nu un server de lunga durata. Unitatea e considerata “activa” cat timp ruleaza scriptul, iar systemd nu porneste o a doua copie cat timp prima inca ruleaza, deci nu mai e nevoie de flock.
  • Nice=10 si IOSchedulingClass=idle scad prioritatea pe CPU si pe disc, ca backup-ul sa nu-ti incetineasca site-ul.
  • Blocul de hardening e un sandbox gratuit: ProtectSystem=strict face tot sistemul de fisiere read-only pentru acest job, cu exceptia cailor din ReadWritePaths, ProtectHome=read-only lasa folderele home doar in citire, PrivateTmp ii da un /tmp privat, iar NoNewPrivileges blocheaza escaladarea de privilegii prin programe setuid. Daca un bug din script ar incerca vreodata sa stearga folderul gresit, pur si simplu nu ar putea.

Nu exista sectiune [Install]: nimeni nu activeaza direct serviciul, il porneste timer-ul. Retine si ca folderul din ReadWritePaths trebuie sa existe, motiv pentru care am creat /var/backups/site la pasul 1.

Intr-un container LXC neprivilegiat, unele gazde nu permit namespace-urile de mount de care au nevoie optiunile de sandbox, iar serviciul pica cu status=226/NAMESPACE. Pe un VPS KVM asta nu se intampla. Daca dai de eroare, sterge cele cinci linii de hardening si ruleaza sudo systemctl daemon-reload; restul functioneaza la fel si in LXC.

Pasul 6: Creeaza unitatea timer

Timer-ul descrie cand. Implicit porneste serviciul cu acelasi nume, deci site-backup.timer porneste site-backup.service.

sudo tee /etc/systemd/system/site-backup.timer > /dev/null <<'EOF'
[Unit]
Description=Run site-backup.service every night

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=15min
AccuracySec=1min

[Install]
WantedBy=timers.target
EOF

Cele patru setari din [Timer] sunt locul in care systemd ia avans fata de cron:

  • OnCalendar=*-*-* 02:30:00 e programarea, in formatul ziua-saptamanii an-luna-zi ora:minut:secunda. *-*-* 02:30:00 inseamna zilnic la 02:30. Alte exemple: Mon *-*-* 07:00:00 (luni la 07:00), *-*-01 04:00:00 (pe 1 ale lunii), *:0/5 (la fiecare 5 minute) sau scurtaturi ca daily si weekly. Poti adauga chiar si un fus orar la final, de exemplu *-*-* 02:30:00 Europe/Bucharest, independent de setarea serverului.
  • Persistent=true il face pe systemd sa tina minte cand a declansat ultima data timer-ul (in /var/lib/systemd/timers/). Daca serverul era oprit sau in reboot la 02:30, jobul ruleaza la scurt timp dupa urmatorul boot. Dupa o pana lunga ruleaza o singura data, nu cate o data pentru fiecare noapte ratata. Asta e solutia pentru capcana numarul 6 a lui cron.
  • RandomizedDelaySec=15min adauga la fiecare rulare o intarziere aleatoare intre 0 si 15 minute. Pe un singur server nu prea conteaza, dar cand multe masini sau multe joburi au aceeasi programare, le impiedica sa loveasca toate deodata discul, reteaua sau destinatia de backup. Daca preferi o intarziere aleatoare per masina, dar stabila de la o noapte la alta, adauga FixedRandomDelay=true (suportat de systemd 255 pe Ubuntu 24.04 si de systemd 257 pe Debian 13).
  • AccuracySec=1min ii permite lui systemd sa grupeze trezirile intr-o fereastra de un minut, ca sa economiseasca resurse. Un minut e valoarea implicita; seteaz-o pe 1s daca ai nevoie de precizie la secunda.

Inainte sa ai incredere intr-o expresie de calendar, cere-i lui systemd sa ti-o explice:

systemd-analyze calendar --iterations=3 "*-*-* 02:30:00"

Output-ul asteptat, aproximativ:

  Original form: *-*-* 02:30:00
Normalized form: *-*-* 02:30:00
    Next elapse: Wed 2026-11-11 02:30:00 EET
       (in UTC): Wed 2026-11-11 00:30:00 UTC
       From now: 16h left
   Iteration #2: Thu 2026-11-12 02:30:00 EET
   Iteration #3: Fri 2026-11-13 02:30:00 EET

cron nu a oferit niciodata un astfel de test. Iata de ce conteaza intarzierea aleatoare la scara mare, de exemplu pe o platforma de hosting unde multi clienti aleg aceeasi ora pentru backup:

60 de servere, acelasi job nocturn (aprox.)cron, fix la 02:30timer, 15 min aleator015304560joburi pornite601201301101201202:3002:3302:3602:3902:42fereastra de 3 minute dupa 02:30 (aprox.)
Distributie aproximativa: cu o ora fixa in cron, toate cele 60 de joburi pornesc in aceeasi fereastra, cu RandomizedDelaySec=15min se imprastie la aproximativ 12 pe fiecare fereastra de 3 minute.

Pasul 7: Activeaza timer-ul si scoate linia din cron

Spune-i lui systemd sa citeasca fisierele noi, verifica-le de greseli, apoi activeaza si porneste timer-ul:

sudo systemctl daemon-reload
sudo systemd-analyze verify /etc/systemd/system/site-backup.service /etc/systemd/system/site-backup.timer
sudo systemctl enable --now site-backup.timer

daemon-reload e necesar de fiecare data cand creezi sau editezi un fisier unit. systemd-analyze verify nu afiseaza nimic cand fisierele sunt in regula si iti arata exact linia problema in caz contrar. enable --now face doua lucruri: enable face ca timer-ul sa porneasca la fiecare boot (vei vedea o linie “Created symlink” catre timers.target.wants), iar --now il porneste imediat. Observa ca activam timer-ul, nu serviciul.

Confirma ca e programat:

systemctl list-timers site-backup.timer --no-pager
NEXT                        LEFT     LAST PASSED UNIT              ACTIVATES
Wed 2026-11-11 02:38:51 EET 16h left -    -      site-backup.timer site-backup.service

Ora din NEXT e undeva intre 02:30 si 02:45 din cauza intarzierii aleatoare. E normal.

Acum pasul pe care il uita toata lumea: sterge intrarea din cron, altfel backup-ul ruleaza de doua ori in fiecare noapte, iar copia din cron si cea din timer nu impart acelasi lock.

sudo rm /etc/cron.d/site-backup

Pentru ca timer-ul logheaza in jurnal, poti sterge si /etc/logrotate.d/site-backup si vechiul /var/log/site-backup.log cand nu mai ai nevoie de ele.

Pasul 8: Testeaza, citeste logurile si adauga alerte la esec

Ruleaza jobul chiar acum, prin systemd, exact cum l-ar rula timer-ul:

sudo systemctl start site-backup.service
systemctl status site-backup.service --no-pager

La un serviciu oneshot, start asteapta pana se termina scriptul. Statusul arata apoi inactive (dead) impreuna cu rezultatul ultimei rulari, de exemplu Main PID: 24051 (code=exited, status=0/SUCCESS) si o linie care spune ca unitatea s-a terminat cu succes. Inactiv e starea normala de repaus pentru un job oneshot.

Tot ce a afisat scriptul e acum in jurnal, cu timestamp, fara nicio redirectare:

journalctl -u site-backup.service -n 20 --no-pager

Un Ubuntu 24.04 sau Debian 13 proaspat pastreaza de obicei jurnalul pe disc (exista folderul /var/log/journal), deci logurile supravietuiesc reboot-urilor. Daca ls /var/log/journal spune ca folderul nu exista, creeaza-l cu sudo mkdir -p /var/log/journal si ruleaza sudo systemctl restart systemd-journald.

Logurile sunt utile, dar nimeni nu le citeste in fiecare dimineata. Hai sa-l punem pe systemd sa ne anunte cand pica backup-ul. Intai, o mica unitate sablon care reactioneaza la esecuri. @ din nume o face sablon: %i e inlocuit cu ce urmeaza dupa @.

sudo tee /etc/systemd/system/notify-failure@.service > /dev/null <<'EOF'
[Unit]
Description=Failure notice for %i

[Service]
Type=oneshot
ExecStart=/usr/bin/logger -p user.err -t systemd-failure "Unit %i failed on %H"
EOF

Apoi o atasam serviciului nostru printr-un drop-in, un mic fisier suplimentar care adauga setari unei unitati fara sa editezi originalul:

sudo mkdir -p /etc/systemd/system/site-backup.service.d
sudo tee /etc/systemd/system/site-backup.service.d/alert.conf > /dev/null <<'EOF'
[Unit]
OnFailure=notify-failure@%n.service
EOF
sudo systemctl daemon-reload

%n se expandeaza la numele complet al unitatii, deci cand pica site-backup.service, systemd porneste notify-failure@site-backup.service.service, care scrie o linie de eroare in jurnal. Testeaza notificatorul separat:

sudo systemctl start notify-failure@test.service
journalctl -t systemd-failure -n 5 --no-pager

Ar trebui sa vezi Unit test failed on <hostname-ul-tau>. Logarea e doar punctul de plecare: inlocuieste linia ExecStart cu un script care trimite un email, posteaza intr-un webhook de chat cu curl sau trimite un ping catre un monitor de tip push dintr-o unealta precum Uptime Kuma. Mecanismul ramane acelasi pentru orice serviciu la care il atasezi.

Bonus: alte trucuri cu timere care merita stiute

Timere monotone. Pe langa programarile dupa ceas, timer-ele pot rula relativ la evenimente: OnBootSec=5min ruleaza la 5 minute dupa boot, OnUnitActiveSec=1h ruleaza la o ora dupa ultima activare. Combinate, iti dau “o data pe ora, incepand la 5 minute dupa boot”, ceva ce cron nu poate exprima.

Sarcini unice cu systemd-run. Vrei ca ceva sa se intample o singura data, peste 10 minute, fara sa scrii fisiere?

sudo systemd-run --on-active=10min /usr/bin/touch /tmp/hello-from-systemd-run

Creeaza un timer si un serviciu temporare, vizibile in systemctl list-timers pana se declanseaza. Exista si --on-calendar= pentru o data si o ora anume.

Timere de utilizator. Utilizatorii obisnuiti pot avea propriile timere in ~/.config/systemd/user/, administrate cu systemctl --user. Ca sa ruleze si cand nu esti logat, activeaza o data lingering-ul: sudo loginctl enable-linger "$USER".

systemd timer vs cron: pe care il alegi

Iata comparatia cinstita, dupa ce am facut acelasi job in ambele feluri:

crontimer systemd
Efort de configurareo liniedoua fisiere unit mici
Logareredirectezi tu output-uljurnal, automat
Rulari ratate cat e opritpierdute (anacron acopera doar joburi zilnice)Persistent=true recupereaza o data
Rulari suprapuseai nevoie de flockprevenite implicit
Dispersarea incarcariisleep $RANDOM manualRandomizedDelaySec
Prioritate, limite, sandboxnu areNice, MemoryMax, ProtectSystem si altele
Dependentenu areAfter=, Requires= alte unitati
Precizieun minutpana la secunda
Portabilitateorice Unix, containere fara systemddoar sisteme cu systemd

Alege cron pentru sarcini personale rapide, pentru one-liner-e pe care vrei sa le vezi dintr-o privire in crontab -l, pentru sisteme sau containere fara systemd (majoritatea containerelor Docker nu ruleaza systemd in interior) si cand o unealta sau un coleg se asteapta la un crontab. O linie cron bine scrisa, cu cai absolute, logare si flock, e perfect respectabila.

Alege un timer systemd pentru orice te-ar supara sa pierzi: backup-uri, dump-uri de baze de date, reinnoiri de certificate, joburi de curatenie care nu au voie sa se suprapuna, joburi care trebuie sa recupereze dupa reboot-uri de mentenanta si orice ruleaza pe multe servere deodata. Fisierul in plus se amortizeaza prima data cand rulezi journalctl -u in loc sa ghicesti.

Depanare si greseli frecvente

  • Merge din terminal, nu merge din cron. Aproape mereu e de vina mediul. Foloseste cai absolute, seteaza PATH in crontab sau in script si refa testul cu env -i de la pasul 2.
  • Nicio linie CMD in logul cron. Fisierul din /etc/cron.d are un punct in nume, ii lipseste campul de utilizator, poate fi scris de grup sau de altii, sau nu e al lui root. Repara cu sudo chown root:root si sudo chmod 644.
  • No MTA installed, discarding output in jurnal. Jobul a afisat ceva si s-a pierdut. Adauga >> /cale/catre/log 2>&1 sau treci pe timer.
  • Nume de fisiere sau date taiate. Un % fara escape in linia crontab. Scrie \% sau muta comanda intr-un script.
  • Permission denied la pornirea jobului. Scriptul nu e executabil: sudo chmod 750 /usr/local/bin/site-backup.sh.
  • Jobul ruleaza la ora gresita. Verifica timedatectl. Ca sa schimbi fusul: sudo timedatectl set-timezone Europe/Bucharest, apoi sudo systemctl restart cron, pentru ca cron citeste fusul orar la pornire. Timer-ele preiau schimbarea singure.
  • Timer-ul arata n/a la NEXT. Nu e activ. Ruleaza sudo systemctl enable --now site-backup.timer si verifica systemctl status site-backup.timer.
  • Ai editat o unitate si nu s-a schimbat nimic. Ruleaza sudo systemctl daemon-reload.
  • status=226/NAMESPACE. Sandbox-ul nu e disponibil, tipic in unele containere LXC neprivilegiate. Scoate blocul de hardening de la pasul 5.
  • Erori Read-only file system din script. ProtectSystem=strict isi face treaba: adauga folderul in care trebuie sa scrii la ReadWritePaths=.
  • Backup-ul ruleaza de doua ori pe noapte. Vechea intrare cron inca exista langa timer. Sterge-o cum am aratat la pasul 7.

Note de securitate

  • Scripturile rulate ca root trebuie sa fie ale lui root si nemodificabile de altii. Daca un utilizator obisnuit poate edita un script pe care root il ruleaza in fiecare noapte, acel utilizator are practic root. Verifica cu ls -l /usr/local/bin/site-backup.sh.
  • Tine secretele departe de liniile crontab si de fisierele unit. Comenzile apar in ps si in logul cron. Pune parolele intr-un fisier citibil doar de root (chmod 600) pe care il citeste scriptul, sau foloseste EnvironmentFile= in serviciu, cu un fisier accesibil doar lui root.
  • Nu rula ca root ce nu are nevoie de root. Intr-un serviciu, adauga User= si Group= pentru joburi care ating doar fisierele unei aplicatii. In cron, pune astfel de joburi in crontab-ul utilizatorului respectiv.
  • Limiteaza cine poate folosi cron. Daca exista /etc/cron.allow, doar utilizatorii listati in el pot rula crontab.
  • Foloseste sandbox-ul gratuit. Blocul de hardening de la pasul 5 nu costa nimic si limiteaza paguba pe care o poate face un script cu bug sau compromis.
  • Un backup pe acelasi server nu e backup. Daca pierzi discul sau VPS-ul, pierzi si arhivele. Copiaza-le in alta locatie, ideal off-site, ca parte a aceluiasi job sau dintr-un al doilea timer.

Deci, cron sau systemd timers?

Amandoua, folosite cu cap. cron ramane o unealta buna pentru sarcini mici, vizibile si personale, atata timp cat ii respecti ciudateniile: cai absolute, % cu escape, logare explicita si un lock. Pentru tot ce conteaza pe un server, timer-ele systemd iti dau persistenta peste reboot-uri, intarzieri aleatoare, zero rulari suprapuse, un sandbox si loguri intr-un singur loc, la pretul unui fisier in plus. Acum ca ai construit acelasi job in ambele feluri, decizia systemd timer vs cron ar trebui sa-ti ia cateva secunde, nu o sesiune de cautari.

Poti exersa tot ce e in tutorialul asta pe un VPS din data center-ul nostru din Romania, gata in cateva minute cu Debian sau Ubuntu. Iar daca preferi ca backup-urile, joburile programate si alertele sa fie proiectate si urmarite de oameni care fac asta zilnic, echipa noastra de administrare servere se poate ocupa de tot.

Intrebari frecvente

Inlocuiesc timer-ele systemd complet cron-ul?

Pe un server Debian sau Ubuntu modern pot, iar distributiile le folosesc deja pentru propria intretinere. cron ramane util pentru joburi personale rapide, pentru portabilitate si in medii fara systemd, deci nu e nevoie sa-l dezinstalezi.

Cum rulez un timer systemd la fiecare 5 minute?

Foloseste OnCalendar=*:0/5 pentru fiecare al cincilea minut al ceasului, sau OnUnitActiveSec=5min impreuna cu OnBootSec=1min pentru “la 5 minute dupa rularea anterioara”. Verifica expresiile de calendar cu systemd-analyze calendar.

Cum vad toate sarcinile programate de pe un server?

Pentru cron: sudo ls /var/spool/cron/crontabs/, cat /etc/crontab si ls /etc/cron.d /etc/cron.daily. Pentru systemd: systemctl list-timers --all, plus systemctl --user list-timers pentru fiecare utilizator care are timere.

Functioneaza cron si timer-ele systemd in containere Docker?

De obicei nu cum ti-ai dori, pentru ca majoritatea containerelor nu ruleaza systemd sau un daemon cron. Programeaza munca pe host, de exemplu cu un timer care ruleaza docker exec sau docker compose run.

Ce face Persistent=true dupa o pana lunga?

Ruleaza jobul o singura data, la scurt timp dupa boot, indiferent cate rulari au fost ratate. Combinat cu RandomizedDelaySec, si rularea de recuperare e intarziata aleator, asa ca o flota de servere care revine deodata nu supraincarca nimic.

Avem Incredere & Suntem Membri

Suntem membri ai principalelor organizatii de infrastructura internet.

RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX