Scheduled Tasks in Joomla
Every website has chores that nobody wants to do by hand. Old log files pile up. Expired sessions clog the database. Articles stay locked because an editor closed the browser tab without saving. Someone really should check whether a new Joomla version is out. These jobs are boring, repetitive, and easy to forget, which is exactly why they should run on their own.
Joomla handles them with a quiet core component called Scheduled Tasks (com_scheduler). It is Joomla's built-in task scheduler: you define a job once, give it a timing rule, and Joomla runs it again and again without you lifting a finger. No third-party extension and no shell access required.
Set it once, and let Joomla do the chores you keep forgetting.
This article explains how the Scheduler really works. It covers the basics for owners and editors, the daily setup and timing rules for administrators, and the task plugins, database schema, locking model, and PHP API for developers. The goal is to help you trust the Scheduler enough to hand it your routine maintenance with confidence.
1. The Basics
1.1 What Are Scheduled Tasks?
The Scheduled Tasks component (com_scheduler) lets you run a piece of work automatically on a timing rule. Joomla introduced it in version 4.1 (early 2022), and it has shipped in core ever since. It replaces the old pattern of writing your own cron scripts for routine Joomla maintenance.
A few ideas make it special:
- Each task is one row in a single table:
#__scheduler_tasks. - A task does not contain code. It points to a task routine that a plugin provides. The plugin does the actual work.
- Timing is data, not code. You pick a rule ("every 6 hours", "every night at 03:00", or a full cron expression) and Joomla calculates the next run.
- Tasks can run from web traffic, from a real server cron, from the command line, or from a secure webcron URL. You choose.
In short: the component is the manager, the plugins are the workers, and the table is the to-do list.
1.2 Where Do I Find It?
In the Joomla 6 backend, these places are relevant:
System → Manage → Scheduled Tasks (the task list)
System → Manage → Scheduled Tasks → New (create a task)
System → Manage → Scheduled Tasks → Options (settings + ACL)
System → Manage → Plugins (filter "Task") (the task routines)
The component lives at administrator/components/com_scheduler/. The task routines live as plugins under plugins/task/. There is one more piece: a system plugin, System - Schedule Runner (plg_system_schedulerunner), which lets web visitors trigger due tasks in the background.
1.3 The Three Pieces
Scheduled Tasks is built from three parts that work together:
| Part | Role |
|---|---|
com_scheduler |
The component. Lists tasks, edits timing rules, shows the last result, and runs the dispatch engine. |
plg_task_* |
The task plugins. Each one advertises one or more routines and does the real work when its routine is called. |
plg_system_schedulerunner |
The system plugin that triggers due tasks from web traffic, the webcron URL, or the backend "Test Run" button. |
Disable the task plugin and its routine disappears from the "New Task" list. Disable the Schedule Runner and the web-based triggering stops, although a real server cron still works.
1.4 A Task vs a Routine
This distinction trips up a lot of people, so fix it early:
- A routine is a capability that a plugin offers, identified by a string such as
session.gcordelete.actionlogs. It is the same for every site. - A task is your configured instance of a routine: a title, a timing rule, some parameters, and a state. You can create several tasks from the same routine, for example two "Make a GET request" tasks pointing at two different URLs.
A routine is the recipe. A task is the meal you actually scheduled.Back to top
2. Creating Your First Task
2.1 The Steps
Open System → Manage → Scheduled Tasks and click New. Joomla shows you the list of available routines from every enabled task plugin. Pick one, for example Session GC, and you land on the task edit screen.
You fill in three things that matter:
- Title: a name you will recognise later, such as "Clean expired sessions".
- Schedule: the timing rule (covered in section 4).
- Parameters: routine-specific options, shown only if the routine needs them.
Save the task. Joomla immediately calculates the next_execution time and the task appears in the list, enabled and waiting.
2.2 Reading the Task List
The list view is your dashboard. For each task it shows:
| Column | Meaning |
|---|---|
| Status | Enabled, disabled, or trashed. |
| Task | Your title plus the routine type. |
| Last Run / Next Run | When it last ran and when it is due again. |
| Last Exit Code | The numeric result of the last run (0 means success). |
| Times Run | Total successful executions and failures. |
Each row has a Test Run icon. Click it and Joomla runs that one task right now, in the background, and refreshes the exit code. This is the fastest way to confirm a task works before you trust it to a schedule.
2.3 The Last Exit Code Is Your Friend
After a run, the last_exit_code column tells you what happened. A green 0 means the routine finished cleanly. Any other number points to a problem, and section 6 lists what each code means. Get into the habit of glancing at this column. A task that quietly fails every night is worse than no task at all, because you assume the chore is done.
3. The Core Task Types
Joomla 6 ships nine task plugins out of the box. Together they cover the most common maintenance chores. Here is what each one does and the routine id it provides.
| Plugin | Routine id | What it does |
|---|---|---|
| Check Files (Image Size) | checkfiles.imagesize |
Scans a folder for images larger than a set limit and resizes them. |
| Delete Action Logs | delete.actionlogs |
Purges User Action Log rows older than a chosen age. |
| Global Check-in | plg_task_globalcheckin_task_get |
Releases items left "checked out" (locked) by users. |
| Privacy Consent | privacy.consent |
Invalidates expired privacy consents and reminds users to renew. |
| Requests (GET request) | plg_task_requests_task_get |
Sends an HTTP GET to a URL you choose. Great for pinging webhooks. |
| Rotate Logs | rotation.logs |
Archives and trims Joomla log files so they do not grow forever. |
| Session GC | session.gc |
Garbage-collects expired user sessions from the session store. |
| Site Status | plg_task_toggle_offline |
Puts the site online or offline on a schedule (toggle, set online, set offline). |
| Update Notification | update.notification |
Emails super users when a new Joomla core version is available. |
Two of these deserve a special mention. Update Notification turns Joomla into something that watches its own version for you, which is one of the simplest security wins on any site. And Requests is the bridge to the outside world: schedule a GET to a webhook and you can trigger anything an external service exposes, from a Slack message to a cache warm-up on a CDN.
Back to top4. Scheduling Rules
4.1 The Rule Types
Every task stores its timing in the execution_rules field as JSON. On the edit screen you pick a rule type, and the form shows the matching inputs:
| Rule type | You set | Example |
|---|---|---|
interval-minutes |
A number of minutes | Every 15 minutes |
interval-hours |
A number of hours | Every 6 hours |
interval-days |
A number of days plus a time | Every 1 day at 03:00 |
interval-months |
A number of months, day, and time | Every 1 month on day 1 at 04:00 |
cron-expression |
A full cron pattern | 0 3 * * 1 (Mondays at 03:00) |
manual |
Nothing; no automatic run | Run only via Test Run or CLI |
The interval rules are easy and good enough for most chores. The cron expression gives you the full power of standard cron timing when you need precise control.
4.2 The Cron Expression
Behind the scenes Joomla uses a real cron parser, so the five-field syntax you may know from Linux works exactly as expected:
┌───── minute (0 - 59)
│ ┌──── hour (0 - 23)
│ │ ┌─── day of month (1 - 31)
│ │ │ ┌─ month (1 - 12)
│ │ │ │ ┌ day of week (0 - 6, Sunday = 0)
│ │ │ │ │
* * * * *
So */30 * * * * runs every 30 minutes, 0 2 * * * runs daily at 02:00, and 0 4 1 * * runs at 04:00 on the first of every month. When you save, Joomla converts your rule into the cron_rules field and works out the next run time from it.
4.3 Next Execution Is Calculated, Not Stored Forever
The next_execution column is Joomla's plan for when the task is due. After each successful run Joomla recalculates it from the rule. A manual task has no next time, so its next_execution is left empty and it never fires on its own. If a task ever looks "stuck in the past", that simply means its trigger has not fired since the due time arrived, which leads us to the next section.
5. How Tasks Actually Run
Defining a task does not make it run. Something has to wake the Scheduler up and ask "is anything due?". Joomla offers four ways to do that, and choosing the right one is the single most important decision you will make with this component.
5.1 The Lazy Scheduler (web traffic)
This is the default. When Lazy Scheduler is enabled in the Options, the Schedule Runner plugin injects a tiny background request into ordinary page loads. When a real visitor opens your site and a task is due, that visitor's page silently triggers the task through an AJAX call (onAjaxRunSchedulerLazy).
It is convenient and needs no server configuration, but it has a weakness: no traffic means no runs. A quiet site at 03:00 will not run its nightly task until the first visitor arrives in the morning. The lazy scheduler also has a minimum interval (default 300 seconds) so it does not fire on every single page view.
5.2 A Real Server Cron (recommended)
For reliable timing, let the operating system do the waking. Add one line to the server crontab that calls the Joomla CLI every few minutes:
*/5 * * * * /usr/bin/php /path/to/site/cli/joomla.php scheduler:run --all
Now the Scheduler is checked every five minutes regardless of visitors. This is the professional choice for any site where timing matters. See section 8 for the full command.
5.3 The Webcron URL
If you cannot add a system cron but your host (or an external service like a monitoring tool) can fetch a URL on a schedule, enable Web Cron in the Options. Joomla generates a secret key and builds a URL like this:
https://example.test/index.php?option=com_ajax&plugin=RunSchedulerWebcron
&group=system&format=json&hash=YOUR_SECRET_KEY
Whatever fetches that URL triggers the due tasks (onAjaxRunSchedulerWebcron). Add &id=5 to run only task id 5. The hash is what keeps strangers from triggering your tasks, so treat it like a password and never share it in public.
5.4 The Test Run Button
The Test Run icon in the backend list fires a single task on demand (onAjaxRunSchedulerTest). It ignores the schedule and is meant for checking a task right after you create it. Use it freely while setting up; do not rely on it for production timing.
5.5 Which Should You Use?
| Method | Needs | Best for |
|---|---|---|
| Lazy scheduler | Nothing, just traffic | Small sites, easy start |
| Server cron | Crontab access | Production, reliable timing |
| Webcron URL | An external URL fetcher | Shared hosting without cron |
| Test Run | Backend access | Setup and debugging only |
On a site you care about, set up a server cron and switch the lazy scheduler off. That removes the small overhead from your visitors' page loads and gives you timing you can trust.
Back to top6. Under the Hood
6.1 The Database Table
Almost everything lives in one table, #__scheduler_tasks. One row is one task. The columns that matter:
id auto-increment primary key
asset_id link to the ACL assets table
title your task name
type the routine id, e.g. session.gc
state 1 enabled, 0 disabled, -2 trashed
execution_rules JSON: the rule type and its values
cron_rules JSON: the parsed cron pattern
last_exit_code result of the most recent run
last_execution when it last ran
next_execution when it is due next (empty for manual)
times_executed successful run counter
times_failed failure counter
locked timestamp while the task is running (else empty)
priority execution priority
params JSON: routine-specific settings
note free-text note
ordering list order
Because the timing rules are JSON, the table stays simple and the same schema serves every kind of task.
6.2 The Locking Model
What stops the same task from running twice at once, for example when two visitors load a page at the same moment? A pseudo-lock. Before a task runs, Joomla writes the current time into its locked column. While locked holds a value, no other process will pick the task up. When the run finishes, Joomla clears the lock and writes the results.
If a task crashes mid-run and never clears its lock, the global timeout (default 300 seconds, set in the Options) saves you. Once the lock is older than the timeout, Joomla treats it as stale and lets the task run again. This is how a stuck task recovers on its own.
6.3 States and Exit Codes
A task's state is simple: 1 enabled, 0 disabled, -2 trashed. The result of a run is richer. Joomla defines a set of status constants in the Status class, and the most useful ones are:
| Code | Constant | Meaning |
|---|---|---|
| 0 | OK |
Ran successfully. |
| 1 | RUNNING |
Currently executing. |
| 2 | NO_LOCK |
Could not acquire the lock (already running). |
| 3 | NO_RUN |
The routine declined or failed to run. |
| 4 | NO_RELEASE |
Ran, but could not release the lock. |
| 5 | KNOCKOUT |
An unhandled error stopped the routine. |
| 123 | WILL_RESUME |
Paused; will continue on the next trigger. |
| 124 | TIMEOUT |
Exceeded the timeout. |
| 125 | NO_TASK |
No such task record. |
| 127 | NO_ROUTINE |
The routine (plugin) was not found. |
The two you will see most are 0 (good) and 127 (you disabled or removed the plugin that provides the routine).
6.4 The Dispatch Flow
When a trigger fires, the flow is always the same:
Trigger (web / cron / webcron)
↓
Scheduler picks the next due, unlocked task
↓
Acquire lock (write "locked" timestamp)
↓
Dispatch onExecuteTask event with the routine id
↓
The matching task plugin runs its routine
↓
Record exit code, recalc next_execution, release lock
The component never contains the work itself. It is purely a dispatcher that hands control to a plugin through the onExecuteTask event.
6.5 The Logs Table and Logs View
The tasks table is only half the story. Joomla keeps a separate run history in a second table, #__scheduler_logs, and one row is written there every time a task runs. Its columns are practical:
id auto-increment primary key
tasktype the routine id that ran
taskname the task title at run time
taskid the id of the task in #__scheduler_tasks
jobid a per-run job identifier
exitcode the status code from section 6.3
duration how long the run took, in seconds
lastdate when this run happened
nextdate the next run time after this one
You read this history in the backend through the Logs button in the Scheduled Tasks toolbar. The Logs view lists every run with its task name, exit code, and duration, and you can filter by exit code to see only the failures. A Clear Logs action empties the table when it grows too large; there is no automatic pruning, so on a busy site you clear it yourself or schedule a Database cleanup task to do it.
The duration column is the one developers watch. A task whose duration creeps up over time is usually processing more data than it used to, and it is the early warning that you should batch the work or raise the timeout before it starts hitting the TIMEOUT exit code.
6.6 Per-task Log File and Priority
Two smaller settings round out the picture. Each task has a logging fieldset where you can point the routine's output at a dedicated log_file, which keeps a noisy task's messages out of the main Joomla log. And the priority column lets a task signal its importance, which matters when several tasks fall due at the same moment and Joomla decides the order to run them in.
7. Writing Your Own Task Plugin
7.1 The Shape of a Task Plugin
A task plugin is an ordinary Joomla plugin in the task group. It does three things: it advertises its routines, it runs them, and it optionally adds form fields for its parameters. Joomla gives you a trait, TaskPluginTrait, that handles most of the plumbing.
The heart of the plugin is a TASKS_MAP constant that maps each routine id to a method and a language prefix:
protected const TASKS_MAP = [
'mycompany.cleanup' => [
'langConstPrefix' => 'PLG_TASK_MYCLEANUP',
'method' => 'doCleanup',
'form' => 'cleanupForm',
],
];
7.2 Subscribing to the Events
The plugin subscribes to the Scheduler's events. onTaskOptionsList makes the routine appear in the "New Task" list; onExecuteTask runs it:
public static function getSubscribedEvents(): array
{
return [
'onTaskOptionsList' => 'advertiseRoutines',
'onExecuteTask' => 'standardRoutineHandler',
'onContentPrepareForm' => 'enhanceTaskItemForm',
];
}
The first two handlers come straight from TaskPluginTrait. You only write the routine method itself.
7.3 The Routine Method
Your method receives the execute event, does the work, and returns one of the status codes from section 6:
protected function doCleanup(ExecuteTaskEvent $event): int
{
$params = $event->getArgument('params');
// ... do the actual work here ...
$this->logTask('Cleanup finished.');
return Status::OK; // 0 = success
}
Return Status::OK when all is well, or another constant to signal a problem. Joomla records the code, updates the counters, and recalculates the next run. That is the whole contract: subscribe, advertise, run, return a code.
7.4 The Key Classes
| Class | Role |
|---|---|
Scheduler |
Selects and runs due tasks; the dispatch engine. |
Task |
Wraps one task record; acquires and releases the lock, logs, holds the snapshot. |
ExecuteTaskEvent |
The event passed to your routine; carries the task id, routine id, and params. |
Status |
The exit-code constants. |
TaskPluginTrait |
The helper trait every core task plugin uses. |
If you ever want a worked example, the nine core plugins under plugins/task/ are short, readable, and follow exactly this pattern. sessiongc is the simplest place to start.
7.5 Long-running Work: Batching with WILL_RESUME
What about a job too big for one run, such as resizing ten thousand images or emailing a newsletter to every subscriber? Run it in one go and it will hit a PHP memory limit or a time limit, especially under the lazy scheduler or a webcron, where the trigger lives inside a normal web request.
Joomla solves this with a dedicated exit code, Status::WILL_RESUME (123). The idea is simple: do one small batch per run, then tell Joomla you are not finished yet.
- Process a manageable chunk (say 100 records), then return
Status::WILL_RESUME. - Joomla logs the run as a success and leaves the task due, so the next trigger calls your routine again for the next batch.
- When the final batch is done, return
Status::OKand the task settles back onto its normal schedule.
The shape of a resumable routine looks like this:
protected function doImport(ExecuteTaskEvent $event): int
{
$offset = (int) $this->getProgress(); // where the last batch stopped
$batch = $this->fetchRecords($offset, 100);
foreach ($batch as $record) {
$this->processOne($record);
}
if (\count($batch) < 100) {
$this->clearProgress();
return Status::OK; // last batch, we are done
}
$this->saveProgress($offset + 100);
return Status::WILL_RESUME; // more to do, run me again
}
One thing the framework does not do for you: it does not remember where you were. Each run is a fresh call, so your routine must persist its own progress (a cursor, an offset, or the ids already handled) between batches. Store it somewhere durable, such as the task parameters or your own table, and read it back at the start of the next run.
Keep each batch comfortably inside the global timeout from section 6.2, and keep the work idempotent so that a batch which runs twice (after a crash, for example) does no harm. None of the nine core task plugins need this pattern, but it is exactly how you would build a serious import, export, or bulk-mail task on top of the Scheduler.
Back to top8. Command Line and Automation
8.1 The Console Commands
Joomla's CLI application exposes two scheduler commands. Run them from the site root:
# List every task with its state and next run
php cli/joomla.php scheduler:list
# Run all currently due tasks
php cli/joomla.php scheduler:run --all
# Run one specific task by id
php cli/joomla.php scheduler:run --id 5
The --id option (short form -i) takes priority: if you pass it, --all is ignored. scheduler:list prints a clean table of id, title, type, state, and next run, which is handy for a quick health check over SSH.
8.2 The Production Setup
The cleanest automation pairs a system cron with the CLI command. One crontab line drives the whole Scheduler:
*/5 * * * * /usr/bin/php /path/to/site/cli/joomla.php scheduler:run --all >/dev/null 2>&1
This runs every five minutes, checks what is due, and runs it. Each task still keeps its own schedule; the cron only controls how often Joomla looks. A five-minute cron with tasks scheduled hourly or daily is the sweet spot for most sites.
8.3 Why CLI Beats the Lazy Scheduler
Running tasks from the command line has real advantages over visitor-triggered runs:
- Reliable timing: it fires even when nobody visits.
- No visitor overhead: page loads stay fast because they no longer carry the trigger.
- No PHP web timeout: long jobs are not cut off by the web server's request limit.
- Clear logs: cron output and exit codes are easy to capture and monitor.
If you have shell access, this is the way. If you do not, the webcron URL from section 5 is the next best thing.
Back to top9. Web Services and Headless
A common question: does the Scheduler have a REST API under /api, like articles or contacts do? In Joomla 6 the answer is no. The component does not register a Web Services route, so there is no v1/scheduler/tasks endpoint to create or list tasks over REST.
What it offers instead is the webcron endpoint, which is a triggering mechanism rather than a data API. It runs due tasks but does not let you read or edit task definitions:
curl "https://example.test/index.php?option=com_ajax&plugin=RunSchedulerWebcron&group=system&format=json&hash=<key>"
For everything else, treat the Scheduler as a server-side tool. If you need to manage tasks programmatically, use the PHP Scheduler and Task classes from section 7 inside your own code, or drive the CLI commands from a deployment script. For remote triggering on a timer, point an external monitoring service at the webcron URL.
10. Performance and SEO Impact
The Scheduler has no frontend output, so it does not appear in search results and has no metadata of its own. Its effect on SEO is indirect but real: a well-tended site ranks and performs better than a neglected one.
- Session GC and Rotate Logs keep the database and disk lean, which keeps response times low. Speed is a ranking signal.
- Update Notification nudges you to patch promptly, and a hacked or defaced site is an SEO disaster.
- Requests can warm a cache or ping a CDN after a deploy, so the first real visitor never meets a cold page.
One performance warning about the trigger model itself. The lazy scheduler adds a small background request to page loads. On a busy site this is measurable. Moving to a server cron removes that cost from every visitor at once, which is the most direct performance win this component offers.
Back to top11. Common Mistakes and Pitfalls
11.1 "My nightly task never runs"
Symptom: a task scheduled for 03:00 only runs hours later, or not at all.
Fix: you are relying on the lazy scheduler on a low-traffic site. No visitor at 03:00 means no trigger. Set up a real server cron (section 8) so timing no longer depends on traffic.
11.2 "Last exit code is 127"
Symptom: the task fails immediately with exit code 127.
Fix: NO_ROUTINE means Joomla cannot find the plugin that provides the routine. The task plugin is probably disabled or uninstalled. Re-enable it under System → Plugins, filtered by the Task group.
11.3 "The same task ran twice"
Symptom: a job appears to execute twice close together.
Fix: usually two triggers fired before the lock was written, or a server cron and the lazy scheduler are both active. Pick one trigger method and disable the lazy scheduler when you use a server cron.
11.4 "A task is stuck and will not run again"
Symptom: a task shows as running forever and never fires again.
Fix: the run crashed and left the locked column set. Wait for the timeout (default 300 seconds) to expire and Joomla clears the stale lock by itself. If it never recovers, lower the timeout or unlock the task from the list toolbar.
11.5 "I shared my webcron URL and tasks fire randomly"
Symptom: tasks run at odd times after the webcron URL leaked.
Fix: the hash in the URL is a secret. Regenerate the key in the Options to invalidate the old URL, then keep the new one private.
11.6 "The site went offline on its own"
Symptom: the site is unexpectedly in offline mode.
Fix: check for a Site Status task. Its whole job is to toggle the site online or offline, and a misconfigured schedule will do exactly that. Disable or retime the task.
Back to top12. Best Practices
If you remember only a few things from this article, remember these:
- On any site that matters, run the Scheduler from a real server cron and turn the lazy scheduler off.
- Always click Test Run after creating a task, and confirm the exit code is
0. - Keep the Update Notification task enabled. It is free early warning for security updates.
- Treat the webcron hash like a password. Regenerate it if it ever leaks.
- Watch the Last Exit Code column. A task that fails silently is worse than no task.
- Give long jobs room: keep the global timeout sensible and prefer the CLI, which is not bound by web request limits.
13. Quick Reference
COMPONENT System → Manage → Scheduled Tasks
NEW TASK Scheduled Tasks → New (pick a routine)
OPTIONS Scheduled Tasks → Options (timeout, lazy, webcron)
TASK PLUGINS System → Plugins (filter "Task")
RUNNER PLUGIN System - Schedule Runner (plg_system_schedulerunner)
MAIN TABLE #__scheduler_tasks
LOGS TABLE #__scheduler_logs (run history; Clear Logs to empty)
LOGS VIEW Scheduled Tasks → Logs (filter by exit code)
RULE TYPES interval-minutes/-hours/-days/-months, cron-expression, manual
TRIGGERS lazy scheduler, server cron, webcron URL, Test Run
CLI RUN php cli/joomla.php scheduler:run --all
CLI ONE php cli/joomla.php scheduler:run --id N
CLI LIST php cli/joomla.php scheduler:list
WEBCRON URL index.php?option=com_ajax&plugin=RunSchedulerWebcron
&group=system&format=json&hash=KEY
EXIT 0 OK (success)
EXIT 124 TIMEOUT EXIT 127 NO_ROUTINE (plugin missing)
STATE 1 enabled, 0 disabled, -2 trashed
LOCK "locked" timestamp; cleared after timeout (default 300s)
SINCE Joomla 4.1 (2022)
Back to top14. Summary
The Scheduled Tasks component is Joomla's built-in answer to "who is going to remember to do this every day?". You define a task once, pick a timing rule, choose how it gets triggered, and Joomla takes the chore off your hands.
For a few minutes of setup you get:
- Nine ready-made task routines covering logs, sessions, check-ins, image sizes, updates, and more.
- Flexible scheduling, from simple intervals to full cron expressions.
- Four trigger methods, so the Scheduler works with or without shell access.
- A safe locking model that prevents double runs and recovers stuck tasks on its own.
- A clean developer API for adding your own task routines in a single small plugin.
It is not magic. The lazy scheduler depends on traffic, a leaked webcron hash is a real risk, and a task that fails silently can lull you into a false sense of safety. But run from a proper server cron, with the exit codes watched and the Update Notification task switched on, the Scheduler quietly does the maintenance that keeps a Joomla site fast, tidy, and secure. If you are unsure whether your scheduled tasks are firing when they should, or you want a maintenance routine you can stop thinking about, it pays to have someone check the setup before a forgotten chore becomes a problem.
Back to top

Joomla specialist and Linux admin for fast, secure and scalable websites.


