
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
sudosi 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”.
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.
Cateva programari pe care le vei folosi des:
| Programare | Inseamna |
|---|---|
*/5 * * * * | la fiecare 5 minute |
0 * * * * | in fiecare ora, la minutul 0 |
30 2 * * * | zilnic la 02:30 |
0 7 * * 1 | in fiecare luni la 07:00 |
0 4 1 * * | pe 1 ale fiecarei luni la 04:00 |
@reboot | o 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 -esi 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 derun-parts. Atat pe Debian, cat si pe Ubuntu,/etc/crontable ruleaza pe cele zilnice la 06:25, cu exceptia cazului in care e instalatanacron, 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”:
- Un mediu minimal. Pe Debian si Ubuntu, cron porneste comenzile cu
PATH=/usr/bin:/binsiSHELL=/bin/sh. Pe ambele distributii/bin/shedash, nubash, deci sintaxa specifica bash se strica..bashrc-ul tau, aliasurile, virtualenv-urile Python si tot ce e in/usr/local/binsau/usr/sbinlipsesc. - Caracterul
%inseamna “linie noua”. Intr-o comanda crontab, un%fara escape termina comanda si transforma restul in input standard.date +%Ftrebuie scrisdate +\%F. - 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. - 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.
- 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.
- 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.
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,PATHsiMAILTOsunt variabile care se aplica doar joburilor din acest fisier. Ii dam jobului unPATHnormal si oprim mailul, pentru ca logam intr-un fisier.30 2 * * * rootinseamna zilnic la 02:30, ca root.flock -n /run/lock/site-backup.lockia 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>&1adauga 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=oneshotii 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 deflock.Nice=10siIOSchedulingClass=idlescad prioritatea pe CPU si pe disc, ca backup-ul sa nu-ti incetineasca site-ul.- Blocul de hardening e un sandbox gratuit:
ProtectSystem=strictface tot sistemul de fisiere read-only pentru acest job, cu exceptia cailor dinReadWritePaths,ProtectHome=read-onlylasa folderele home doar in citire,PrivateTmpii da un/tmpprivat, iarNoNewPrivilegesblocheaza 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:00e programarea, in formatulziua-saptamanii an-luna-zi ora:minut:secunda.*-*-* 02:30:00inseamna 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 cadailysiweekly. Poti adauga chiar si un fus orar la final, de exemplu*-*-* 02:30:00 Europe/Bucharest, independent de setarea serverului.Persistent=trueil 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=15minadauga 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, adaugaFixedRandomDelay=true(suportat de systemd 255 pe Ubuntu 24.04 si de systemd 257 pe Debian 13).AccuracySec=1minii permite lui systemd sa grupeze trezirile intr-o fereastra de un minut, ca sa economiseasca resurse. Un minut e valoarea implicita; seteaz-o pe1sdaca 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:
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:
| cron | timer systemd | |
|---|---|---|
| Efort de configurare | o linie | doua fisiere unit mici |
| Logare | redirectezi tu output-ul | jurnal, automat |
| Rulari ratate cat e oprit | pierdute (anacron acopera doar joburi zilnice) | Persistent=true recupereaza o data |
| Rulari suprapuse | ai nevoie de flock | prevenite implicit |
| Dispersarea incarcarii | sleep $RANDOM manual | RandomizedDelaySec |
| Prioritate, limite, sandbox | nu are | Nice, MemoryMax, ProtectSystem si altele |
| Dependente | nu are | After=, Requires= alte unitati |
| Precizie | un minut | pana la secunda |
| Portabilitate | orice Unix, containere fara systemd | doar 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
PATHin crontab sau in script si refa testul cuenv -ide la pasul 2. - Nicio linie
CMDin logul cron. Fisierul din/etc/cron.dare un punct in nume, ii lipseste campul de utilizator, poate fi scris de grup sau de altii, sau nu e al lui root. Repara cusudo chown root:rootsisudo chmod 644. No MTA installed, discarding outputin jurnal. Jobul a afisat ceva si s-a pierdut. Adauga>> /cale/catre/log 2>&1sau treci pe timer.- Nume de fisiere sau date taiate. Un
%fara escape in linia crontab. Scrie\%sau muta comanda intr-un script. Permission deniedla 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, apoisudo systemctl restart cron, pentru ca cron citeste fusul orar la pornire. Timer-ele preiau schimbarea singure. - Timer-ul arata
n/alaNEXT. Nu e activ. Ruleazasudo systemctl enable --now site-backup.timersi verificasystemctl 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 systemdin script.ProtectSystem=strictisi face treaba: adauga folderul in care trebuie sa scrii laReadWritePaths=. - 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
pssi in logul cron. Pune parolele intr-un fisier citibil doar de root (chmod 600) pe care il citeste scriptul, sau folosesteEnvironmentFile=in serviciu, cu un fisier accesibil doar lui root. - Nu rula ca root ce nu are nevoie de root. Intr-un serviciu, adauga
User=siGroup=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 rulacrontab. - 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.