🔭 Want to get alerted when your Laravel scheduled tasks stop working?
Head over to Better Uptime start monitoring your jobs in 2 minutes
Task scheduling is a useful technique for automating various repetitive tasks based on a schedule. Such tasks or jobs may be mission critical in nature (such as backing up a database), or it may be as simple as send a weekly email to yourself or your customers. Manually running such jobs can get tedious real quick so a better solution is to automate and monitor them so they can run predictably in a timely fashion.
The traditional way to schedule tasks on applications deployed to Linux servers is through the cron utility. However, such system must be implemented separately from the application which can be quite limiting.
Laravel offers a holistic approach to task scheduling through its command scheduler which allows you to schedule tasks within the application itself. In this tutorial, we will explore how to create scheduled jobs in Laravel, and we will also implement a monitoring solution to help you to promptly notify you if a scheduled task fails or doesn't run as expected.
Head over to Better Uptime start monitoring your jobs in 2 minutes
Before proceeding with this article, ensure that you have access to a Linux machine with recent versions of Cron, PHP, and Composer installed.
You should also create a new Laravel project so that you may test the code snippets in this tutorial. Assuming PHP and Composer are installed, you can proceed to create a Laravel project using the following command:
Your new Laravel project should have the following structure:
For this tutorial, we only care about the Console directory because all
scheduled jobs in Laravel are defined in the app\Console\Kernel.php file.
Go ahead and open the file with the following command:
Notice the highlighted schedule() function above. It's where you can schedule
your tasks, and Laravel provides an example of how it is done within the
function:
This line is commented out by default, but you should uncomment it before going
forward. The $schedule variable is an instance of the Schedule class built
into Laravel. Its command() method will schedule an
Artisan command which
displays an inspiring quote in the console.
To view a list of all available Artisan commands, run the command below in the terminal from your project root:
You will observe the command list alongside a brief description of what they do:
After choosing a command, you must specify how often you wish to execute it. In
the default example, the hourly() method executes the inspire command every
hour. For demonstration purposes, change it to everyMinute() so you can see
the effect of the task scheduling much quicker.
After making the change, invoke the scheduler by executing the command below:
The schedule:run command will go through all scheduled tasks and determine if
Laravel should execute the task based on the current time. For example,
hourly() will schedule a task to be executed on the hour mark (xx:00) while
everyFiveMinutes() executes the job when the current time is xx:x0, xx:x5,
xx:10, etc. If no task is currently scheduled to be executed, the following
output will be printed to the terminal:
However, since we are using everyMinute() in our example, you should observe
that the task is executed and the following output is printed to the terminal:
Notice that a log message describing the executed command is printed, but the
result isn't displayed. That's because Laravel automatically ignores the output
of a scheduled task by forwarding it to /dev/null (see second line of above
output) since they are typically run in the background.
If you need to utilize the output of your scheduled task, you can place it in a
file for later inspection through the sendOutputTo() method:
After making the above change, invoke the scheduler again and view the contents
of the scheduler-output.log file that is subsequently created:
Note that the sendOutputTo() method overwrites the contents of its file
argument for each invocation. If this is not desired, you can use the
appendOutputTo() method that appends the command output to the end of the file
instead.
Run the scheduler a few times, and observe that the file is no longer being overwritten:
A problem with the schedule:run command is that it only invokes the scheduler
once which means that the scheduled tasks are executed just once. To ensure
tasks are run as scheduled, the scheduler needs to be constantly running and
this is achieved by using the schedule:work command instead as shown below:
This command will run in the foreground and invoke the schedule:run command
every minute until it is terminated by pressing Ctrl-C. It is useful for
testing your code during development so you don't have to manually invoke the
scheduler each time.
In a production environment, you should execute the scheduler in the background through a Cron Job as shown below:
Aside from Artisan commands, you can also schedule the execution of any system
command using the exec() method as follows:
The sendOutputTo() and appendOutputTo() methods discussed earlier can also
be used to capture the output of system commands.
If you need to schedule a function in Laravel, you can use the call() method
and pass in a closure that wraps the function as follows:
The scheduled job will run daily in this example and delete all inactive users.
In addition to scheduling closures, you may also schedule
invokable objects
with the call() method. These are PHP classes that contain an __invoke()
method. When a new instance of the class is fed to the call() method, its
__invoke() method will be executed according to the specified schedule.
Queued jobs are typically
long-running tasks that are processed in the background to avoid interfering
with the application's main processes. Such tasks can be executed on a defined
schedule by using the job() method on the scheduler.
For instance, you can use this feature to update the search index of your application once every hour as follows:
If you are using a third-party service (such as Amazon SQS) to handle your queued jobs, you also need to specify the name of the job and the service you are using so that the appropriate queue connection will be used to queue the job:
You can obtain a list of all scheduled tasks using the command below. It displays each scheduled task and the time of their next invocation:
When you have multiple tasks scheduled to run simultaneously, Laravel will
execute them one by one according to how they are defined in the
app/Console/Kernel.php file. However, if one task requires a long time to run,
it will delay all subsequent tasks. Therefore, you might want to run your
scheduled tasks in the background using the runInBackground() method, so that
multiple jobs can run simultaneously.
Note that runInBackground() can only be used for tasks scheduled with the
exec() or command() methods only.
So far, we've only seen the hourly() and everyMinute() methods in action,
but Laravel offers several other frequency options, which can be broadly
classified into a few different categories which are discussed below.
The hour-based options above will run their tasks at the first minute of the
hour, but the hourlyAt(n) method can be used to run a task at the nth minute
of the hour instead.
The weeklyOn() method allows you to specify a day (Sunday:0, Monday:1, ...
, Saturday:6) and a time, but you can also specify multiple days using an
array:
Constraint options are a special set of methods that defines additional constraints after you have specified the frequency. For example, you can first schedule a job to run weekly and then set constraints like this:
The days() method takes an array of integers as input, which allows you to
limit the task execution to specific days of the week.
Besides the day constraints, there are two additional time-based constraint
methods, between() and unlessBetween(), which defines a range of times that
a job is allowed to be executed.
A major benefit of using Laravel to schedule tasks is that you can use something
other than the current time to trigger the execution of the tasks. For example,
the when() method takes a callback function as its input, and the task will
only run if the function returns true:
The above example schedules a task for execution based on the weather. It only
executes on sunny days. The one below uses the skip() method to defines
condition for skipping a scheduled task.
Lastly, you may also schedule tasks based on the current application environment, which is helpful if you want to schedule some tasks only in production.
In the above example, the task will only run in staging and production environments.
The scheduling methods discussed so far should be enough for most scenarios, but Laravel offers some advanced techniques for creating more complex schedules.
cron() methodThe cron() method allows you to create schedules using raw Cron expressions.
For example:
This code will schedule the inspire Artisan command to run at 04:05 every
Sunday.
at() methodYou may have noticed that some of the frequency methods (such as mondays(),
quarterly(), and others) do not allow you to specify an exact time, which can
sometimes be limiting. This can be fixed through the at() method as shown
below:
timezone() methodIf you'd like to schedule a task in a different timezone, you can also chain a
timezone() method in your schedule like this:
Another useful feature that Laravel provides for task scheduling is task hooks. They let you execute some code before or after the task is executed, or if a certain condition is true (such as if the task failed or succeeded).
For example, you can log a message before and after a task is executed as shown below. Please refer to the linked article for more information on logging in Laravel. We assume you've already configured the logging system correctly for demonstration purposes.
If your scheduled job produces some output, you can access it in the after()
method through the $output variable with the Illuminate\Support\Stringable
type.
A scheduled tasks might succeed or fail, so Laravel also provides the
onSuccess() and onFailure() hooks for dealing with either outcome:
Just like the after() hook, you can access the output through
Stringable $output. Note that the onSuccess() and onFailure() hooks only
work for tasks scheduled with the command() or exec() methods because task
failure is detected through a non-zero exit code.
Finally, we have the ping family of hooks which are useful for notifying an
external service when a scheduled task has began, completed, or failed. Here are
the available methods and their signatures:
pingBefore($url)thenPing($url)pingBeforeIf($condition, $url)thenPingIf($condition, $url)pingOnSuccess($url)pingOnFailure($url)Now that we've covered several Laravel task scheduling essentials, let's look at some practical examples of how it can be useful in a real-life project. We'll automate some tasks for a class management app where teachers can record the student's grades for each class.
Start by cloning the project to your computer using the following command:
Next, change into the class-management-app directory:
Rename the .env.example file at the project root to .env:
Afterward, install the required dependencies with composer:
Generate a new APP_KEY by running the following Artisan command:
Before starting the development server, ensure all the required modules are
enabled in your php.ini file:
The location of your php.ini file can be found by running:
Finally, start the dev server:
Open your browser and go to http://127.0.0.1:8000. You should see the home page of the class management app. To save time, we've included some dummy data for the project.
You can then create new students:
Or update/delete students:
Let's begin by scheduling a common task that is required in almost all
production web applications: a daily database backup. Since the SQLite database
is being utilized for this project, backing it up only involves copying the
database.sqlite file to a specified location.
You can write a shell script to perform this task as shown below:
When this script is executed, it will backup the database by copying the
database.sqlite into the backup directory under a new name containing the
current date. It also scans for files older than seven days and deletes them to
prevent the backups directory from growing too large. This isn't much of a
backup, but it suffices to demonstrate the concept of automating such a process.
Go ahead and schedule this script to run once a day with the following code:
You can run the schedule:list command to see the next time it is scheduled to
run:
If you want to confirm that the script is working properly, you can change the
frequency to from daily() to everyMinute() and run the schedule:work
command so that the scheduler keeps running every minute:
After a minute or so, you should observe the generated backup directory as
well as the backup files:
A classic use case for task scheduling is automating the generation of reports for relevant business metrics. In this section, we will demonstrate how to automate such tasks by sending a weekly report of students' performance to an email address. In this example, the report will be in the same format as the application's homepage.
Before proceeding, you must configure Laravel's emailing functionality with Gmail SMTP (or any email provider of your choosing). Start by heading to Google My Account → Security, and enabling 2-Step Verification. Afterward, go to App passwords and create a unique password for your Laravel application. Under Select app, choose the Other (Custom name) option and type "Class Management App" in the text input.
Once the password is generated, copy it to your clipboard. Head back to your
text editor and open your .env file once again. Edit the MAIL_ section of
the file as shown below. Note that the MAIL_PASSWORD must be the unique
password you just created.
Next, head to your app/Console/Kernel.php file, and modify it as shown below.
Don't forget to replace the <your_email> placeholder with an actual email
address.
The WeeklyReport class has not been created yet, so create it by pasting the
code below in a new app/Mail/WeeklyReport.php file:
When the call() method is executed, Laravel will send the index page
(resources/views/index.blade.php) to the email of your choice, displaying
everyone in the classes and their grade for each subject.
You can temporarily change the schedule frequency to something more suitable for
testing such as everyMinute() for example, and wait for the task to execute.
You should receive the following email in your inbox:
A simple way to verify that your scheduled tasks are running as expected and
providing the correct result is by sending an email to yourself. Assuming your
mail credentials have been setup in the .env file (as discussed earlier), the
emailOutputTo method can be used to send the output of task to an email
address:
When you run the scheduler, you should receive an email with the output each
time the task is executed. If you want to receive an email only when something
goes wrong, you can use the emailOutputOnFailure() method instead.
Sending emails via Laravel in the manner described above is a suitable approach for monitoring scheduled tasks in toy projects, especially for non-critical tasks. However, it is unsuitable for production systems where need to guarantee that each task runs according to schedule. A more appropriate solution here is to use a dedicated monitoring service.
Better Uptime is an excellent monitoring and incident management platform that can monitor your entire infrastructure and alert you appropriately if something goes wrong. This section will discuss how to use Better Uptime to monitor the status of your scheduled Laravel tasks.
Go ahead and create a free Better Uptime account if you don't have one already. Once you are signed in, head to the Heartbeats section and create a new heartbeat.
Choose an appropriate name for your monitor and select how often you expect this job to be repeated. Then, in the On-call escalation section, select how you wish to be notified when the job fails to execute.
Once you are done, click Save Changes, and you should see this page:
The highlighted URL above is how Better Uptime can monitor your scheduled task. Every time a task executes, you should make a HEAD, GET, or POST request to this URL.
Head back to your app/Console/Kernel.php file and add the thenPing() hook
for the corresponding scheduled task as shown below:
The thenPing() method sends a request to the Better Uptime API once the task
has finished executing. Once Better Uptime starts receiving requests, the
monitor will be marked as "Up", which means the scheduled task is up and
running.
You can then simulate an incident by stopping the scheduler with Ctrl-C. If
Better Uptime does not receive a request within the time frame you just
configured, the monitor will be marked as "Down", which means an incident
occurred.
You will also receive an alert in the configured channels:
Note that you must create a separate heartbeat for each scheduled task so that they can be monitored independently.
In this tutorial, we investigated task scheduling in a Laravel application and discussed many of its essential features. We also demonstrated how to set it up in a typical web application, and how to monitor the status of your scheduled tasks so that you are promptly notified if something goes wrong.
If you wish to dig deeper into task scheduling in Laravel, you can read its official documentation for more information.
Thanks for reading, and happy scheduling!
We use cookies to authenticate users, improve the product user experience, and for personalized ads. Learn more.