diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index f8c3a01..6455671 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -1722,12 +1722,12 @@ class CommentController extends DrupalDefaultEntityController {
 function comment_num_new($nid, $timestamp = 0) {
   global $user;
 
-  if ($user->uid) {
+  if ($user->uid && module_exists('history')) {
     // Retrieve the timestamp at which the current user last viewed this node.
     if (!$timestamp) {
       $timestamp = node_last_viewed($nid);
     }
-    $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT);
+    $timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT);
 
     // Use the timestamp to retrieve the number of new comments.
     return db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid  AND created > :timestamp AND status = :status', array(
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index b5e4226..638d10d 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -838,18 +838,22 @@ function forum_forum_load($tid = NULL) {
 }
 
 /**
- * Calculate the number of nodes the user has not yet read and are newer
- * than NODE_NEW_LIMIT.
+ * Calculate the number of nodes the user has not yet read and are newer than HISTORY_READ_LIMIT.
+ *
+ * @todo Might be a bit stupid to run this query without History module.
  */
 function _forum_topics_unread($term, $uid) {
   $query = db_select('node', 'n');
   $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
   $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
   $query->addExpression('COUNT(n.nid)', 'count');
+  $query->condition('status', 1);
+  if (module_exists('history')) {
+    $query
+      ->condition('n.created', HISTORY_READ_LIMIT, '>')
+      ->isNull('h.nid');
+  }
   return $query
-    ->condition('status', 1)
-    ->condition('n.created', NODE_NEW_LIMIT, '>')
-    ->isNull('h.nid')
     ->addTag('node_access')
     ->execute()
     ->fetchField();
@@ -1181,13 +1185,13 @@ function _forum_user_last_visit($nid) {
   global $user;
   $history = &drupal_static(__FUNCTION__, array());
 
-  if (empty($history)) {
+  if (empty($history) && module_exists('history')) {
     $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid', array(':uid' => $user->uid));
     foreach ($result as $t) {
-      $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
+      $history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT;
     }
   }
-  return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
+  return isset($history[$nid]) ? $history[$nid] : 0;
 }
 
 function _forum_get_topic_order($sortby) {
diff --git a/modules/history/history.api.php b/modules/history/history.api.php
new file mode 100644
index 0000000..78f8b92
--- /dev/null
+++ b/modules/history/history.api.php
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @file
+ * API documentation for History module.
+ */
+
diff --git a/modules/history/history.info b/modules/history/history.info
new file mode 100644
index 0000000..2414d09
--- /dev/null
+++ b/modules/history/history.info
@@ -0,0 +1,6 @@
+name = History
+description = Records which user has read which content.
+package = Core
+core = 8.x
+dependencies[] = node
+files[] = tests/history.test
diff --git a/modules/history/history.install b/modules/history/history.install
new file mode 100644
index 0000000..109ef1c
--- /dev/null
+++ b/modules/history/history.install
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Installation functions for History module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function history_schema() {
+  $schema['history'] = array(
+    'description' => 'A record of which {users} have read which {node}s.',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'The {users}.uid that read the {node} nid.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'nid' => array(
+        'description' => 'The {node}.nid that was read.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'timestamp' => array(
+        'description' => 'The Unix timestamp at which the read occurred.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid', 'nid'),
+    'indexes' => array(
+      'nid' => array('nid'),
+    ),
+  );
+
+  return $schema;
+}
+
diff --git a/modules/history/history.module b/modules/history/history.module
new file mode 100644
index 0000000..5417720
--- /dev/null
+++ b/modules/history/history.module
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Records which users has read which content.
+ *
+ * @todo
+ * - Generic helper for _forum_user_last_visit() + node_last_viewed().
+ * - Generic helper for node_mark().
+ */
+
+/**
+ * Entities changed before this time are always shown as read.
+ *
+ * Entities changed within this time may be marked as new, updated, or read,
+ * depending on their state for the current user. Defaults to 30 days ago.
+ */
+define('HISTORY_READ_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
+
+/**
+ * Implements hook_cron().
+ */
+function history_cron() {
+  db_delete('history')
+    ->condition('timestamp', HISTORY_READ_LIMIT, '<')
+    ->execute();
+}
+
+/**
+ * Update the 'last viewed' timestamp of the specified entity for the current user.
+ *
+ * @param $nid
+ *   The node ID that has been read.
+ * @param $account
+ *   (optional) The user account to update the history for. Defaults to the
+ *   current user.
+ */
+function history_update($nid, $account = NULL) {
+  global $user;
+
+  if (!isset($account)) {
+    $account = $user;
+  }
+
+  if ($account->uid) {
+    db_merge('history')
+      ->key(array(
+        'uid' => $account->uid,
+        'nid' => $nid,
+      ))
+      ->fields(array('timestamp' => REQUEST_TIME))
+      ->execute();
+   }
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function history_node_delete($node) {
+  db_delete('history')
+    ->condition('nid', $node->nid)
+    ->execute();
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function history_user_cancel($edit, $account, $method) {
+  switch ($method) {
+    case 'user_cancel_reassign':
+      db_delete('history')
+        ->condition('uid', $account->uid)
+        ->execute();
+      break;
+  }
+}
+
diff --git a/modules/history/tests/history.test b/modules/history/tests/history.test
new file mode 100644
index 0000000..0daecb3
--- /dev/null
+++ b/modules/history/tests/history.test
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @file
+ * Tests for History module.
+ */
+
diff --git a/modules/node/node.install b/modules/node/node.install
index 4632916..7aceec7 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -395,34 +395,6 @@ function node_schema() {
     ),
   );
 
-  $schema['history'] = array(
-    'description' => 'A record of which {users} have read which {node}s.',
-    'fields' => array(
-      'uid' => array(
-        'description' => 'The {users}.uid that read the {node} nid.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'nid' => array(
-        'description' => 'The {node}.nid that was read.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'timestamp' => array(
-        'description' => 'The Unix timestamp at which the read occurred.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array('uid', 'nid'),
-    'indexes' => array(
-      'nid' => array('nid'),
-    ),
-  );
-
   return $schema;
 }
 
@@ -451,3 +423,20 @@ function node_install() {
 function _update_8000_node_get_types() {
   return db_query('SELECT * FROM {node_type}')->fetchAllAssoc('type', PDO::FETCH_OBJ);
 }
+
+/**
+ * Enable History module.
+ */
+function node_update_8000() {
+  // Create a fake entry in {system} to not re-install the {history} schema.
+  db_update('system')
+    ->fields(array(
+      'schema_version' => 0,
+    ))
+    ->condition('type', 'module')
+    ->condition('name', 'history')
+    ->execute();
+
+  module_enable(array('history'));
+}
+
diff --git a/modules/node/node.module b/modules/node/node.module
index a59a5b1..634c6e0 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -37,14 +37,6 @@ define('NODE_NOT_STICKY', 0);
 define('NODE_STICKY', 1);
 
 /**
- * Nodes changed before this time are always marked as read.
- *
- * Nodes changed after this time may be marked new, updated, or read, depending
- * on their state for the current user. Defaults to 30 days ago.
- */
-define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
-
-/**
  * Modules should return this value from hook_node_access() to allow access to a node.
  */
 define('NODE_ACCESS_ALLOW', 'allow');
@@ -152,15 +144,6 @@ function node_theme() {
 }
 
 /**
- * Implements hook_cron().
- */
-function node_cron() {
-  db_delete('history')
-    ->condition('timestamp', NODE_NEW_LIMIT, '<')
-    ->execute();
-}
-
-/**
  * Implements hook_entity_info().
  */
 function node_entity_info() {
@@ -295,25 +278,6 @@ function node_title_list($result, $title = NULL) {
 }
 
 /**
- * Update the 'last viewed' timestamp of the specified node for current user.
- *
- * @param $node
- *   A node object.
- */
-function node_tag_new($node) {
-  global $user;
-  if ($user->uid) {
-    db_merge('history')
-      ->key(array(
-        'uid' => $user->uid,
-        'nid' => $node->nid,
-      ))
-      ->fields(array('timestamp' => REQUEST_TIME))
-      ->execute();
-   }
-}
-
-/**
  * Retrieves the timestamp at which the current user last viewed the
  * specified node.
  */
@@ -321,11 +285,11 @@ function node_last_viewed($nid) {
   global $user;
   $history = &drupal_static(__FUNCTION__, array());
 
-  if (!isset($history[$nid])) {
-    $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
+  if (!isset($history[$nid]) && module_exists('history')) {
+    $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchField();
   }
 
-  return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
+  return !empty($history[$nid]) ? $history[$nid] : 0;
 }
 
 /**
@@ -342,16 +306,16 @@ function node_mark($nid, $timestamp) {
   global $user;
   $cache = &drupal_static(__FUNCTION__, array());
 
-  if (!$user->uid) {
+  if (!$user->uid || !module_exists('history')) {
     return MARK_READ;
   }
   if (!isset($cache[$nid])) {
     $cache[$nid] = node_last_viewed($nid);
   }
-  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
+  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
     return MARK_NEW;
   }
-  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
+  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
     return MARK_UPDATED;
   }
   return MARK_READ;
@@ -1221,9 +1185,6 @@ function node_delete_multiple($nids) {
       db_delete('node_revision')
         ->condition('nid', $nids, 'IN')
         ->execute();
-      db_delete('history')
-        ->condition('nid', $nids, 'IN')
-        ->execute();
       db_delete('node_access')
        ->condition('nid', $nids, 'IN')
        ->execute();
@@ -1406,7 +1367,7 @@ function node_show($node, $message = FALSE) {
   }
 
   // Update the history table, stating that this user viewed this node.
-  node_tag_new($node);
+  module_invoke('history', 'update', $node->nid);
 
   // For markup consistency with other pages, use node_view_multiple() rather than node_view().
   return node_view_multiple(array($node->nid => $node), 'full');
@@ -1758,10 +1719,6 @@ function node_user_cancel($edit, $account, $method) {
         ->fields(array('uid' => 0))
         ->condition('uid', $account->uid)
         ->execute();
-      // Clean history.
-      db_delete('history')
-        ->condition('uid', $account->uid)
-        ->execute();
       break;
   }
 }
@@ -1783,10 +1740,6 @@ function node_user_delete($account) {
   foreach ($revisions as $revision) {
     node_revision_delete($revision);
   }
-  // Clean history.
-  db_delete('history')
-    ->condition('uid', $account->uid)
-    ->execute();
 }
 
 /**
diff --git a/modules/user/user.api.php b/modules/user/user.api.php
index 272731a..b7eb709 100644
--- a/modules/user/user.api.php
+++ b/modules/user/user.api.php
@@ -102,10 +102,6 @@ function hook_user_cancel($edit, $account, $method) {
         ->fields(array('uid' => 0))
         ->condition('uid', $account->uid)
         ->execute();
-      // Clean history.
-      db_delete('history')
-        ->condition('uid', $account->uid)
-        ->execute();
       break;
   }
 }
diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info
index 56e4308..2204e44 100644
--- a/profiles/standard/standard.info
+++ b/profiles/standard/standard.info
@@ -8,6 +8,7 @@ dependencies[] = comment
 dependencies[] = contextual
 dependencies[] = dashboard
 dependencies[] = help
+dependencies[] = history
 dependencies[] = image
 dependencies[] = list
 dependencies[] = menu
