81 comments

[ 2.2 ms ] story [ 101 ms ] thread
I've been almost convinced by systemd (and have switched to using it), but God the syntax of those service files is so ugly ...
As a passionate systemd hater I would say I do not agree. Cron syntax is worse.
(comment deleted)
NixOS comes with systemd, so I've been using it as a first-class part of managing stuff. It's great, especially coming from macOS' launchd.

Which makes it nice to distribute a tool for NixOS so that it can lean into systemd instead of as some bolted-on afterthought.

Makes me wonder what you'd do if you were distributing a lifecycle-heavy tool for Linux users in general since systemd isn't ubiquitous.

I use a systemd timer to run a monthly scrub for my btrfs pool. Kinda cool how you can do increasingly useful things like skip the next scheduled event if the user initiates a scrub, do or don't accumulate tasks if you have a monthly task but the machine was offline for 6 months -- or fold them into a single task, etc.

Oh I love them quite a lot! I use them to run all of our backup jobs, easy to set up and have never had an issue.
Moved from cronie to systemd timers because they are resilient to system startup times. My backup strategy is to create a borg archive entry every day at a fixed time. With cronie the system needs to be running at the scheduled time, but systemd timer tolerates this and runs the service as soons as the system is available.

Btw this is my repo for the backup automation: https://github.com/gchamon/borg-automated-backups

I'm sorry, I tried Googling the word "tolates" but I can't find any definition that makes sense?

> runs the service as soons as the system is available.

cron has the @reboot option which I use for a few scripts and works great.

> With cronie the system needs to be running at the scheduled time, but systemd timer tolerates this and runs the service as soons as the system is available.

Cronie doesn't have a `@reboot` meta-trigger?

Snooze offers similar resilience to the system being offline: https://github.com/leahneukirchen/snooze

I find it very easy to reason about: a single process maps to a single recurring task.

It can track last execution into a file, yielding durable schedules when the host is offline.

Timers can work with arbitrary units (not just a similarly-named service unit) so they can be surprisingly flexible. I have a timer on my servers that starts a backup.target that fires off a full "restic backup","restic prune", "restic forget" backup cycle each morning with randomized start times and notifications. The actual restic-* units are Podman Quadlets so the whole setup runs agnosticaly of what's on the server, just as long as it has Podman and Systemd installed.

I will admit thought, timers are up there in terms of being the clunkiest systemd unit type to use on a regular basis. I get why they're split up into two files and require different start vs enable syntax's, but man sometimes I just want to create a file that runs a script and be done with it.

I feel like systemd units could need a layer of abstraction above them, so instead of editing the files manually, a tool would do it, some kind of declarative CLI or something. Probably not really a concern in the age of LLMs anymore, but it feels just slightly too tedious every time.
I do similar stuff for backups. What's bitten me sometimes was that I disabled the timers in order to do other stuff on the restic repo (e.g. cleaning locks) but didn't see that the triggered units were still running.
systemd is complex on first view, but after using it you didn't want to use anything else. It's handy to manage everything using systemctl
Some people see it differently. As in: after using it they don't want to use it. I hate systemd with passion.

But that's because I'm old because obviously systemd-* is the only right way and everyone else who see things differently is a pundit.

I hard disagree. Previously I could use what I knew about Linux commands to read logs and administer the systemm. Systemctl knowledge does not generalize.

I have done scheme all my life, which is why I prefer shepherd. Not only is it in a syntax that i can use elsewhere, I get completion in Emacs.

I still hate journald logging though
There are still some weird things/choices in its design I don't fully understand.

For example, why do unit definitions have to be actual files on disk? Then, all of these files are reloaded when the daemon reloads, not just changed ones. But, why couldn't there be an API letting me add units programmatically? (There kind of is but it's constrained/inflexible)

Or, why can't I declare multiple units in the same file? It's really designed around the filesystem instead of abstracting at a different level, which is a choice I don't think is smart. It's not like it follows the unix philosophy though.

As for the format used for unit definitions, I wish TOML had been around so they could have had something sensible...

it's so, but overall linux is not following linux philosophy in many other places, and do it far before systemd. Many tools even running under unix not doing so like gcc, x.org, etc.
I have a Canon printer, I actually can't trust that their print nozzle won't get jammed up after sitting idle for a while. So I had claude setup a systemd script to print a picture of my dog every week, I ensure it has enough CMYK spectrum to stress the printer. Its a nice surprise every monday as I sit on my desk to see a sudden picture pop up from the printer :)
I used to do something similar with an old Samsung ML-2010 back when I was in college the first time around.

I think it was software and not hardware, but for some reason when I had that printer hooked up to my computer and idle for more than a week, it would simply stop printing. I probably could have dug through logs and figured it out, but I instead set up a cron job to print a test page every Monday and Thursday. The test pages would just have something on the top that said something like LOL PRINTER WORKS.

This wasn't actually as wasteful as it sounds; I was taking a boatload of math courses and needed tons of scratch paper in order to do my problems. Since it was scratch paper and would eventually end up in the trash anyway, I would usually prioritize doing my problems on failed prints and/or test prints, and I would usually exhaust those and then use blank paper afterwards.

I've been using Linux for over 20 years, systemd for over 10.

Yet there's always something new to learn and actually consider as another useful tool.

I'm using Linux for about 30 years and apparently we were all wrong using cron for decades.
I've converted all my crons to systemd timers+services over the past year but cant help but think it's sort of.. less tangible than cron

Like imagine trying to explain systemd timers and services and unit files to a beginner.

> Like imagine trying to explain systemd timers and services and unit files to a beginner.

I think it's... easier? Like "systemd is the place where your system manages all the processes it needs to run. Part of those processes can be run on a schedule, or on a timer, and you define them using this simple text file".

cron is easier for easy stuff ("just run this every 10 minutes") but harder for hard stuff ("run it every 8 hours but with randomized offset so not all machines at once do it, but also if machine was down when it should run, run it immediately").

It is also easier to debug as every job gets its own log rather than trying to write to system mailer nobody had set up with the job errors

I haven't used systemd timers enough to disagree, but

> Ambiguous $PATH settings make cron script execution difficult to predict.

What makes you say that? You can set the PATH right in the crontab. Is that harder to "predict" than it being set in /etc/bashrc, ~/.bashrc, ~/.profile, ~/.bash_profile, /etc/systemd/…, or wherever else?

> You might feel cool knowing the scheduling grammar by heart

I've used Linux since 1994 and I don't know it by heart. But luckily it's pre-printed in the crontab as comments:

    # For more information see the manual pages of crontab(5) and cron(8)
    # 
    # m h  dom mon dow   command
You just put numbers aligned with the titles.

The rest of the complaints, sure. Next time I need a cronjob, I'll try it out.

The main nice thing about the environment in systemd is that it is standard and mostly a blank slate, whereas at least for me I was always getting bit by the fact that the environment in Crontab was completely different from say, the environment inherited by supervisord or sysvinit scripts. In systemd the actual unit that gets executed is the same regardless of what triggers it, so there is no gap.

That does require you to still know what the default environment is, but it is a mostly completely clean environment, without any influence from any shell.

I'd have to concur that I agree this is an advantage of systemd.

"You just put numbers aligned with the titles."

That is not a fair summarization of their point because that is not the grammar. There's commas, slashes, asterisks, combinations, and then if you want randomization you need to put it in the command itself because cron can't do it. (Some crons can, but it's not a general capability of cron.) Writing a non-trivial cron spec is not easy.

Having had to work on an application supposedly supporting cron expressions: the numbers are just the basic parts of the language.

When someone inputs something ridiculous like "5,3/4 4-8,11 1 4,5,6,9-11 */2" you get to enjoy the fun of reverse engineering what they meant (it's never what they actually wrote).

And that's before you get to all the extensions supported in some cron environments (but not all).

I find systemd timers a lot more manageable. Things like having control over whether or not long-running jobs are allowed to overlap and the ability to run tasks between start-finish rather than a fixed time window are major improvements for me. At some point my VPS went down because the backup job ran into some kind of symlink loop and cron just kept spawning more and more backup tasks even though none of them finished.

Having to re-write commands and scripts because CRON had its own special PATH was also a pain point, but the same can be true for some types of systemd timers. But: you can execute those timers manually if you want instead of updating the crontab to trigger in 30 seconds and simply waiting.

> I've used Linux since 1994

Same here.

We are now considered old and therefore irrelevant. The new generation uses timers and couldn't care less about cron that has served us just fine for decades.

I use cron and my general attitude towards LP and systemd is very similar to the attitude of LP and systemd to us.

> But luckily it's pre-printed in the crontab as comments

That's true, but most people don't know the numbered manual sections, so they get the docs for the cron table command not the cron table config file.

problem with vars is that they apply to any subsequent entry in the file so you need to take that into consideration; the nice thing about timers is that all settings are self contained and not affected by previous entries. The standard /10 and similar cron expressions also have thundering herd problem when on bunch of servers, tho some variants like in Jenkins use variant H/10 (H standing for hash) where the thing is randomly shifted in time to not hit same minute on same server/job

another benefit is having logs in one place for the job; cron's "send a mail when there is any amount of output text" is just annoying behaviour, but also only place to get the job output unless you redirect it somewhere. Also starting from timer vs just doing systemctl start job.service is the same so easier to debug

other than that the few improvements in how to specify run time have been pretty useful.

For example, setting timer as "persistent" will mean any run "lost" to machine powered off will just be ran next time after boot, so you can have job on your PC that is just "run backup at 2AM" and if you turn it off before that you get the backup done first thing in the morning

There is also both random, and fixed (depending on machine UUID) random delay so avoiding thundering herd problem with backups is also pretty convenient.

There is even option to wake a device for the job if necessary tho the problem of shutdown is left to the user. And picking whether to start counting to next timer from previous one or from the job's end.

What I would like also is to have job summary page ("hey this job was done X times but failed Y times") but that's probably better left to external tooling

> You can set the PATH right in the crontab. Is that harder to "predict" than it being set in /etc/bashrc, ~/.bashrc, ~/.profile, ~/.bash_profile, /etc/systemd/…, or wherever else?

There is* a common trap as the cron PATH is usually just /usr/bin:/bin so anything in /usr/local/bin, or in /sbin won't be there.

> There is* a common trap as the cron PATH is usually just /usr/bin:/bin so anything in /usr/local/bin, or in /sbin won't be there.

There will always be a default. Is systemd timer's default inherently correct for all users any more than cron's is?

I'm just playing devil's advocate here, but why not just change cron, then? Good for the goose is good for the gander?

> What makes you say that? You can set the PATH right in the crontab.

OK but I don't want to hardcode $PATH in the crontab just so I can test the cronjob. Barring the hardcode, $PATH is one thing when cron runs and another when you try out the command yourself. systemctl start foo.service starts the command inside with the same environment as when the timer fires so you know it'll work the same.

On the flip side, your cron job will run at the time you specify in the crontab. Your systemd timer, on the other hand, may fire at the specified time (and most of the time, it will), but it can also suddenly stop firing once it has fired on a February 29th and then never fire again, due to logic bugs in systemd, or it may or may not fire when you "restart" the timer unit, due to logic bugs in systemd (that's when it only has OnCalendar, so yes, definitely a bug).

> $PATH is one thing when cron runs and another when you try out the command yourself.

Why would that be different with systemd timers? If my ~/.bashrc adds /opt/foo/bin, that's also not part of the systemd timer's PATH, right?

But I guess you're saying the ability to trigger the systemd timer off-schedule is the difference? Yeah, it's annoying with cron to have to temporarily set the trigger two minutes into the future. :-P

Not sure adding that feature justifies a complete rewrite, but certainly a nice addition.

> due to logic bugs in systemd

Yeah my main gripe with systemd and other Lennartware is the extremely low implementation quality, not necessarily the ideas. Though the idea of killing tmux/screen on logout is downright criminal. And the fd passing nonsense[1] for system services is clearly just the idea of a child that found a tool and is misusing it.

[1] which is an awesome and underused feature (https://blog.habets.se/2025/10/The-strange-webserver-hot-pot...), but completely misapplied by systemd.

> But luckily it's pre-printed in the crontab

    man 5 crontab
Way easier.
> You can set the PATH right in the crontab.

Yes, but people don't. I've had to debug other's crontabs many times over the last umpteen years.

But how is that different with systemd timers?

Two environments that set the PATH differently won't have the same value set, either way.

Is this about "yes, but cron doesn't let me trigger through its environment, and systemd timers do"?

I wouldn't say that the PATH is ambiguous, but cron does have some problems with PATH:

- the default value is missing some values you would expect, like /use/local/bin and /usr/sbin for root.

- on some distributions (for example Arch Linux) the man page doesn't even say what the default path is, or recommend setting it.

- if you need to add something to the path for a single script, you either need to wrap it with a call to env, set it in a wrapper script, or set the path before the entry and reset it afterwards

- you can't use ~ or $HOME in the path, you have to write out the full absolute path. Which is particularly annoying for user crontabs.

Sure, it isn't too hard to work around those, but IMO systemd timers are a better experience, especially since the default uses the same path as all your other services.

> - the default value is missing some values you would expect, like /use/local/bin and /usr/sbin for root.

What do you mean by "you would expect", that doesn't also apply to systemd timers? /opt/foo/bin is not in the path. Would you expect that?

And if this is an objective problem, can we just change the cron default PATH?

> - on some distributions (for example Arch Linux) the man page doesn't even say what the default path is, or recommend setting it.

Send a PR. This doesn't seem like an inherent problem.

> - if you need to add something to the path for a single script, you either need to wrap it with a call to env, set it in a wrapper script, or set the path before the entry and reset it afterwards

Or on the line, right?

    * * * * * FOO=bar $HOME/bin/foo.sh
The line can get long, but is this really a problem?

> - you can't use ~ or $HOME in the path, you have to write out the full absolute path. Which is particularly annoying for user crontabs.

This is incorrect. You can definitely use $HOME in user crontabs.

I'm still not seeing something that warrants a rewrite. (except what you did not mention, which is the ability to run "trigger this now" as a missing feature)

> What do you mean by "you would expect"

I mean it should include things that are usually on the path, like /usr/local/bin. And for the root user it should include sbin.

I don't really expect /opt/foo/bin to be there, but if you want to have it available by default everywhere then you can just add it to systemd once, rather than having to remember to add it to crontab as well.

> Or on the line, right?

Oh you're right. I forgot cron evaluates the command with the shell (which has its own set of issues...)

> This is incorrect. You can definitely use $HOME in user crontabs.

You can in the command itself, because that is evaluated by a shell, but not when setting an environment variable. From the Ubuntu crontab(5) manpage:

> The value string is not parsed for environmental substitutions or replacement of variables or tilde(~) expansion

Maybe it depends on the cron implementation though. The cronie documentation doesn't say either way.

Is there a way yet to force-trigger a timer? There wasn't the last time I used them, which I found to be super annoying for testing them.
In decades of trying, I do not believe there was one time that I ever got a cron job to work properly in the first attempt. Systemd timers are a godsend.
I love systemd timers! I've slowly moved all of my ansible-deployed cron jobs to timers (now just an ansible copy!). The integration with journalctl, especially in a newer OS like Debian 13 where syslog is gone, is really nice. It's also really nice to be able to start the service manually for debug. Having a cron job that didn't work was an annoying exercise in copy/pasting or writing an extra shell script. Don't even get me started on the black hole of cron job stdout. I can monitor systemd services like I already do and get a notification on failure.

I've noticed more and more open source projects recommending timers as a deployment method and I think that's great!

This is actually something that I like in systemd.

I am dealing with mostly non systemd system: BSD, Alpine, termux On BSD anacron works well, but I do not why I am always running into problems with the cronie anacron implementation. And it is very hard to debug.

I would really like a simple modern cron/anacron alternative.

Cronicle looked cool but it is node.js, a bit heavy and being replace now by their new product called xyOps anyway.

I haven't always been the biggest fan of systemd in some regards, but I will say that I mostly agree with this sentiment. I've almost completely quit using cron, and now favor systemd timers for scheduled jobs - at the "system" level anyway. I might still embed Quartz for scheduling that's scoped to a particular application or something.

Why? It's one of those fuzzy and somewhat hard to explain things. The systemd approach just maps more cleanly to my mental model of "how things should work" I guess. And maybe some of it is that I did indeed experience plenty of " Ambiguous $PATH settings make cron script execution difficult to predict" in the past, although it's not just that.

I won't sit here and claim that systemd timers are necessarily better than cron in any universal / objective sense. But they've won me over, for what it's worth.

(comment deleted)
I believe one of the major distro lines (redhat or debian, I forget which) uses systemd-cron, where cron is just a thin wrapper around systemd. You get more power from writing the unit files directly, but if all you ever need is a simple cron job, you have the old interface still available.
I'm fully ready to drink the "just let systemd do all the things" kool-aid, but I would love to see some sort of introductory/tutorial info into some of the things it can do other than services - i.e. containers and timers. I know man pages exist, but it would be nice if there was more scannable intro out there.
> stdout and stderr output often ends up in a black hole

Ain't that the truth. Literally every crontab I've written for the last 10 years has had this in it:

2>&1 | logger -t cron-WHATEVER

...and that does a pretty good job of capturing anything that the script emits and making it easy to grep for in syslog the following morning.

But I'm still amazed at how many crontabs I run across that don't capture any output at all.

systemd is great all around. Don’t listen to the boomers complaining about it because their cheese was loved.
They should listen to other people talking about their knowledge about cheese, of the single one they know, the cheese slices from the supermarket