Beyond Cron: Using Systemd Path Units for Real-Time Linux Automation

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Stop Polling, Start Reacting

I used to rely on Cron to process incoming files, but it was a clumsy solution. Every sixty seconds, a script would wake up, scan a directory for new data, and go back to sleep. It worked, but it felt slow. If a file landed at 12:00:01, it sat idle for 59 seconds. Even worse, if the folder was empty, the script still fired, burning CPU cycles and cluttering logs for no reason.

Systemd Path units changed that. Instead of asking “Is there something new?” every minute, the system notifies you the moment an event occurs. On a standard Ubuntu 22.04 server with 4GB of RAM, switching from Cron to Path units dropped my baseline CPU usage during idle periods. Because Path units use the Linux kernel’s inotify subsystem, they consume less than 1MB of memory while waiting for activity. You don’t need to install extra tools like incron; it’s already built into your OS.

Quick Start: The 5-Minute Setup

To get a path trigger working, you need two specific files: a .path unit to monitor the target and a .service unit to execute your script. Let’s set up a monitor for a file named /tmp/trigger.txt.

1. Create the Service Unit

The service defines the action. Create /etc/systemd/system/task-runner.service and paste this configuration:

[Unit]
Description=Execute script on file change

[Service]
Type=oneshot
ExecStart=/usr/local/bin/my-script.sh

2. Create the Path Unit

This file tells Systemd what to watch. For the automatic link to work, it must share the same base name as your service. Create /etc/systemd/system/task-runner.path:

[Unit]
Description=Watch /tmp/trigger.txt for modifications

[Path]
PathModified=/tmp/trigger.txt

[Install]
WantedBy=multi-user.target

3. Activate the Watcher

Reload the system manager and enable the path unit. Remember: you start the path unit, not the service.

sudo systemctl daemon-reload
sudo systemctl enable --now task-runner.path

Now, as soon as you run echo "data" >> /tmp/trigger.txt, Systemd triggers the script instantly.

Choosing the Right Trigger

Systemd offers several ways to monitor file activity. Selecting the wrong one is a common pitfall for beginners. Here is how the different directives actually behave in a production environment:

  • PathExists: This fires if the file exists at all. It doesn’t care if the file is new or ten years old. If the file is present, the service runs.
  • PathExistsGlob: This works like the above but supports wildcards. Use this for patterns like /uploads/*.pdf.
  • PathChanged: This waits until a file is closed after being written to. It is the safest choice for file uploads. It ensures the writing process is finished before your script touches the data.
  • PathModified: This is more aggressive. It triggers on any write operation, even before the file is closed. Avoid this for large files to prevent your script from reading incomplete data.
  • DirectoryNotEmpty: Use this for “watch-folders.” It triggers the moment the first file appears in a previously empty directory.

I’ve found that PathChanged is almost always the best choice for data pipelines. It provides a natural buffer that prevents race conditions between the writer and your script.

Handling High-Frequency Events

What happens if a hundred files land in a directory at the exact same time? You might worry about Systemd spawning a hundred simultaneous processes and crashing your VPS. Fortunately, Systemd has built-in protection.

The One-at-a-Time Rule

If your .service is already running, Systemd will not start a second instance. It waits for the current task to finish. If new events occurred during that time, Systemd queues them and triggers the service exactly once more. This automatic batching prevents your server from being overwhelmed by rapid-fire file changes.

Monitoring Multiple Locations

You can watch several files within a single unit. I often use this to monitor multiple configuration files simultaneously:

[Path]
PathModified=/etc/myapp/config.json
PathModified=/etc/myapp/users.db
Unit=reload-app.service

By using the Unit= directive, you can point different path files to a single service, which keeps your automation organized.

Hard-Won Lessons from Production

After managing these units for years, I’ve identified a few specific strategies to avoid common headaches.

1. Debugging with Journalctl

If your script isn’t firing, check the logs for both the path and the service. You can watch the hand-off happen in real-time with this command:

journalctl -u task-runner.path -u task-runner.service -f

2. The Atomic Move Pattern

When moving files into a watched folder, never write to that folder directly. Instead, write to a temporary location like /tmp and use the mv command to slide it into the watched directory. Since mv is an atomic operation in Linux, it prevents the path unit from triggering on a half-written, corrupted file.

3. Permissions and Ownership

Systemd runs as root by default. If your script needs to modify files owned by a web server, specify the user in your .service file. This prevents “Permission Denied” errors that are often hard to spot in the logs.

[Service]
User=www-data
ExecStart=/var/www/scripts/process.sh

4. Breaking the Infinite Loop

Be careful: if your script modifies the very file the path unit is watching, you will trigger an infinite loop. I once accidentally generated 10GB of logs in an hour because a script updated a timestamp in its own trigger file. Always write your output or logs to a different directory to stay safe.

The Bottom Line

Systemd Path units are a cleaner, more modern alternative to legacy Cron polling. They react instantly, preserve CPU resources, and integrate perfectly with the rest of the Systemd ecosystem. Whether you are building an auto-backup tool or a complex data ingestion system, these units are a vital addition to any sysadmin’s toolkit.

Share: