diff --git a/search_api.admin.inc b/search_api.admin.inc
index 807db9f..3d64040 100644
--- a/search_api.admin.inc
+++ b/search_api.admin.inc
@@ -866,7 +866,7 @@ function search_api_admin_index_status_form(array $form, array &$form_state, Sea
       else {
         $form['index']['limit'] = array(
           '#type' => 'value',
-          '#value' => -1,
+          '#value' => 1,
         );
         $desc = t("This will immediately index all items that haven't been indexed in their latest version yet.");
       }
@@ -951,25 +951,22 @@ function search_api_admin_index_status_form_submit(array $form, array &$form_sta
       $redirect = $pre . '/disable';
       break;
     case t('Index now'):
-      $limit = $values['limit'];
       $batch_size = empty($index->options['cron_limit']) ? SEARCH_API_DEFAULT_CRON_LIMIT : $index->options['cron_limit'];
-      // @todo Batch API?
-      try {
-        $ret = search_api_index_items($index, ($limit < 0 || $batch_size < 0) ? -1 : $limit * $batch_size);
-      }
-      catch (SearchApiException $e) {
-        $ret = FALSE;
-        watchdog_exception('search_api', $e);
-      }
-      if ($ret) {
-        drupal_set_message(format_plural($ret, 'Successfully indexed 1 item.', 'Successfully indexed @count items.'));
-        if (($limit > 0 && $ret < min(array($limit, $values['remaining']))) || ($limit < 0 && $ret < $values['remaining'])) {
-          drupal_set_message(t("Some items couldn't be indexed. Check the logs for details."), 'warning');
-        }
-      }
-      else {
-        drupal_set_message(t("Couldn't index items. Check the logs for details."), 'error');
+      $limit = $values['limit'];
+      if ($limit < 0) {
+        // Add 1 to be a bit on the safe side.
+        $limit = (int) ceil($values['remaining'] / $batch_size) + 1;
       }
+      $batch = array(
+        'title' => t('Indexing items'),
+        'operations' => array(
+          array('_search_api_admin_index_now_callback', array($index, $batch_size, $limit)),
+        ),
+        'progress_message' => t('Completed indexing of @current batches of @total.'),
+        'finished' => '_search_api_admin_index_now_finished',
+        'file' => drupal_get_path('module', 'search_api') . '/search_api.admin.inc',
+      );
+      batch_set($batch);
       $redirect = $pre . '/status';
       break;
     case t('Re-index content'):
@@ -997,6 +994,68 @@ function search_api_admin_index_status_form_submit(array $form, array &$form_sta
 }
 
 /**
+ * Batch API callback for the "Index now" functionality.
+ *
+ * @param SearchApiIndex $index
+ *   The index for which items should be indexed.
+ * @param integer $batch_size
+ *   Number of items to index per batch.
+ * @param integer $limit
+ *   Number of batch runs.
+ * @param $context
+ *   The batch context.
+ */
+function _search_api_admin_index_now_callback(SearchApiIndex $index, $batch_size, $limit, array &$context) {
+  // Persistent data among batch runs.
+  if (!isset($context['sandbox']['index'])) {
+    $context['sandbox']['index'] = $index;
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['limit'] = $limit;
+    $context['sandbox']['batch_size'] = $batch_size;
+  }
+
+  // Persistent data to store results.
+  if (!isset($context['results']['indexed'])) {
+    $context['results']['indexed'] = 0;
+  }
+
+  if ($context['sandbox']['progress'] >= $context['sandbox']['limit']) {
+    $context['finished'] = 1;
+  }
+  else {
+    $context['sandbox']['progress']++;
+    $indexed = search_api_index_items($index, $context['sandbox']['batch_size']);
+    $context['results']['indexed'] += $indexed;
+    $context['message'] = format_plural($context['results']['indexed'], 'Successfully indexed 1 item.', 'Successfully indexed @count items.');
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['limit'];
+  }
+}
+
+/**
+ * Batch API finishing callback for the "Index now" functionality.
+ *
+ * @param boolean $success
+ *   Result of the batch operation.
+ * @param array $results
+ *   Results.
+ * @param array $operations
+ *   Remaining batch operation to process.
+ */
+function _search_api_admin_index_now_finished($success, $results, $operations) {
+  if ($success) {
+    if (!empty($results['indexed'])) {
+      drupal_set_message(format_plural($results['indexed'], 'Successfully indexed 1 item.', 'Successfully indexed @count items.'));
+    }
+    else {
+      drupal_set_message(t("Couldn't index items. Check the logs for details."), 'error');
+    }
+  }
+  else {
+    drupal_set_message(t("An error occurred while trying to index items. Check the logs for details."), 'error');
+  }
+}
+
+/**
  * Edit an index' settings.
  *
  * @param SearchApiIndex $index
