I wrote a service script in Python and I wanted it to run daily on Azure Web App Service. The catch? Same script will run differently when launched manually via SSH and when triggered with cron. Most notably, it will be missing all your environment variables.
So how do you know what actually is in the limited cron environment? You can find out with a simple script that runs with cron and dumps everything into a log file.
Skip to the end of the page to see the example of what environment I got. YMMV.
Install your debug cron
In your app Configuration → Stack settings → Startup command:
chmod +x $APP_PATH/setup.sh && $APP_PATH/setup.sh && HOME="/home/site" python3 app.py
The above assumes you have app.py
that will launch web server listening on port 8000. Azure checks that port and if it's not live, it will kill the container.
ERROR - Container my_app_container for site my-app has exited, failing site start ERROR - Container my_app_container didn't respond to HTTP pings on port: 8000. Failing site start. See container logs for debugging. INFO - Stopping site my-app because it failed during startup.
Here's the setup.sh
:
#!/bin/sh
PYTHON3=$(which python3)
cat <<EOF > "/etc/cron.d/cron_environment"
# Dump cron environment
* * * * * root cd ${PWD} && ${PYTHON3} -m bin.cron_environment >> /tmp/cron.log 2>&1
EOF
chmod 644 "/etc/cron.d/cron_environment"
service cron start
Create ./bin/cron_environment.py
module
Create the ./bin
folder and __init__.py
inside it to make this a module.
Create cron_environment.py
:
import os
import sys
import platform
import socket
from pathlib import Path
logfile = Path('/tmp/cron_environment.log')
with logfile.open('w') as f:
f.write(f"__file__ = {__file__}\n")
f.write(f"Path(__file__).resolve() = {Path(__file__).resolve()}\n")
f.write(f"Path(__file__).resolve().parent = {Path(__file__).resolve().parent}\n")
f.write(f"os.getcwd() = {os.getcwd()}\n")
f.write(f"__name__ = {__name__}\n")
f.write(f"__package__ = {__package__}\n")
f.write(f"sys.path = {sys.path}\n")
f.write(f"sys.executable = {sys.executable}\n")
f.write(f"socket.gethostname() = {socket.gethostname()}\n")
f.write(f"platform.system() = {platform.system()}\n")
f.write(f"platform.release() = {platform.release()}\n")
f.write(f"sys.version = {sys.version}\n")
f.write(f"len(os.environ) = {len(os.environ)}\n")
for k in sorted(os.environ):
f.write(f"os.environ[{k}]={os.environ[k]}\n")
Example
Here's my environment if you just want to have a rough idea what to expect without going through the trouble of setting up the above.
__file__ = /tmp/8de074b44776819/bin/cron_environment.py
Path(__file__).resolve() = /tmp/8de074b44776819/bin/cron_environment.py
Path(__file__).resolve().parent = /tmp/8de074b44776819/bin
os.getcwd() = /tmp/8de074b44776819
__name__ = __main__
__package__ = bin
sys.path = ['/tmp/8de074b44776819', '/opt/python/3.13.5/lib/python313.zip', '/opt/python/3.13.5/lib/python3.13', '/opt/python/3.13.5/lib/python3.13/lib-dynload', '/tmp/8de074b44776819/antenv/lib/python3.13/site-packages', '/opt/python/3.13.5/lib/python3.13/site-packages']
sys.executable = /tmp/8de074b44776819/antenv/bin/python3
socket.gethostname() = 697258d0de0a
platform.system() = Linux
platform.release() = 6.6.96.1-1.azl3
sys.version = 3.13.5 (main, Jul 8 2025, 13:54:32) [GCC 12.2.0]
len(os.environ) = 7
os.environ[HOME]=/root
os.environ[LC_CTYPE]=C.UTF-8
os.environ[LOGNAME]=root
os.environ[OLDPWD]=/root
os.environ[PATH]=/usr/bin:/bin
os.environ[PWD]=/tmp/8de074b44776819
os.environ[SHELL]=/bin/sh