diff --git a/modules/cron/bin/cron.php b/modules/cron/bin/cron.php
new file mode 100755
index 0000000000000000000000000000000000000000..073c278cbea20179f238d99e5477a11acf568ce8
--- /dev/null
+++ b/modules/cron/bin/cron.php
@@ -0,0 +1,45 @@
+#!/usr/bin/env php
+<?php
+
+/*
+ * This script can be used to invoke cron jobs from cli.
+ * You most likely want to execute as the user running your webserver.
+ * Example:  su -s "/bin/sh" -c "php /var/simplesamlphp/modules/cron/bin/cron.php -t hourly" apache
+ */
+
+// This is the base directory of the SimpleSAMLphp installation
+$baseDir = dirname(dirname(dirname(dirname(__FILE__))));
+
+// Add library autoloader.
+require_once($baseDir . '/lib/_autoload.php');
+
+if (!SimpleSAML\Module::isModuleEnabled('cron')) {
+    echo("You need to enable the cron module before this script can be used.\n");
+    echo("You can enable it by running the following command:\n");
+    echo('  echo >"' . $baseDir . '/modules/cron/enable' . "\"\n");
+    exit(1);
+}
+
+$options = getopt("t:");
+if (function_exists('posix_getuid') && posix_getuid() === 0) {
+    echo "Running as root is discouraged. Some cron jobs will generate files that would have the wrong ownership.\n";
+    echo 'Suggested invocation: su -s "/bin/sh" -c "php /var/simplesamlphp/modules/cron/bin/cron.php -t hourly" apache';
+    exit(3);
+}
+
+if (!array_key_exists('t', $options)) {
+    echo "You must provide a tag (-t) option";
+    exit(2);
+}
+
+$tag = $options['t'];
+$cron = new SimpleSAML\Module\cron\Cron();
+if (!$cron->isValidTag($tag)) {
+    echo "Invalid tag option '$tag'.\n";
+    exit(2);
+}
+
+$cronInfo = $cron->runTag($tag);
+
+print_r($cronInfo);
+exit(0);
diff --git a/modules/cron/docs/cron.md b/modules/cron/docs/cron.md
index d75db3b8c83297992760adac349b2dd6753a6177..88630a29b3584d5e3947e3a882fa2b066596ec79 100644
--- a/modules/cron/docs/cron.md
+++ b/modules/cron/docs/cron.md
@@ -47,9 +47,17 @@ here is a random key available to no one but you. Additionally, make
 sure that you include here the appropriate tags - for example any tags
 that you previously told metarefresh to use in the `cron` directive.
 
-Triggering Cron via HTTP
+Triggering Cron
 ---------------------------
 
+You can trigger the cron hooks through HTTP or CLI.  The HTTP method
+is the original technique, and it is recommended if you don't need to
+trigger CPU or memory intensive cron hooks.  The CLI option is
+recommended if you need more control over memory, CPU limits and
+process priority.
+
+### With HTTP
+
 `cron` functionality can be invoked by making an HTTP request to the
 cron module.  Use your web browser to go to
 `https://YOUR_SERVER/simplesaml/module.php/cron/croninfo.php`. Make
@@ -80,3 +88,35 @@ follow the appropriate links to execute the cron jobs you want. The
 page will take a while loading, and eventually show a blank page.
 
 
+### With CLI
+
+You can invoke cron functionality by running
+`/var/simplesamlphp/modules/cron/bin/cron.php` and providing a tag
+with the `-t ` argument.
+
+It is strongly recommended that you run the cron cli script as the
+same user as the web server.  Several cron hooks created files and
+those files may have the wrong permissions if you run the job as root.
+
+**note:** Logging behavior in SSP when running from CLI varies by
+version. The latest version logs to PHP's error log and ignores any
+logging configuration from `config.php`
+
+Below is an example of invoking the script. It will:
+
+* Run a command as the `apache` user
+   * `-s` specifies `apache` user's shell, since the default is non-interactive
+* Override INI entries to increase memory and execution time.
+    * This allows for processing large metadata files in metarefresh
+* Run the `cron.php` script with the `hourly` tag
+* Use `nice` to lower the priority below that of web server processes
+
+```bash
+su -s "/bin/sh" \
+   -c "nice -n 10 \
+       php -d max_execution_time=120 -d memory_limit=600M \
+       /var/simplesamlphp/modules/cron/bin/cron.php -t hourly" \
+    apache
+    
+```
+
diff --git a/modules/cron/lib/Cron.php b/modules/cron/lib/Cron.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2532e75fa098ce6892c37287d64462bc366e56b
--- /dev/null
+++ b/modules/cron/lib/Cron.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace  SimpleSAML\Module\cron;
+
+/**
+ * Handles interactions with SSP's cron system/hooks.
+ */
+class Cron
+{
+    /**
+     * The configuration for the Cron module
+     * @var \SimpleSAML_Configuration
+     */
+    private $cronconfig;
+
+    /*
+     * @param \SimpleSAML_Configuration $cronconfig The cron configuration to use. If not specified defaults
+     * to `config/module_cron.php`
+     */
+    public function __construct(\SimpleSAML_Configuration $cronconfig = null)
+    {
+        if ($cronconfig == null) {
+            $cronconfig = \SimpleSAML_Configuration::getConfig('module_cron.php');
+        }
+        $this->cronconfig = $cronconfig;
+    }
+
+    /**
+     * Invoke the cron hook for the given tag
+     * @param $tag string The tag to use. Must be valid in the cronConfig
+     * @return array the tag, and summary information from the run.
+     * @throws Exception If an invalid tag specified
+     */
+    public function runTag($tag)
+    {
+
+        if (!$this->isValidTag($tag)) {
+            throw new \Exception("Invalid cron tag '$tag''");
+        }
+
+        $summary = array();
+        $croninfo = array(
+            'summary' => &$summary,
+            'tag' => $tag,
+        );
+
+        \SimpleSAML\Module::callHooks('cron', $croninfo);
+
+        foreach ($summary as $s) {
+            \SimpleSAML\Logger::debug('Cron - Summary: ' . $s);
+        }
+
+        return $croninfo;
+    }
+
+    public function isValidTag($tag)
+    {
+        if (!is_null($this->cronconfig->getValue('allowed_tags'))) {
+            return in_array($tag, $this->cronconfig->getArray('allowed_tags'));
+        }
+        return true;
+    }
+}
diff --git a/modules/cron/www/cron.php b/modules/cron/www/cron.php
index 79d0472ff08ece30cb8377cc4d7bff3ee94dcf74..4f22fc5df0b111403d9fd6e119264a8d083b04f2 100644
--- a/modules/cron/www/cron.php
+++ b/modules/cron/www/cron.php
@@ -9,27 +9,19 @@ if (!is_null($cronconfig->getValue('key'))) {
 		exit;
 	}
 }
-if (!is_null($cronconfig->getValue('allowed_tags'))) {
-	if (!in_array($_REQUEST['tag'], $cronconfig->getValue('allowed_tags'))) {
-		SimpleSAML\Logger::error('Cron - Illegal tag [' . $_REQUEST['tag'] . '].');
-		exit;
-	}
+
+$cron = new SimpleSAML\Module\cron\Cron();
+if (!$cron->isValidTag($_REQUEST['tag'])) {
+    SimpleSAML\Logger::error('Cron - Illegal tag [' . $_REQUEST['tag'] . '].');
+    exit;
 }
 
 
-$summary = array(); 
-$croninfo = array(
-	'summary' => &$summary,
-	'tag' => $_REQUEST['tag'],
-);
 $url = \SimpleSAML\Utils\HTTP::getSelfURL();
 $time = date(DATE_RFC822);
 
-SimpleSAML\Module::callHooks('cron', $croninfo);
-
-foreach ($summary AS $s) {
-	SimpleSAML\Logger::debug('Cron - Summary: ' . $s);
-}
+$croninfo = $cron->runTag($_REQUEST['tag']);
+$summary = $croninfo['summary'];
 
 if ($cronconfig->getValue('sendemail', TRUE) && count($summary) > 0) {