Scheduling Jobs with Systemd

In a previous post, I discussed some of the different options available to you on most POSIX system to automate the running of jobs on schedule. Here is a brief recipe on how to actually use the modern/trendy and actually functionally beneficial “systemd” to do this as a normal (non-root).

In addition to the job file or script itself, you will need to create two files: a timer file and a service file (also referred to as timer unit and service unit).

  1. Let us start with creating the job file. In practice, this might be your backup, cleanup, indexing, etc. script Here, it is going to be a little less useful, but perhaps much more iconic. Create, “~/.local/bin/hello-world.sh”:

    # /bin/sh
    echo "Hello, world: $(date)" >> "$HOME/scheduledjob.txt"
  2. Now, create the service unit which will call the above script, in “~/.config/systemd/user/hello-world.timer”:

    [Unit]
    Description=A job to greet the world
    
    [Service]
    Type=simple
    ExecStart=/home/you/.local/bin/hello-world.sh
    
    [Install]
    WantedBy=default.target

    Important things to note:

    • The part to the executable must be absolute, with no variables!
    • “user” in the file path “~/.config/systemd/user/…” path above is literally “user”, not your user name.
  3. Create the timer unit which will actually schedule the service unit above in “~/.config/systemd/user/hello-world.timer”:

    [Unit]
    Description=Schedule a greeting every 1 minute
    RefuseManualStart=no        # Allow manual starts
    RefuseManualStop=no         # Allow manual stops
    
    [Timer]
    # Execute job if it missed a run due to machine being off
    Persistent=true
    # Run 120 seconds after boot for the first time
    OnBootSec=120
    # Run every 1 minute thereafter
    OnUnitActiveSec=60
    # File describing job to execute
    Unit=hello-world.service
    
    [Install]
    WantedBy=timers.target

    While this example uses “OnUnitActiveSec” to specify the schedule, “OnCalendar” offers you incredible control and flexibility in expressing the timing if you need it. For example:

    # run on the minute of every minute every hour of every day
    OnCalender=*-*-* *:*:00
    
    # run on the hour of every hour of every day
    OnCalender=*-*-* *:00:00
    
    # run every day
    OnCalender=*-*-* 00:00:00
    
    # run 11:12:13 of the first or fifth day of any month of the year 2012, but only if that day is a Thursday or Friday
    OnCalender=Thu,Fri 2012-*-1,5 11:12:13

    See here for more information.

  4. All our components are in place, but before we actually schedule the service, we want to make sure it works.

    First, we have to enable to service:

    $ systemctl --user enable hello-world.service

    which should result in something like:

    Created symlink /home/you/.config/systemd/user/default.target.wants/hello-world.service → /home/you/.config/systemd/user/hello-world.service.

    Then, we do a test run of the job:

    $ systemctl --user start hello-world.service

    And check to make sure all is as wanted/expected. If not, then debug the script and repeat, remembering to enable the service again if the problem was in the service file (as opposed to the script that the service file invokes).

    Otherwise …

  5. Actually schedule it!

        $ systemctl --user enable hello-world.timer
    $ systemctl --user start hello-world.timer
  6. Check/monitor the service:

    $ systemctl --user status hello-world
    $ systemctl --user list-unit-files
  7. View the logs:

    $ journalctl --user --unit hello-world.service
    $ journalctl --user --unit hello-world.timer

    Or, to see only log messages for the current boot:

    $ journalctl --user --unit hello-world.service --boot
    $ journalctl --user --unit hello-world.timer --boot
  8. If we want to manually run the backup:

    $ systemctl --user start hello-world.service
  9. Or manually stop it:

    $ systemctl --user stop hello-world.service
  10. Permanently stopping/disabling the timer and service:

    $ systemctl --user stop hello-world.timer
    $ systemctl --user disable hello-world.timer
    $ systemctl --user stop hello-world.service
    $ systemctl --user disable hello-world.service
    $ systemctl --user daemon-reload
    $ systemctl --user reset-failed
  11. You can see a practical example of this system in action driving a cloud-based backup system here.

  12. References:

Share