I want a Python script to run from its virtualenv every day at 12:00 UTC. My machine is in a different timezone and I want the setup to be portable so I don't have to rewrite paths when I move the project. Right now I have a crontab entry trying CRON_TZ=Etc/UTC and a small bash wrapper that sets TZ and sources the venv, but it's not reliably running at the UTC noon time. My files currently use $PWD in the shell script and Path(__file__).parent in Python. Questions: 1) Can I pass timezone like "Etc/UTC" directly in crontab without a wrapper script? 2) If I need a shell wrapper, how do I handle portable paths in bash similar to Python's __file__? 3) Any other common cron pitfalls I should watch for?
4 Answers
If you need reliable timezone behavior and can use systemd, user-level systemd timers are often easier. You can run a service as a specific user (so it uses that user’s locale), place the app in a standard location, and the timer semantics are more explicit than cron. You can also have the systemd unit run a small wrapper that activates the venv and invokes your script. If you prefer to stick with cron, systemd isn’t required, but it’s a solid alternative for precise scheduling across timezones.
Don’t rely on $PWD in cron. Make the shell wrapper compute its own directory and use paths relative to the script. Example pattern: script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" then source "$script_dir/venv/bin/activate" and run python "$script_dir/sotd.py". That way you can move the folder and the wrapper still finds the venv and the script. Also consider using absolute paths for any external binaries in cron, or set PATH at the top of the wrapper. If you want cron jobs to get your usual environment, you can source the user’s .bashrc at the start of the wrapper, but it’s cleaner to explicitly export what you need.
Try using UTC instead of "Etc/UTC" — many systems have zoneinfo named UTC. Set CRON_TZ=UTC (or put TZ=UTC in the script) and make sure your cron implementation actually supports CRON_TZ in per-user crontabs. A quick check is to ls /usr/share/zoneinfo and confirm UTC exists. If the Python script shows UTC when you run the shell wrapper manually but cron still fires at the host local time, that often means the cron daemon ignores CRON_TZ or the crontab environment isn't what you expect. Workarounds: put TZ=UTC at the top of the crontab, set TZ inside the wrapper, or use systemd timers which handle timezone/locale more predictably.
I tried that — ls showed UTC (not Etc/UTC) and running the .sh manually makes the .py output UTC. But CRON_TZ=UTC still didn’t make the job run at UTC noon: it runs if I schedule it every minute, but not when I try to have it run during the current UTC hour only. So it looks like cron isn’t honoring CRON_TZ on my host.
Debugging tips and a fallback trick: log environment and times to a file (printenv, date -u, and os.environ in Python) so you see what cron actually gives your script. Remember crontab has a minimal environment and % is special in crontabs (escape percent signs). If you can’t get CRON_TZ honored, schedule the job more frequently (e.g. every 0 and 30 minutes) and in the wrapper check UTC time with TZ=UTC date '+%H%M' (note escaping in crontab) and exit unless it’s 1200 — this ensures you only run at UTC noon regardless of host timezone. Also consider symlinking a stable path into the system (or using the script-dir technique above) so paths remain portable.
I do this myself: source the user .bashrc at the top of the crontab-invoked script and then call the script. It makes the environment match interactive runs and can avoid surprises.