The Quiet Terminal

← Practical Tips

Getting a small script onto a Linux server properly

2026-02-20 · 3 min read

A script that runs fine on my laptop is one thing. A script that runs at 4 AM, on a server, with nobody watching, is a different animal — and my first attempt at moving one over was a small comedy of errors. It now runs every night without me. The difference between then and now is that I stopped improvising and wrote a checklist. Here it is, followed by the five mistakes that made the checklist necessary.

The checklist

1. Copy the script up. scp is "copy a file over SSH", nothing more:

scp tidy_downloads.py me@203.0.113.7:/home/me/scripts/

The path after the colon is where the file lands on the server.

2. Give it a venv on the server. Same idea as on your own machine — its own folder of packages, so the server's system Python stays untouched:

python3 -m venv /home/me/venvs/tidy
/home/me/venvs/tidy/bin/pip install requests

3. Make every path absolute. Cron does not start your script where you think it does (mistake three, below). My script builds its paths from Path(__file__).parent and from /home/me/..., never from wherever it happens to be running.

4. Schedule it with cron. Run crontab -e and add:

30 4 * * * /home/me/venvs/tidy/bin/python /home/me/scripts/tidy_downloads.py >> /home/me/logs/tidy.log 2>&1

Five fields: minute, hour, day of month, month, weekday — so this is 4:30 every morning. Two details that matter. The interpreter is the venv's python, not bare python. And the two redirects at the end append both output and errors to a log file; without them, cron mails everything to a local mailbox that nobody has opened since 1998.

5. Log from inside the script too. When it misbehaves at 4 AM, the log is the only witness:

import logging

logging.basicConfig(
    filename="/home/me/logs/tidy.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)
logging.info("started")

One trap: logging creates the log file but not the log folder. If /home/me/logs doesn't exist, the script dies on startup with a FileNotFoundError — which you will only notice promptly because of step 4's redirect.

The five mistakes from the first attempt

The grown-up answer to all of this is a systemd timer, and one day I'll sit down and learn it. But cron at 4:30 has gotten up every morning for two years without asking me a single question, and at my scale that kind of reliability is worth more than modernity.