A cron job that fails emails root, which goes nowhere on a modern server, so the standard arrangement is a job whose failures are invisible.
# /etc/systemd/system/reports.service
[Service]
Type=oneshot
ExecStart=/usr/bin/php /var/www/app/artisan reports:generate
User=www-data
# /etc/systemd/system/reports.timer
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true runs a missed job after a reboot, which cron cannot do at all. RandomizedDelaySec stops forty servers hitting the same API at exactly 02:00. The real gain is that output goes to the journal with a unit name attached, so journalctl -u reports answers what happened last night, and systemctl list-timers shows when everything next runs — two questions that a crontab genuinely cannot answer.