diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 2253f71..21ce62f 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -755,6 +755,29 @@ function node_permission() {
 }
 
 /**
+ * Implements hook_cron().
+ */
+function node_cron() {
+  // Calculate the oldest and newest node created times, for use in search
+  // rankings. (Note that field aliases have to be variables passed by
+  // reference.)
+  $min_alias = 'min_created';
+  $max_alias = 'max_created';
+  $result = \Drupal::entityQueryAggregate('node')
+    ->aggregate('created', 'MIN', NULL, $min_alias)
+    ->aggregate('created', 'MAX', NULL, $max_alias)
+    ->execute();
+  if (isset($result[0])) {
+    // Make an array with definite keys and store it in the state system.
+    $array = array(
+      'min_created' => $result[0][$min_alias],
+      'max_created' => $result[0][$max_alias],
+    );
+    \Drupal::state()->set('node.min_max_update_time', $array);
+  }
+}
+
+/**
  * Implements hook_ranking().
  */
 function node_ranking() {
@@ -776,14 +799,23 @@ function node_ranking() {
       'score' => 'n.promote',
     ),
   );
-
-  // Add relevance based on creation or changed date.
-  if ($node_cron_last = \Drupal::state()->get('node.cron_last')) {
+  // Add relevance based on updated date, but only if it the scale values have
+  // been calculated in node_cron().
+  if ($node_min_max = \Drupal::state()->get('node.min_max_update_time')) {
     $ranking['recent'] = array(
-      'title' => t('Recently posted'),
-      // Exponential decay with half-life of 6 months, starting at last indexed node
-      'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)',
-      'arguments' => array(':node_cron_last' => $node_cron_last),
+      'title' => t('Recently created'),
+      'join' => array(
+        'type' => 'LEFT',
+        'table' => 'node_field_data',
+        'alias' => 'nfd',
+        'on' => 'nfd.nid = sid',
+      ),
+      // Exponential decay with half life of 14% of the age range of nodes.
+      'score' => 'EXP(-5 * (1 - (nfd.created - :node_oldest) / :node_range))',
+      'arguments' => array(
+        ':node_oldest' => $node_min_max['min_created'],
+        ':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
+      ),
     );
   }
   return $ranking;
diff --git a/core/modules/search/src/Tests/SearchRankingTest.php b/core/modules/search/src/Tests/SearchRankingTest.php
index 48410d6..bae7b4a 100644
--- a/core/modules/search/src/Tests/SearchRankingTest.php
+++ b/core/modules/search/src/Tests/SearchRankingTest.php
@@ -41,11 +41,12 @@ public function setUp() {
 
     // Create a plugin instance.
     $this->nodeSearch = entity_load('search_page', 'node_search');
-  }
 
-  public function testRankings() {
     // Login with sufficient privileges.
     $this->drupalLogin($this->drupalCreateUser(array('post comments', 'skip comment approval', 'create page content', 'administer search')));
+  }
+
+  public function testRankings() {
     // Add a comment field.
     $this->container->get('comment.manager')->addDefaultField('node', 'page');
 
@@ -62,6 +63,8 @@ public function testRankings() {
         )),
         'title' => 'Drupal rocks',
         'body' => array(array('value' => "Drupal's search rocks")),
+        // Node is one day old.
+        'created' => REQUEST_TIME - 24 * 3600,
       );
       foreach (array(0, 1) as $num) {
         if ($num == 1) {
@@ -74,7 +77,8 @@ public function testRankings() {
               $settings['body'][0]['value'] .= " really rocks";
               break;
             case 'recent':
-              $settings['created'] = REQUEST_TIME + 3600;
+              // Node is 1 hour hold.
+              $settings['created'] = REQUEST_TIME - 3600;
               break;
             case 'comments':
               $settings['comment'][0]['status'] = CommentItemInterface::OPEN;
@@ -107,7 +111,7 @@ public function testRankings() {
     // Run cron to update the search index and comment/statistics totals.
     $this->cronRun();
 
-    // Test that the settings form displays the context ranking section.
+    // Test that the settings form displays the content ranking section.
     $this->drupalGet('admin/config/search/pages/manage/node_search');
     $this->assertText(t('Content ranking'));
 
@@ -131,6 +135,7 @@ public function testRankings() {
       $this->nodeSearch->getPlugin()->setSearch('rocks', array(), array());
       $set = $this->nodeSearch->getPlugin()->execute();
       $this->assertEqual($set[0]['node']->id(), $nodes[$node_rank][1]->id(), 'Search ranking "' . $node_rank . '" order.');
+
       // Clear this ranking for the next test.
       $edit['rankings_' . $node_rank] = 0;
     }
@@ -142,6 +147,55 @@ public function testRankings() {
     foreach ($node_ranks as $node_rank) {
       $this->assertTrue($this->xpath('//select[@id="edit-rankings-' . $node_rank . '"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.');
     }
+
+    // Try with sticky, then promoted. This is a test for issue
+    // https://drupal.org/node/771596.
+    $node_ranks = array(
+      'sticky' => 10,
+      'promote' => 1,
+      'relevance' => 0,
+      'recent' => 0,
+      'comments' => 0,
+      'views' => 0,
+    );
+    $configuration = $this->nodeSearch->getPlugin()->getConfiguration();
+    foreach ($node_ranks as $var => $value) {
+      $configuration['rankings'][$var] = $value;
+    }
+    $this->nodeSearch->getPlugin()->setConfiguration($configuration);
+    $this->nodeSearch->save();
+
+    // Do the search and assert the results. The sticky node should show up
+    // first, then the promoted node, then all the rest.
+    $this->nodeSearch->getPlugin()->setSearch('rocks', array(), array());
+    $set = $this->nodeSearch->getPlugin()->execute();
+    $this->assertEqual($set[0]['node']->id(), $nodes['sticky'][1]->id(), 'Search ranking for sticky first worked.');
+    $this->assertEqual($set[1]['node']->id(), $nodes['promote'][1]->id(), 'Search ranking for promoted second worked.');
+
+    // Try with recent, then comments. This is a test for issues
+    // https://drupal.org/node/771596 and https://drupal.org/node/303574.
+    $node_ranks = array(
+      'sticky' => 0,
+      'promote' => 0,
+      'relevance' => 0,
+      'recent' => 10,
+      'comments' => 1,
+      'views' => 0,
+    );
+    $configuration = $this->nodeSearch->getPlugin()->getConfiguration();
+    foreach ($node_ranks as $var => $value) {
+      $configuration['rankings'][$var] = $value;
+    }
+    $this->nodeSearch->getPlugin()->setConfiguration($configuration);
+    $this->nodeSearch->save();
+
+    // Do the search and assert the results. The recent node should show up
+    // first, then the commented node, then all the rest.
+    $this->nodeSearch->getPlugin()->setSearch('rocks', array(), array());
+    $set = $this->nodeSearch->getPlugin()->execute();
+    $this->assertEqual($set[0]['node']->id(), $nodes['recent'][1]->id(), 'Search ranking for recent first worked.');
+    $this->assertEqual($set[1]['node']->id(), $nodes['comments'][1]->id(), 'Search ranking for comments second worked.');
+
   }
 
   /**
@@ -154,9 +208,6 @@ public function testHTMLRankings() {
     ));
     $full_html_format->save();
 
-    // Login with sufficient privileges.
-    $this->drupalLogin($this->drupalCreateUser(array('create page content')));
-
     // Test HTML tags with different weights.
     $sorted_tags = array('h1', 'h2', 'h3', 'h4', 'a', 'h5', 'h6', 'notag');
     $shuffled_tags = $sorted_tags;
@@ -225,46 +276,4 @@ public function testHTMLRankings() {
       $node->delete();
     }
   }
-
-  /**
-   * Verifies that if we combine two rankings, search still works.
-   *
-   * See issue http://drupal.org/node/771596
-   */
-  function testDoubleRankings() {
-    // Login with sufficient privileges.
-    $this->drupalLogin($this->drupalCreateUser(array('skip comment approval', 'create page content')));
-
-    // Create two nodes that will match the search, one that is sticky.
-    $settings = array(
-      'type' => 'page',
-      'title' => 'Drupal rocks',
-      'body' => array(array('value' => "Drupal's search rocks")),
-    );
-    $this->drupalCreateNode($settings);
-    $settings['sticky'] = 1;
-    $node = $this->drupalCreateNode($settings);
-
-    // Update the search index.
-    $this->nodeSearch->getPlugin()->updateIndex();
-    search_update_totals();
-
-    // Set up for ranking sticky and lots of comments; make sure others are
-    // disabled.
-    $node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views');
-    $configuration = $this->nodeSearch->getPlugin()->getConfiguration();
-    foreach ($node_ranks as $var) {
-      $value = ($var == 'sticky' || $var == 'comments') ? 10 : 0;
-      $configuration['rankings'][$var] = $value;
-    }
-    $this->nodeSearch->getPlugin()->setConfiguration($configuration);
-    $this->nodeSearch->save();
-
-    // Do the search and assert the results.
-    $this->nodeSearch->getPlugin()->setSearch('rocks', array(), array());
-    // Do the search and assert the results.
-    $set = $this->nodeSearch->getPlugin()->execute();
-    $this->assertEqual($set[0]['node']->id(), $node->id(), 'Search double ranking order.');
-  }
-
 }
