WatchCron

Cron Every 10 Seconds: 4 Methods That Work

Cron every 10 seconds isn't something crontab supports natively. Write */10 * * * * and you'll get every 10 minutes, not seconds. People make this mistake constantly. The cron daemon's smallest unit is one minute, and no special syntax exists for sub-minute intervals. Running a task every 10 seconds means 8,640 executions per day, and at that frequency you're closer to building a daemon than scheduling a cron job.

Four methods get you there. Which one fits depends on whether you need a quick terminal test, a crontab workaround, or a production-grade setup. And whether 10-second polling is the right architecture at all.

Six sleep commands inside one cron minute

The brute-force approach: schedule six copies of the same command, each delayed by 10 seconds.

* * * * * /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 10 && /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 20 && /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 30 && /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 40 && /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 50 && /usr/bin/php /opt/scripts/poll-queue.php

Each line fires at second 0, then waits its designated offset before running. Result: your script executes at :00, :10, :20, :30, :40, and :50 of every minute.

Works everywhere cron works. No extra tooling, no config files. But six crontab lines per task gets ugly fast. We had a staging server with three sub-minute jobs using this pattern: 18 crontab lines, all nearly identical, and every edit required updating all six copies. One typo in line four and you get a 20-second gap followed by two back-to-back runs.

The bigger concern is overlap. If your script takes more than 10 seconds to finish, the next invocation starts before the previous one exits. Two processes become three, three become six. On a queue processor hitting a database, that's connection exhaustion waiting to happen. Protect against it with flock:

* * * * * /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 10 && /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 20 && /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 30 && /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 40 && /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php
* * * * * sleep 50 && /usr/bin/flock -n /tmp/poll-queue.lock /usr/bin/php /opt/scripts/poll-queue.php

flock -n grabs an exclusive lock on the file. If another instance already holds it, the new one exits immediately instead of stacking. The overhead is less than a microsecond per call.

Run a cron job every 10 seconds with a bash loop

Cleaner crontab, same result. One cron entry launches a script that handles the timing internally:

* * * * * /opt/scripts/run-every-10s.sh

The script:

#!/bin/bash
for i in 0 10 20 30 40 50; do
  sleep $i && /usr/bin/php /opt/scripts/poll-queue.php &
done
wait

Six iterations, each backgrounded with &. The wait at the end keeps the parent process alive until all children finish. Without it, cron considers the job complete as soon as the loop exits, and orphaned child processes lose their parent. They'll still run, but cleanup and signal handling get messy.

Note the boundary: the last child starts at second 50. If it finishes at second 53, the next minute's first child starts at second 0, only 7 seconds later. For most workloads this doesn't matter. If it does, start the loop at 10 instead of 0 and let cron's own :00 invocation serve as the first run.

For three sub-minute jobs, you go from 18 crontab lines down to three. Worth the extra script file.

A subtlety to watch: if any child process runs longer than 60 seconds, the next minute's invocation overlaps. Add flock inside the loop or wrap the entire script in a lock file. We learned this after a cache-warming script that usually took 3 seconds occasionally spiked to 90 on cold starts.

systemd timer with OnUnitActiveSec=10s

If you're on any modern Linux distro (Ubuntu 16.04+, CentOS 7+, Debian 8+), systemd timers handle sub-minute scheduling without workarounds. More setup, but cleaner once running. On macOS, the equivalent is a launchd plist with StartInterval set to 10. The crontab sleep methods work identically on macOS and BSD.

Service unit at /etc/systemd/system/poll-queue.service:

[Unit]
Description=Poll queue processor

[Service]
Type=oneshot
ExecStart=/usr/bin/php /opt/scripts/poll-queue.php

Timer unit at /etc/systemd/system/poll-queue.timer:

[Unit]
Description=Run queue poll every 10 seconds

[Timer]
OnBootSec=10s
OnUnitActiveSec=10s
AccuracySec=1s

[Install]
WantedBy=timers.target

Enable and start:

$ sudo systemctl enable --now poll-queue.timer

Verify it's running:

$ systemctl list-timers --all | grep poll-queue
NEXT                         LEFT     LAST                         PASSED  UNIT               ACTIVATES
Fri 2026-10-03 14:22:18 UTC  8s left  Fri 2026-10-03 14:22:08 UTC  1s ago  poll-queue.timer   poll-queue.service

One thing easy to miss: AccuracySec=1s is not optional here. systemd batches timer events within a default window of 1 minute to save power (a leftover from laptop battery management). Skip AccuracySec and your "every 10 seconds" becomes "every 10 to 70 seconds." We've seen this bite people in staging where the systemd timer 10 seconds interval seemed fine, then drifted badly under load in production.

OnUnitActiveSec=10s means "10 seconds after the last run finishes." If your script takes 4 seconds, the next start is at second 14. For most polling tasks, that's actually better. No overlap, no flock, no lock files to clean up.

The watch command (quick testing only)

For ad-hoc tasks or debugging, watch does the job in one line:

$ watch -n 10 /opt/scripts/poll-queue.php

Runs the command every 10 seconds and shows the output in your terminal. Simple. But it needs an active terminal session, so for anything persistent you'd run it inside screen or tmux:

$ tmux new-session -d -s poller 'watch -n 10 /opt/scripts/poll-queue.php'

One quirk: watch waits for the command to finish before starting the next countdown, so a 3-second script on -n 10 actually runs every 13 seconds. If timing precision matters even for testing, use watch -n 10 --precise (GNU coreutils 8.24+) to compensate for execution time.

Not a production tool. If the tmux session dies, your polling stops and nobody gets notified. But for "I need to run a script every 10 seconds on Linux for an hour while debugging" it beats setting up crontab entries or systemd units.

Drift: the math that bites you later

Here's the thing nobody mentions upfront: sleep-based approaches drift. sleep 10 pauses for 10 seconds, but doesn't account for how long your script takes. If the script runs for 0.5 seconds, the actual interval is 10.5 seconds. Sounds trivial. Over an hour, 360 expected runs become about 342. Over 24 hours, cumulative drift adds up to roughly 72 minutes of total shift. In practice, that means about 400 fewer runs than expected, over an hour's worth of missed executions across the day.

systemd timers have a similar quirk with OnUnitActiveSec: the timer starts counting after the task completes, not when it started. A 4-second task on a 10-second timer produces 14-second intervals from start to start.

If precise 10-second intervals matter (metrics collection, sampling where timing accuracy affects data quality), neither sleep nor OnUnitActiveSec are the right fit. Use OnCalendar=*:*:0/10 in your systemd timer instead. That fires at fixed wall-clock seconds (:00, :10, :20...) regardless of how long the previous run took. The tradeoff: overlap is possible again, so you're back to needing flock or oneshot guards.

When 10-second polling is the wrong architecture

Asking a script to wake up, check for work, find nothing, and go back to sleep 8,640 times a day is wasteful if the work arrives unpredictably. Most of those runs return "nothing to do." Event-driven alternatives react to actual work instead of guessing.

Message queues (Redis, RabbitMQ, SQS) let your worker block until a message arrives. Zero wasted cycles. If you're polling a database for new rows, a queue is almost always the right move.

For file-based triggers, inotifywait watches a directory and fires when a file appears or changes. PostgreSQL has its own trick: LISTEN/NOTIFY lets your worker sit idle until the database sends a notification on INSERT. No polling, no delay.

And if your "cron job every 10 seconds" is really just a process that needs to stay alive, call it what it is: a long-running daemon managed by Supervisor, PM2, or a systemd service with Type=simple and Restart=always.

Honest assessment: if you need something to schedule a task every 10 seconds in production, and it isn't a throwaway script, the sleep-and-cron approach is a patch. It works, sometimes for years. But you're fighting a tool designed for minute-level scheduling, and at some point the hacks around overlap, drift, and monitoring become more complex than just writing a daemon. As the every 30 seconds guide covers, at sub-minute intervals you're essentially building a daemon whether you frame it as one or not.

Monitoring 8,640 daily runs

A cron job running every hour gives you 24 chances to notice a failure. At 10-second intervals, a broken PATH variable, a full disk, or a crashed lock file can stack up 360 missed runs in a single hour. If that job processes payment callbacks or health-check pings, the unfinished work compounds fast.

Cron logs (grep CRON /var/log/syslog) confirm the command was invoked, but not whether it succeeded. And at 8,640 entries per day per job, good luck reading them. A more practical approach: add a heartbeat to the end of your script. One HTTP request, a few bytes. As long as those pings arrive on schedule, everything is working. The moment they stop, you get an alert before the failures pile up.

For HTTP-based tasks (API syncs, webhook delivery), an external scheduler can handle both the triggering and the monitoring. WatchCron's Cloud Cron runs your URL on a schedule and alerts across 10+ channels if it fails. The minimum Cloud Cron interval is one minute, so true cron every 10 seconds still needs one of the local methods above. But for anything at one-minute granularity or slower, it saves you from maintaining scripts and lock files entirely.

Picking the right method

MethodBest forWatch out for
Six crontab lines + sleepOne-off jobs, no extra filesCrontab clutter, overlap without flock
Bash loop scriptMultiple sub-minute jobsOrphan processes if parent dies
systemd timerProduction on modern LinuxAccuracySec default, post-completion timing
watch commandQuick testing, debuggingNeeds active terminal/tmux

For a quick test, watch -n 10 wins. For production on any modern Linux box, systemd timers with OnUnitActiveSec are the cleanest option: overlap is avoided by design, and restarts are handled by systemd itself. The crontab approaches work until you have more than two sub-minute jobs. After that, every edit to the crontab feels like defusing a bomb.

If 10 seconds turns out to be overkill, the same sleep trick scales down: every 30 seconds needs only two crontab lines, and every minute is just * * * * * with no workarounds at all. For standard intervals, the cron expression examples library and the cheat sheet cover everything from 5 minutes to once an hour.

Monitor your cron jobs — even the fast ones

20 monitoring checks and Cloud Cron jobs on the free plan. No credit card.

free — no credit card required
Free plan · 20 checks forever Start Free

Frequently asked

No. Cron's smallest interval is one minute. To run a command every 10 seconds, use six crontab entries with staggered sleep delays, a bash loop script, or a systemd timer with OnUnitActiveSec=10s.

There is no crontab expression for seconds. Cron only supports minute-level granularity. The expression */10 * * * * runs every 10 minutes, not 10 seconds. Use sleep-based workarounds or systemd timers instead.

Use flock -n /tmp/yourscript.lock before the command to acquire an exclusive lock file. If a previous run is still active, the new one exits immediately. Alternatively, systemd timers with OnUnitActiveSec wait for the previous run to finish before starting the timer.

For production use on modern Linux, yes. Systemd timers support second-level precision, handle overlap prevention natively, provide built-in logging via journalctl, and don't require multiple crontab lines. Set AccuracySec=1s to avoid timer batching.

Yes. Sleep pauses for exactly 10 seconds but doesn't account for script execution time. A script taking 0.5 seconds produces 10.5-second intervals. Over 24 hours, cumulative drift can exceed one hour of lost runs.

When the task is long-running, event-driven, or needs precise timing. If your script polls a database or queue, a message queue consumer (Redis, RabbitMQ, SQS) or a long-running daemon managed by Supervisor or systemd is more efficient than 8,640 daily cron invocations.

Add a heartbeat ping at the end of each successful run. A monitoring service detects when pings stop arriving and sends alerts. At 8,640 runs per day, reading cron logs manually is impractical.