diff --git a/modules/node.module b/modules/node.module
index e8fc08b49aec2c3e0fd8195349627d6ff8a6d79c..1fdd1e7e5ed7093ca950e3a30ce3170c998ecfd5 100644
--- a/modules/node.module
+++ b/modules/node.module
@@ -598,12 +598,14 @@ function node_search($op = 'search', $keys = null) {
 
     case 'reset':
       variable_del('node_cron_last');
+      variable_del('node_cron_last_nid');
       return;
 
     case 'status':
       $last = variable_get('node_cron_last', 0);
+      $last_nid = variable_get('node_cron_last_nid', 0);
       $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND moderate = 0'));
-      $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d)', $last, $last, $last));
+      $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d ) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d))', $last, $last_nid, $last, $last, $last));
       return array('remaining' => $remaining, 'total' => $total);
 
     case 'admin':
@@ -2068,29 +2070,41 @@ function node_page() {
   }
 }
 
+/**
+ * shutdown function to make sure we always mark the last node processed.
+ */
+function node_update_shutdown() {
+  global $last_change, $last_nid;
+
+  if ($last_change && $last_nid) {
+    variable_set('node_cron_last', $last_change);
+    variable_set('node_cron_last_nid', $last_nid);
+  }
+}
+
 /**
  * Implementation of hook_update_index().
  */
 function node_update_index() {
+  global $last_change, $last_nid;
+
+  register_shutdown_function('node_update_shutdown');
+
   $last = variable_get('node_cron_last', 0);
+  $last_nid = variable_get('node_cron_last_nid', 0);
   $limit = (int)variable_get('search_cron_limit', 100);
 
   // Store the maximum possible comments per thread (used for ranking by reply count)
   variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
   variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
 
-  $result = db_query_range('SELECT n.nid, c.last_comment_timestamp FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last, 0, $limit);
+  $result = db_query_range('SELECT GREATEST(c.last_comment_timestamp, n.changed, n.created) as last_change, n.nid FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d)) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC, n.nid ASC', $last, $last_nid, $last, $last, $last, 0, $limit);
 
   while ($node = db_fetch_object($result)) {
-    $last_comment = $node->last_comment_timestamp;
+    $last_change = $node->last_change;
+    $last_nid = $node->nid;
     $node = node_load($node->nid);
 
-    // We update this variable per node in case cron times out, or if the node
-    // cannot be indexed (PHP nodes which call drupal_goto, for example).
-    // In rare cases this can mean a node is only partially indexed, but the
-    // chances of this happening are very small.
-    variable_set('node_cron_last', max($last_comment, $node->changed, $node->created));
-
     // Get node output (filtered and with module-specific fields).
     if (node_hook($node, 'view')) {
       node_invoke($node, 'view', false, false);
diff --git a/modules/node/node.module b/modules/node/node.module
index e8fc08b49aec2c3e0fd8195349627d6ff8a6d79c..1fdd1e7e5ed7093ca950e3a30ce3170c998ecfd5 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -598,12 +598,14 @@ function node_search($op = 'search', $keys = null) {
 
     case 'reset':
       variable_del('node_cron_last');
+      variable_del('node_cron_last_nid');
       return;
 
     case 'status':
       $last = variable_get('node_cron_last', 0);
+      $last_nid = variable_get('node_cron_last_nid', 0);
       $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND moderate = 0'));
-      $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d)', $last, $last, $last));
+      $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d ) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d))', $last, $last_nid, $last, $last, $last));
       return array('remaining' => $remaining, 'total' => $total);
 
     case 'admin':
@@ -2068,29 +2070,41 @@ function node_page() {
   }
 }
 
+/**
+ * shutdown function to make sure we always mark the last node processed.
+ */
+function node_update_shutdown() {
+  global $last_change, $last_nid;
+
+  if ($last_change && $last_nid) {
+    variable_set('node_cron_last', $last_change);
+    variable_set('node_cron_last_nid', $last_nid);
+  }
+}
+
 /**
  * Implementation of hook_update_index().
  */
 function node_update_index() {
+  global $last_change, $last_nid;
+
+  register_shutdown_function('node_update_shutdown');
+
   $last = variable_get('node_cron_last', 0);
+  $last_nid = variable_get('node_cron_last_nid', 0);
   $limit = (int)variable_get('search_cron_limit', 100);
 
   // Store the maximum possible comments per thread (used for ranking by reply count)
   variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
   variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
 
-  $result = db_query_range('SELECT n.nid, c.last_comment_timestamp FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last, 0, $limit);
+  $result = db_query_range('SELECT GREATEST(c.last_comment_timestamp, n.changed, n.created) as last_change, n.nid FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND ((GREATEST(n.created, n.changed, c.last_comment_timestamp) = %d AND n.nid > %d) OR (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d)) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC, n.nid ASC', $last, $last_nid, $last, $last, $last, 0, $limit);
 
   while ($node = db_fetch_object($result)) {
-    $last_comment = $node->last_comment_timestamp;
+    $last_change = $node->last_change;
+    $last_nid = $node->nid;
     $node = node_load($node->nid);
 
-    // We update this variable per node in case cron times out, or if the node
-    // cannot be indexed (PHP nodes which call drupal_goto, for example).
-    // In rare cases this can mean a node is only partially indexed, but the
-    // chances of this happening are very small.
-    variable_set('node_cron_last', max($last_comment, $node->changed, $node->created));
-
     // Get node output (filtered and with module-specific fields).
     if (node_hook($node, 'view')) {
       node_invoke($node, 'view', false, false);
diff --git a/modules/search.module b/modules/search.module
index e8294107db8f464d02c7571fbead32d6469d2bdd..42c658123cd02ebeb679974f759a5792562a9e9b 100644
--- a/modules/search.module
+++ b/modules/search.module
@@ -292,10 +292,21 @@ function search_dirty($word = null) {
  * search_dirty).
  */
 function search_cron() {
+  // We register a shutdown function to ensure that search_total is always up
+  // to date.
+  register_shutdown_function('search_update_totals');
+
   // Update word index
   foreach (module_list() as $module) {
     module_invoke($module, 'update_index');
   }
+}
+
+/**
+ * This function is called on shutdown to ensure that search_total is always
+ * up to date (even if cron times out or otherwise fails).
+ */
+function search_update_totals() {
   // Update word IDF (Inverse Document Frequency) counts for new/changed words
   foreach (search_dirty() as $word => $dummy) {
     // Get total count
diff --git a/modules/search/search.module b/modules/search/search.module
index e8294107db8f464d02c7571fbead32d6469d2bdd..42c658123cd02ebeb679974f759a5792562a9e9b 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -292,10 +292,21 @@ function search_dirty($word = null) {
  * search_dirty).
  */
 function search_cron() {
+  // We register a shutdown function to ensure that search_total is always up
+  // to date.
+  register_shutdown_function('search_update_totals');
+
   // Update word index
   foreach (module_list() as $module) {
     module_invoke($module, 'update_index');
   }
+}
+
+/**
+ * This function is called on shutdown to ensure that search_total is always
+ * up to date (even if cron times out or otherwise fails).
+ */
+function search_update_totals() {
   // Update word IDF (Inverse Document Frequency) counts for new/changed words
   foreach (search_dirty() as $word => $dummy) {
     // Get total count