diff --git a/redirect.admin.inc b/redirect.admin.inc
index 880f596..c6ed5fc 100644
--- a/redirect.admin.inc
+++ b/redirect.admin.inc
@@ -18,6 +18,7 @@ function redirect_list_form($form, &$form_state) {
   $header = array(
     'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'),
     'redirect' => array('data' => t('To'), 'field' => 'redirect'),
+    'status' => array('data' => t('Status'), 'field' => 'status'),
     'status_code' => array('data' => t('Type'), 'field' => 'status_code'),
     'language' => array('data' => t('Language'), 'field' => 'language'),
     'count' => array('data' => t('Count'), 'field' => 'count'),
@@ -84,6 +85,7 @@ function redirect_list_form($form, &$form_state) {
     drupal_alter('redirect_url', $redirect->redirect, $redirect->redirect_options);
     $row['source'] = l($source_url, $redirect->source, $redirect->source_options);
     $row['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options);
+    $row['status'] = $redirect->status ? t('Enabled') : t('Disabled');
     $row['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code));
     $row['language'] = module_invoke('locale', 'language_name', $redirect->language);
     $row['count'] = $redirect->count;
@@ -365,6 +367,13 @@ function redirect_edit_form($form, &$form_state, $redirect = NULL) {
     '#description' => t('Enter an internal Drupal path, path alias, or complete external URL (like http://example.com/) to redirect to. Use %front to redirect to the front page.', array('%front' => '<front>')),
     '#element_validate' => array('redirect_element_validate_redirect'),
   );
+  $form['status'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enabled'),
+    '#default_value' => $redirect->status,
+    '#required' => FALSE,
+  );
+
   $form['redirect_options'] = array(
     '#type' => 'value',
     '#value' => $redirect->redirect_options,
@@ -818,6 +827,7 @@ function redirect_list_table($redirects, $header) {
   $header = array_intersect_key(array(
     'source' => array('data' => t('From'), 'field' => 'source', 'sort' => 'asc'),
     'redirect' => array('data' => t('To'), 'field' => 'redirect'),
+    'status' => array('data' => t('Status'), 'field' => 'status'),
     'status_code' => array('data' => t('Type'), 'field' => 'status_code'),
     'language' => array('data' => t('Language'), 'field' => 'language'),
     'count' => array('data' => t('Count'), 'field' => 'count'),
@@ -838,6 +848,7 @@ function redirect_list_table($redirects, $header) {
     $redirect_url = redirect_url($redirect->redirect, array_merge($redirect->redirect_options, array('alias' => TRUE)));
     $row['data']['source'] = l($source_url, $redirect->source, $redirect->source_options);
     $row['data']['redirect'] = l($redirect_url, $redirect->redirect, $redirect->redirect_options);
+    $row['data']['status'] = $redirect->status ? t('Enabled') : t('Disabled');
     $row['data']['status_code'] = $redirect->status_code ? $redirect->status_code : t('Default (@default)', array('@default' => $default_status_code));
     $row['data']['language'] = module_invoke('locale', 'language_name', $redirect->language);
     $row['data']['count'] = $redirect->count;
diff --git a/redirect.api.php b/redirect.api.php
index f38d715..2323833 100644
--- a/redirect.api.php
+++ b/redirect.api.php
@@ -275,6 +275,38 @@ function hook_redirect_delete($redirect) {
 }
 
 /**
+ * Respond to redirect disabling.
+ *
+ * This hook is invoked from redirect_disable_multiple() after the redirect has
+ * been disabled in the redirect table in the database.
+ *
+ * @param $redirect
+ *   The redirect that is being disabled.
+ *
+ * @see redirect_disable_multiple()
+ * @ingroup redirect_api_hooks
+ */
+function hook_redirect_disable($redirect) {
+
+}
+
+/**
+ * Respond to redirect re-enabling.
+ *
+ * This hook is invoked from redirect_save() after the redirect has
+ * been changed from disabled to enabled.
+ *
+ * @param $redirect
+ *   The redirect that is being re-enabled.
+ *
+ * @see redirect_save()
+ * @ingroup redirect_api_hooks
+ */
+function hook_redirect_reenable($redirect) {
+
+}
+
+/**
  * Act on a redirect being redirected.
  *
  * This hook is invoked from redirect_redirect() before the redirect callback
diff --git a/redirect.install b/redirect.install
index 6ccb75b..cd8a0f1 100644
--- a/redirect.install
+++ b/redirect.install
@@ -86,7 +86,14 @@ function redirect_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => 'The timestamp of when the redirect was last accessed.'
+        'description' => 'The timestamp of when the redirect was last accessed.',
+      ),
+      'status' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 1,
+        'description' => 'Boolean indicating whether the redirect is enabled (visible to non-administrators).',
       ),
     ),
     'primary key' => array('rid'),
@@ -96,6 +103,7 @@ function redirect_schema() {
     'indexes' => array(
       'expires' => array('type', 'access'),
       'source_language' => array('source', 'language'),
+      'status' => array('status'),
     ),
   );
 
@@ -322,6 +330,7 @@ function _redirect_migrate_path_redirect_redirect($old_redirect) {
         'redirect_options' => serialize($redirect->redirect_options),
         'language' => $redirect->language,
         'status_code' => $redirect->status_code,
+        'status' => 1,
         'count' => 0,
         'access' => $old_redirect->last_used,
       ))
@@ -363,3 +372,40 @@ function _redirect_migrate_path_redirect_variables() {
     variable_del($old_variable);
   }
 }
+
+/**
+ * Add status field to redirect table.
+ */
+function redirect_update_7101() {
+  $field = array(
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 1,
+    'description' => 'Boolean indicating whether the redirect is enabled (visible to non-administrators).',
+  );
+  db_add_field('redirect', 'status', $field);
+  db_add_index('redirect', 'status', array('status'));
+}
+
+/**
+ * Disable existing redirects causing infinite loops.
+ */
+function redirect_update_7102(&$sandbox) {
+  $query = db_select('redirect', 'r');
+  $query->join(
+    'url_alias',
+    'u',
+    'r.source = u.alias AND r.redirect = u.source AND r.language = u.language'
+  );
+  $query->fields('r', array('rid'));
+  $result = $query->execute();
+  $rids_to_disable = $query->execute()->fetchCol();
+  if (count($rids_to_disable) > 0) {
+    redirect_disable_multiple($rids_to_disable);
+    return format_plural(count($rids_to_disable), '1 circular redirect causing infinite loop was disabled.', '@count circular redirects causing infinite loop were disabled.');
+  }
+  else {
+    return t('No circular redirect causing infinite loop was found.');
+  }
+}
diff --git a/redirect.module b/redirect.module
index a503159..285261f 100644
--- a/redirect.module
+++ b/redirect.module
@@ -376,6 +376,8 @@ function redirect_path_update(array $path) {
   }
 
   if (!empty($path['original']['pid']) && $path['original']['pid'] == $path['pid'] && $path['original']['alias'] != $path['alias']) {
+    // Disable all redirects having the same source as this alias.
+    redirect_disable_by_path($path['alias'], $path['language']);
     $redirect = new stdClass();
     redirect_object_prepare($redirect);
     $redirect->source = $path['original']['alias'];
@@ -390,6 +392,16 @@ function redirect_path_update(array $path) {
 }
 
 /**
+ * Implements hook_path_insert().
+ */
+function redirect_path_insert(array $path) {
+  if (!empty($path['alias'])) {
+    // Disable all redirects having the same source as this alias.
+    redirect_disable_by_path($path['alias'], $path['language']);
+  }
+}
+
+/**
  * Implements hook_path_delete().
  */
 function redirect_path_delete($path) {
@@ -519,7 +531,7 @@ function redirect_load($rid, $reset = FALSE) {
  * @ingroup redirect_api
  */
 function redirect_load_by_hash($hash, $reset = FALSE) {
-  $redirects = entity_load('redirect', FALSE, array('hash' => $hash), $reset);
+  $redirects = entity_load('redirect', FALSE, array('hash' => $hash, 'status' => 1), $reset);
   return !empty($redirects) ? reset($redirects) : FALSE;
 }
 
@@ -542,6 +554,7 @@ function redirect_load_by_source($source, $language = LANGUAGE_NONE, array $quer
   // Run a case-insensitive query for matching RIDs first.
   $rid_query = db_select('redirect');
   $rid_query->addField('redirect', 'rid');
+  $rid_query->condition('status', 1);
   if ($source != variable_get('site_frontpage', 'node')) {
     $rid_query->condition('source', db_like($source), 'LIKE');
   }
@@ -705,6 +718,7 @@ function redirect_object_prepare($redirect, $defaults = array()) {
     'count' => 0,
     'access' => 0,
     'hash' => '',
+    'status' => 1,
   );
 
   foreach ($defaults as $key => $default) {
@@ -748,6 +762,15 @@ function redirect_save($redirect) {
       $redirect->access = 0;
     }
 
+    // Invoke post-disable hooks if disabling a redirect.
+    if ($redirect->status == 0 && isset($redirect->original) && $redirect->original->status == 1) {
+      module_invoke_all('redirect_disable', $redirect);
+    }
+    // Invoke post-re-enable hooks if re-enbling a redirect.
+    if ($redirect->status == 1 && isset($redirect->original) && $redirect->original->status == 0) {
+      module_invoke_all('redirect_reenable', $redirect);
+    }
+
     // Allow other modules to alter the redirect before saving.
     module_invoke_all('redirect_presave', $redirect);
     module_invoke_all('entity_presave', $redirect, 'redirect');
@@ -848,6 +871,36 @@ function redirect_delete_by_path($path) {
 }
 
 /**
+ * Disable any redirects associated with a path.
+ *
+ * Given a source like 'node/1' this function will delete any redirects that
+ * have that specific source.
+ *
+ * @param $path
+ *   An string with an internal Drupal path.
+ *
+ * @param @langauge
+ *   The langcode of the path.
+ *
+ * @ingroup redirect_api
+ */
+function redirect_disable_by_path($path, $language) {
+  $query = db_select('redirect');
+  $query->addField('redirect', 'rid');
+  $query_or = db_or();
+  $query_or->condition('source', db_like($path), 'LIKE');
+  $query->condition($query_or);
+  if ($language) {
+    $query->condition('language', $language);
+  }
+  $rids = $query->execute()->fetchCol();
+
+  if ($rids) {
+    return redirect_disable_multiple($rids);
+  }
+}
+
+/**
  * Delete an entity URL alias and any of its sub-paths.
  *
  * This function also checks to see if the default entity URI is different from
@@ -877,6 +930,42 @@ function redirect_delete_by_entity_path($entity_type, $entity) {
 }
 
 /**
+ * Disable multiple URL redirects.
+ *
+ * @param $rids
+ *   An array of redirect IDs to disable.
+ *
+ * @ingroup redirect_api
+ */
+function redirect_disable_multiple(array $rids) {
+  $transaction = db_transaction();
+  if (!empty($rids)) {
+    $redirects = redirect_load_multiple($rids);
+
+    try {
+      // Let modules react to the individual redirects being disabled.
+      foreach ($redirects as $rid => $redirect) {
+        module_invoke_all('redirect_disable', $redirect);
+      }
+
+      db_update('redirect')
+        ->fields(array('status' => 1))
+        ->condition('rid', $rids, 'IN')
+        ->execute();
+    }
+
+    catch (Exception $e) {
+      $transaction->rollback();
+      watchdog_exception('redirect', $e);
+      throw $e;
+    }
+
+    // Clear the redirect_load_multiple cache.
+    entity_get_controller('redirect')->resetCache();
+  }
+}
+
+/**
  * Delete multiple URL redirects.
  *
  * @param $rids
@@ -989,6 +1078,10 @@ function redirect_redirect($redirect = NULL) {
 
   // Continue if the redirect has not been disabled by hook_redirect_alter().
   if (isset($redirect->redirect) && isset($redirect->callback) && $redirect->redirect !== FALSE && function_exists($redirect->callback)) {
+    // Prevent circular redirects.
+    if ($GLOBALS['base_root'] . request_uri() == url($redirect->redirect, array('absolute' => TRUE) + $redirect->redirect_options)) {
+      return FALSE;
+    }
     // Perform the actual redirect.
     $callback = $redirect->callback;
     $callback($redirect);
diff --git a/redirect.test b/redirect.test
index 1c237f8..1a67e33 100644
--- a/redirect.test
+++ b/redirect.test
@@ -234,11 +234,37 @@ class RedirectFunctionalTest extends RedirectTestHelper {
     //$this->assertRedirect($redirect);
 
     $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'first-alias'), 'Save');
+    $this->assertResponse(200, "Changing node's alias back to 'first-alias' does not break page load with a circular redirect.");
+    $this->assertNoText('Infinite redirect loop prevented.');
     //$redirect = redirect_load_by_source('second-alias');
     //$this->assertRedirect($redirect);
 
     $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'second-alias'), 'Save');
+    $this->assertResponse(200, "Changing node's alias back to 'second-alias' does not break page load with a circular redirect.");
+    $this->assertNoText('Infinite redirect loop prevented.');
     //$redirect = redirect_load_by_source('first-alias');
     //$this->assertRedirect($redirect);
   }
+
+  function testDisableEnableRedirect() {
+    // Add a new redirect.
+    $redirect = $this->addRedirect('redirect', 'node');
+    // Check that it is enabled.
+    $this->assertEqual($redirect->status, 1);
+
+    // Disable the redirect.
+    $edit = array('status' => FALSE);
+    $this->drupalPost("admin/config/search/redirect/edit/{$redirect->rid}", $edit, 'Save');
+    $redirect = redirect_load($redirect->nid);
+    // Check that it has been disabled.
+    $this->assertEqual($redirect->status, 0);
+    $this->drupalGet("admin/config/search/redirect/edit/{$redirect->rid}");
+    $this->assertNoFieldChecked('edit-status', 'status is unchecked');
+    $this->assertNoRedirect($redirect);
+
+    // Re-enable the redirect.
+    $edit = array('status' => 1);
+    $this->drupalPost("admin/config/search/redirect/edit/{$redirect->rid}", $edit, 'Save');
+    $this->assertRedirect($redirect);
+  }
 }
