The command works in your terminal and produces nothing at three in the morning. Cron is not broken. It runs your command in an environment that is almost nothing like your shell, and that difference is the whole answer.
See what happened
grep CRON /var/log/syslog | tail -20
If your job is not in there at all, cron never tried. If it is there, cron ran it and something else went wrong.
1. PATH is nearly empty
Your shell knows where php is. Cron does not - its PATH is usually just /usr/bin:/bin. Use the full path, always.
which php\n# /usr/bin/php8.3\n\n# in the crontab\n0 3 * * * /usr/bin/php8.3 /var/www/site/cron.php
2. The output goes nowhere
A job with no redirect sends its output to mail that is not configured, so errors vanish. Send it to a file and the next failure explains itself.
0 3 * * * /usr/bin/php8.3 /var/www/site/cron.php >> /var/log/site-cron.log 2>&1
3. The wrong user
A job in root's crontab writes files owned by root, which the web server then cannot change. Put it in the crontab of the user that owns the site.
crontab -u youruser -l
4. A percent sign
% means newline. A date format like +%Y-%m-%d silently becomes three lines and the command breaks. Escape it: \%.Test it the way cron will
env -i /bin/sh -c "/usr/bin/php8.3 /var/www/site/cron.php"
That runs it with an empty environment, which is what cron does. If it fails here, it will fail at three.