WatchCron

How to Run a Cron Job Every 30 Seconds

Cron doesn't do seconds. The smallest unit in a cron expression is one minute, and no amount of syntax tricks changes that. If you write */30 * * * *, you get every 30 minutes, not every 30 seconds. The standard crontab has five fields, and none of them represent seconds.

But running a task every 30 seconds is a real need. Health checks against a local service, queue workers that need near-instant pickup, sensor reads, cache warming. People run into this a lot, and the workaround depends on your environment. Three approaches that actually work in production.

The sleep 30 trick (simplest approach)

Run the same command twice per minute: once immediately, once after a 30-second delay. Two crontab entries handle it:

* * * * * /usr/bin/php /opt/scripts/check-queue.php
* * * * * sleep 30 && /usr/bin/php /opt/scripts/check-queue.php

First line fires at second 0 of every minute. Second line also fires at second 0, but sleep 30 pauses it for half a minute before running the actual command. && means the script only executes if the sleep completes successfully (it always does, but it's good practice).

Result: your script runs at :00 and :30 of every minute. 2,880 executions per day.

A few things to watch:

  • Overlap. If your script takes more than 30 seconds, the next invocation starts while the previous one is still running. Protect against this with flock:
* * * * * /usr/bin/flock -n /tmp/check-queue.lock /usr/bin/php /opt/scripts/check-queue.php
* * * * * sleep 30 && /usr/bin/flock -n /tmp/check-queue.lock /usr/bin/php /opt/scripts/check-queue.php
  • Log noise. 2,880 cron log entries per day per job. We once had a staging server with four sub-minute jobs running this way. Syslog hit 2 GB in under a week. Redirect output to a dedicated file with >> and set up log rotation before you forget.
  • Exit code swallowing. If the script fails, cron won't retry. The next run in 30 seconds starts fresh regardless. Good or bad depending on your use case, but worth knowing.

A wrapper script with a loop

Instead of two crontab lines, you can run a single entry per minute that loops internally:

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

Where run-every-30s.sh looks like:

#!/bin/bash
/usr/bin/php /opt/scripts/check-queue.php &
sleep 30
/usr/bin/php /opt/scripts/check-queue.php &

Backgrounding the first execution with & lets the sleep start immediately. Whole thing finishes in about 30 seconds, well before the next minute's invocation. Keeps your crontab cleaner when you have multiple sub-minute jobs.

For tighter intervals like every 10 or 15 seconds, extend the pattern:

#!/bin/bash
for i in 0 30; do
  sleep $i && /usr/bin/php /opt/scripts/check-queue.php &
done

Same result, slightly more maintainable if you later change the interval.

systemd timers (no cron at all)

If you're on a system running systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+, basically anything modern), timers can handle sub-minute scheduling without the sleep workaround. More boilerplate to set up, but cleaner once it's running.

You need two files. First, a service unit at /etc/systemd/system/check-queue.service:

[Unit]
Description=Check queue processor

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

Then a timer unit at /etc/systemd/system/check-queue.timer that tells systemd how often to trigger it:

[Unit]
Description=Run queue check every 30 seconds

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

[Install]
WantedBy=timers.target

Enable and start:

sudo systemctl enable --now check-queue.timer

Check status:

systemctl list-timers --all | grep check-queue

Here's the important part: OnUnitActiveSec=30s triggers the service 30 seconds after the last run completes. Not 30 seconds after it started. So if your script takes 10 seconds, the actual interval between starts is 40 seconds. For most use cases that's fine. For long-running scripts it's actually better than cron's approach, because you never get overlapping runs.

One detail easy to miss: AccuracySec=1s. Without it, systemd batches timer events within a 1-minute window to save power (a holdover from laptop power management). Setting accuracy to 1 second disables that grouping. Skip it and your "every 30 seconds" might drift to every 45 or 60.

Which approach to pick

MethodProsCons
Two crontab lines + sleepWorks everywhere, zero setupMessy with many jobs, overlap risk
Wrapper scriptClean crontab, flexible intervalsAnother file to maintain
systemd timerNative sub-minute support, no overlapLinux only, more boilerplate

For one or two jobs, the sleep trick is fine. We've seen it run for years on production servers without issues. When you have five or more sub-minute jobs, systemd timers are cleaner. The wrapper script sits in between: pragmatic for three or four jobs where you don't want to touch systemd.

Do you actually need every 30 seconds?

Worth asking before you wire any of this up. A 30-second interval means 2,880 runs per day. Each one spawns a process, potentially opens a database connection, writes to a log. On a $5 VPS running a WordPress site with limited CPU, that overhead adds up faster than you'd expect.

Common cases where 30 seconds makes sense:

  • Queue processing where jobs need near-instant pickup (payment callbacks, webhook delivery)
  • Health checks against local services where a 1-minute gap is too long for your SLA
  • Sensor or metrics collection where sampling rate matters for accuracy

Cases where every minute or every 5 minutes is usually enough:

  • Cache invalidation (most caches handle TTL natively)
  • Email queue processing (30 seconds of delay is invisible to the recipient)
  • Log rotation, cleanup scripts, report generation

If a 1-minute gap is genuinely too long but a full daemon feels like overkill, 30 seconds is a reasonable middle ground. Once you start wanting every 5 or 10 seconds though, you're really building a daemon whether you call it one or not. At that point, a proper background process managed by systemd, Supervisor, or PM2 is both more honest about what you're doing and more reliable than layering hacks on top of cron.

Keeping track of 2,880 daily runs

A job that runs every hour and misses once — no big deal, the next run picks it up. At 30-second intervals the math is different: a broken path after a PHP update, a full disk at 3 AM, a stuck lock file from a crashed process — and within 10 minutes you've already skipped 20 runs. If that job processes a payment queue or checks service health, the unfinished work piles up fast.

Cron logs (grep CRON /var/log/syslog) tell you the command was called, but not whether it finished successfully. A simpler approach: add a heartbeat ping — a small HTTP request — to the end of your script. As long as those pings keep arriving on schedule, everything is working. The moment they stop, you get an alert before the unprocessed work stacks up.

If the job already calls an HTTP endpoint (API sync, webhook delivery, health probe), an external scheduler can handle both the triggering and the monitoring in one place. WatchCron's Cloud Cron works this way, with 10+ alert channels built in — though the minimum interval there is one minute, so true 30-second scheduling still needs one of the local methods above.

Related schedules

ScheduleMethodRuns per day
Every 10 secondsLoop script or systemd timer8,640
Every 15 secondsLoop script or systemd timer5,760
Every 30 secondssleep 30 trick or systemd timer2,880
Every minute* * * * *1,440
Every 5 minutes*/5 * * * *288
Every 10 minutes*/10 * * * *144
Every 30 minutes*/30 * * * *48
Every hour0 * * * *24

More patterns in the cron expression examples library. For syntax questions, the cron expression cheat sheet covers all five fields with examples.

Monitor your cron jobs — even the fast ones

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

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

Frequently asked

Not directly. Cron's smallest interval is one minute. To run a task every 30 seconds, use two crontab entries where the second one starts with sleep 30, a wrapper script that loops internally, or a systemd timer with OnUnitActiveSec=30s.

Add two lines to your crontab: one runs the command normally at second 0, and the other runs sleep 30 && /path/to/script.sh which waits 30 seconds before executing. Together they fire your script at :00 and :30 of every minute, giving you 2,880 runs per day.

Use flock to create a lock file. Wrap your command with /usr/bin/flock -n /tmp/your-job.lock /path/to/script.sh. The -n flag makes the new invocation exit immediately if the lock is already held, so two instances never run at the same time.

For sub-minute intervals, yes. systemd timers support seconds natively with OnUnitActiveSec=30s, and they wait for the previous run to finish before starting the next one, which prevents overlap. The trade-off is more setup: you need a separate service and timer unit file.

2,880 times. There are 1,440 minutes in a day, and with two executions per minute (at :00 and :30), that doubles to 2,880. Each run spawns a process, so make sure your server can handle the load.