Running a Python Script as a Systemd Service with a Timer in a Virtual Environment

Posted on August 06, 2025

Category: Technology

Tags: python, systemd, linux, scheduling

Views: 919

Running a Python Script as a Systemd Service with a Timer in a Virtual Environment

Running a standalone Python script at a specific time is a common requirement for tasks like data processing or API calls. Using systemd with a timer on a Linux server provides precise scheduling, dependency management, and robust logging. This guide shows how to set up a Python script to run in a virtual environment as a systemd service, scheduled to execute at an exact time (e.g., 9:30 AM daily or a one-time run), and addresses common issues like the status=217/USER error.

Prerequisites

Step 1: Set Up the Python Script and Virtual Environment

Create a directory for your script and virtual environment, ensuring the user (e.g., ubuntu) has full access.

sudo mkdir -p /home/ubuntu/scripts
sudo chown ubuntu:ubuntu /home/ubuntu/scripts

Create a sample Python script:

vim /home/ubuntu/scripts/script.py
import datetime
import requests

print(f"Script ran at {datetime.datetime.now()}")
# Example task: Make an API call
response = requests.get("https://dog.ceo/api/breeds/list/all")
print(response.json())

Set up a virtual environment and install dependencies:

python3 -m venv /home/ubuntu/scripts/venv
source /home/ubuntu/scripts/venv/bin/activate
pip install requests
deactivate

Test the script manually as the intended user:

/home/ubuntu/scripts/venv/bin/python3 /home/ubuntu/scripts/script.py

If this fails, check for missing dependencies or permissions.

Step 2: Create the Systemd Service

Create a systemd service file to run the script as a one-time task.

sudo vim /etc/systemd/system/myscript.service
[Unit]
Description=Run standalone Python script in virtual environment
After=network.target

[Service]
Type=oneshot
ExecStart=/home/ubuntu/scripts/venv/bin/python /home/ubuntu/scripts/script.py
User=ubuntu
WorkingDirectory=/home/ubuntu/scripts
Environment="MYENVVAR1=1234"
Environment="MYENVVAR2=5678"
Restart=no

[Install]
WantedBy=multi-user.target

Step 3: Create the Systemd Timer

Create a timer to schedule the service at a specific time (e.g., 9:30 AM on weekdays).

sudo vim /etc/systemd/system/myscript.timer
[Unit]
Description=Run myscript.service at specific time

[Timer]
OnCalendar=Mon..Fri *-*-* 09:30:00
AccuracySec=1min
Persistent=true
Unit=myscript.service

[Install]
WantedBy=timers.target

Step 4: Set Permissions

Ensure the ubuntu user has access to the virtual environment and script:

sudo chown -R ubuntu:ubuntu /home/ubuntu/scripts
sudo chmod -R u+rx /home/ubuntu/scripts
sudo chmod +x /home/ubuntu/scripts/script.py

Verify permissions:

ls -ld /home/ubuntu/scripts
ls -ld /home/ubuntu/scripts/venv
ls -l /home/ubuntu/scripts/venv/bin/python
ls -l /home/ubuntu/scripts/script.py

Expected output:

drwxr-xr-x 3 ubuntu ubuntu 4096 Aug  5 19:38 /home/ubuntu/scripts
drwxr-xr-x 5 ubuntu ubuntu 4096 Aug  5 19:38 /home/ubuntu/scripts/venv
-rwxr-xr-x 1 ubuntu ubuntu 2320 Aug  5 19:38 /home/ubuntu/scripts/venv/bin/python
-rwxr-xr-x 1 ubuntu ubuntu  123 Aug  5 19:38 /home/ubuntu/scripts/script.py

Step 5: Enable and Start the Timer

Activate the timer:

sudo systemctl daemon-reload
sudo systemctl enable myscript.timer
sudo systemctl start myscript.timer

Step 6: Verify Execution

Check the timer status:

sudo systemctl status myscript.timer
systemctl list-timers

View logs after the script runs:

journalctl -u myscript.service -b

Troubleshooting the status=217/USER Error

If you encounter the error Main process exited, code=exited, status=217/USER, it means the User= specified in myscript.service (e.g., ubuntu) is invalid or inaccessible.

  1. Verify User:

    id ubuntu
    

    If the user doesn’t exist, create it:

    sudo adduser youruser
    

    Or temporarily use User=root in myscript.service, then revert to a non-root user.

  2. Check Paths:

    ls -l /home/ubuntu/scripts/venv/bin/python
    ls -l /home/ubuntu/scripts/script.py
    

    Fix paths in myscript.service if incorrect.

  3. Test Service:

    sudo systemctl start myscript.service
    journalctl -u myscript.service -b
    
  4. Test Script:

    su - ubuntu -c "/home/ubuntu/scripts/venv/bin/python /home/ubuntu/scripts/script.py"
    
  5. SELinux/AppArmor (if applicable):

    sudo setenforce 0
    sudo systemctl start myscript.service
    sudo setenforce 1
    
  6. Logs:

    journalctl -xe
    

References

Additional Notes

This setup ensures your Python script runs reliably at a specific time, leveraging systemd’s precision and logging, and is isolated in a virtual environment for dependency management. For further customization, share your script’s purpose or specific schedule requirements!

Disclaimer: This blog post was created with assistance from Grok 3, an AI developed by xAI, under my direct supervision and guidance to ensure accuracy and alignment with my vision for the content.