diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index 3f732a4..19c989a 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -469,3 +469,30 @@ function _update_7000_node_get_types() {
   }
   return $node_types;
 }
+
+/**
+ * @addtogroup updates-7.x-to-8.x
+ * @{
+ */
+
+/**
+ * Split out the "publish content" permission from "administer nodes".
+ */
+function node_update_8000() {
+  $rids = db_query("SELECT rid FROM {role_permission} WHERE permission = :perm", array(':perm' => 'administer nodes'))->fetchCol();
+  if (!empty($rids)) {
+    $insert = db_insert('role_permission')->fields(array('rid', 'permission', 'module'));
+    foreach ($rids as $rid) {
+      $insert->values(array(
+        'rid' => $rid,
+        'permission' => 'publish content',
+        'module' => 'node',
+      ));
+    }
+    $insert->execute();
+  }
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-to-8.x"
+ */
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index ebf68eb..54cb95e 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -32,7 +32,10 @@ Drupal.behaviors.nodeFieldsetSummaries = {
         vals.push(Drupal.checkPlain($.trim($(this).text())));
       });
 
-      if (!$('.form-item-status input', context).is(':checked')) {
+      // If the 'published' checkbox is present and unchecked, explicitly
+      // indicate in the summary that the content is not published.
+      var publishedCheckbox = $('.form-item-status input', context);
+      if (publishedCheckbox.length && !publishedCheckbox.is(':checked')) {
         vals.unshift(Drupal.t('Not published'));
       }
       return vals.join(', ');
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 7956bff..1dace24 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1517,6 +1517,9 @@ function node_permission() {
       'title' => t('Administer content'),
       'restrict access' => TRUE,
     ),
+    'publish content' => array(
+      'title' => t('Publish and unpublish content'),
+    ),
     'access content overview' => array(
       'title' => t('Access the content overview page'),
     ),
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 89335be..34c6166 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -231,10 +231,12 @@ function node_form($form, &$form_state, $node) {
     '#default_value' => !empty($node->date) ? $node->date : '',
   );
 
-  // Node options for administrators
+  // Node options for administrators.
+  $admin_access = user_access('administer nodes');
+  $publish_access = user_access('publish content');
   $form['options'] = array(
     '#type' => 'fieldset',
-    '#access' => user_access('administer nodes'),
+    '#access' => $admin_access || $publish_access,
     '#title' => t('Publishing options'),
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
@@ -251,16 +253,19 @@ function node_form($form, &$form_state, $node) {
     '#type' => 'checkbox',
     '#title' => t('Published'),
     '#default_value' => $node->status,
+    '#access' => $publish_access,
   );
   $form['options']['promote'] = array(
     '#type' => 'checkbox',
     '#title' => t('Promoted to front page'),
     '#default_value' => $node->promote,
+    '#access' => $admin_access,
   );
   $form['options']['sticky'] = array(
     '#type' => 'checkbox',
     '#title' => t('Sticky at top of lists'),
     '#default_value' => $node->sticky,
+    '#access' => $admin_access,
   );
 
   // Add the buttons.
diff --git a/core/modules/node/node.test b/core/modules/node/node.test
index 56a2d34..c67d6bb 100644
--- a/core/modules/node/node.test
+++ b/core/modules/node/node.test
@@ -1915,7 +1915,7 @@ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase {
 
   function setUp() {
     parent::setUp('poll');
-    $web_user = $this->drupalCreateUser(array('administer nodes', 'create poll content'));
+    $web_user = $this->drupalCreateUser(array('administer nodes', 'publish content', 'create poll content'));
     $this->drupalLogin($web_user);
   }
 
diff --git a/core/modules/translation/translation.test b/core/modules/translation/translation.test
index fe320a9..d40b7e6 100644
--- a/core/modules/translation/translation.test
+++ b/core/modules/translation/translation.test
@@ -20,7 +20,7 @@ class TranslationTestCase extends DrupalWebTestCase {
     parent::setUp('locale', 'translation', 'translation_test');
 
     // Setup users.
-    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content'));
+    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'publish content', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content'));
     $this->translator = $this->drupalCreateUser(array('create page content', 'edit own page content', 'translate content'));
 
     $this->drupalLogin($this->admin_user);
diff --git a/core/modules/trigger/trigger.test b/core/modules/trigger/trigger.test
index 9a9a4ba..c40dd3d 100644
--- a/core/modules/trigger/trigger.test
+++ b/core/modules/trigger/trigger.test
@@ -63,7 +63,7 @@ class TriggerContentTestCase extends TriggerWebTestCase {
     $content_actions = array('node_publish_action', 'node_unpublish_action', 'node_make_sticky_action', 'node_make_unsticky_action', 'node_promote_action', 'node_unpromote_action');
 
     $test_user = $this->drupalCreateUser(array('administer actions'));
-    $web_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer nodes'));
+    $web_user = $this->drupalCreateUser(array('create page content', 'access content', 'publish content', 'administer nodes'));
     foreach ($content_actions as $action) {
       $hash = drupal_hash_base64($action);
       $info = $this->actionInfo($action);
