diff --git a/includes/install.core.inc b/includes/install.core.inc
index 608ed5608eb284558b69aa3cc208e996fb0cae45..7405f9fd63bf63a7b3c60af71f4b9ca015a1e8ab 100644
--- a/includes/install.core.inc
+++ b/includes/install.core.inc
@@ -711,8 +711,10 @@ function install_display_output($output, $install_state) {
  *
  * @return
  *   A themed status report, or an exception if there are requirement errors.
- *   Otherwise, no output is returned, so that the next task can be run
- *   in the same page request.
+ *   If there are only requirement warnings, a themed status report is shown
+ *   initially, but the user is allowed to bypass it by providing 'continue=1'
+ *   in the URL. Otherwise, no output is returned, so that the next task can be
+ *   run in the same page request.
  */
 function install_verify_requirements(&$install_state) {
   // Check the installation requirements for Drupal and this profile.
@@ -724,22 +726,30 @@ function install_verify_requirements(&$install_state) {
   // Check the severity of the requirements reported.
   $severity = drupal_requirements_severity($requirements);
 
-  if ($severity == REQUIREMENT_ERROR) {
+  // If there are errors, always display them. If there are only warnings, skip
+  // them if the user has provided a URL parameter acknowledging the warnings
+  // and indicating a desire to continue anyway. See drupal_requirements_url().
+  if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) {
     if ($install_state['interactive']) {
       drupal_set_title(st('Requirements problem'));
       $status_report = theme('status_report', array('requirements' => $requirements));
-      $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(request_uri())));
+      $status_report .= st('Check the messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(drupal_requirements_url($severity))));
       return $status_report;
     }
     else {
-      // Throw an exception showing all unmet requirements.
+      // Throw an exception showing any unmet requirements.
       $failures = array();
       foreach ($requirements as $requirement) {
+        // Skip warnings altogether for non-interactive installations; these
+        // proceed in a single request so there is no good opportunity (and no
+        // good method) to warn the user anyway.
         if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
           $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
         }
       }
-      throw new Exception(implode("\n\n", $failures));
+      if (!empty($failures)) {
+        throw new Exception(implode("\n\n", $failures));
+      }
     }
   }
 }
diff --git a/includes/install.inc b/includes/install.inc
index 089cdee8f56b4a1bcae4dd6a07ffc6b3e8f2b4ce..88a663a4f2ed63efdf98d8bf008641a7893389a9 100644
--- a/includes/install.inc
+++ b/includes/install.inc
@@ -999,7 +999,6 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) {
   }
 }
 
-
 /**
  * Send the user to a different installer page.
  *
@@ -1016,6 +1015,68 @@ function install_goto($path) {
   drupal_exit();
 }
 
+/**
+ * Returns the URL of the current script, with modified query parameters.
+ *
+ * This function can be called by low-level scripts (such as install.php and
+ * update.php) and returns the URL of the current script. Existing query
+ * parameters are preserved by default, but new ones can optionally be merged
+ * in.
+ *
+ * This function is used when the script must maintain certain query parameters
+ * over multiple page requests in order to work correctly. In such cases (for
+ * example, update.php, which requires the 'continue=1' parameter to remain in
+ * the URL throughout the update process if there are any requirement warnings
+ * that need to be bypassed), using this function to generate the URL for links
+ * to the next steps of the script ensures that the links will work correctly.
+ *
+ * @param $query
+ *   (optional) An array of query parameters to merge in to the existing ones.
+ *
+ * @return
+ *   The URL of the current script, with query parameters modified by the
+ *   passed-in $query. The URL is not sanitized, so it still needs to be run
+ *   through check_url() if it will be used as an HTML attribute value.
+ *
+ * @see drupal_requirements_url()
+ */
+function drupal_current_script_url($query = array()) {
+  $uri = $_SERVER['SCRIPT_NAME'];
+  $query = array_merge(drupal_get_query_parameters(), $query);
+  if (!empty($query)) {
+    $uri .= '?' . drupal_http_build_query($query);
+  }
+  return $uri;
+}
+
+/**
+ * Returns a URL for proceeding to the next page after a requirements problem.
+ *
+ * This function can be called by low-level scripts (such as install.php and
+ * update.php) and returns a URL that can be used to attempt to proceed to the
+ * next step of the script.
+ *
+ * @param $severity
+ *   The severity of the requirements problem, as returned by
+ *   drupal_requirements_severity().
+ *
+ * @return
+ *   A URL for attempting to proceed to the next step of the script. The URL is
+ *   not sanitized, so it still needs to be run through check_url() if it will
+ *   be used as an HTML attribute value.
+ *
+ * @see drupal_current_script_url()
+ */
+function drupal_requirements_url($severity) {
+  $query = array();
+  // If there are no errors, only warnings, append 'continue=1' to the URL so
+  // the user can bypass this screen on the next page load.
+  if ($severity == REQUIREMENT_WARNING) {
+    $query['continue'] = 1;
+  }
+  return drupal_current_script_url($query);
+}
+
 /**
  * Functional equivalent of t(), used when some systems are not available.
  *
diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info
new file mode 100644
index 0000000000000000000000000000000000000000..04bf73ca8b4346713e9d60eb20d51f2f22c4e1a1
--- /dev/null
+++ b/modules/simpletest/tests/update_script_test.info
@@ -0,0 +1,6 @@
+name = "Update script test"
+description = "Support module for update script testing."
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
diff --git a/modules/simpletest/tests/update_script_test.install b/modules/simpletest/tests/update_script_test.install
new file mode 100644
index 0000000000000000000000000000000000000000..8af516bc0a3beeadbdbdb7d9d4a18cb487c627df
--- /dev/null
+++ b/modules/simpletest/tests/update_script_test.install
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the update_script_test module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function update_script_test_requirements($phase) {
+  $requirements = array();
+
+  if ($phase == 'update') {
+    // Set a requirements warning or error when the test requests it.
+    $requirement_type = variable_get('update_script_test_requirement_type');
+    switch ($requirement_type) {
+      case REQUIREMENT_WARNING:
+        $requirements['update_script_test'] = array(
+          'title' => 'Update script test',
+          'value' => 'Warning',
+          'description' => 'This is a requirements warning provided by the update_script_test module.',
+          'severity' => REQUIREMENT_WARNING,
+        );
+        break;
+      case REQUIREMENT_ERROR:
+        $requirements['update_script_test'] = array(
+          'title' => 'Update script test',
+          'value' => 'Error',
+          'description' => 'This is a requirements error provided by the update_script_test module.',
+          'severity' => REQUIREMENT_ERROR,
+        );
+        break;
+    }
+  }
+
+  return $requirements;
+}
+
+/**
+ * Dummy update function to run during the tests.
+ */
+function update_script_test_update_8000() {
+  return t('The update_script_test_update_8000() update was executed successfully.');
+}
diff --git a/modules/simpletest/tests/update_script_test.module b/modules/simpletest/tests/update_script_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..b3d9bbc7f3711e882119cd6b3af051245d859d04
--- /dev/null
+++ b/modules/simpletest/tests/update_script_test.module
@@ -0,0 +1 @@
+<?php
diff --git a/modules/system/system.test b/modules/system/system.test
index 1dd2349f7007e7ce75bc50aa09fff6d7c38f8026..f84bc781785c8b6c02d3c2527e538481cae05de0 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -2090,7 +2090,7 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp();
+    parent::setUp('update_script_test');
     $this->update_url = $GLOBALS['base_url'] . '/update.php';
     $this->update_user = $this->drupalCreateUser(array('administer software updates'));
   }
@@ -2126,6 +2126,49 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
     $this->assertResponse(200);
   }
 
+  /**
+   * Tests that requirements warnings and errors are correctly displayed.
+   */
+  function testRequirements() {
+    $this->drupalLogin($this->update_user);
+
+    // If there are no requirements warnings or errors, we expect to be able to
+    // go through the update process uninterrupted.
+    $this->drupalGet($this->update_url, array('external' => TRUE));
+    $this->drupalPost(NULL, array(), t('Continue'));
+    $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
+
+    // If there is a requirements warning, we expect it to be initially
+    // displayed, but clicking the link to proceed should allow us to go
+    // through the rest of the update process uninterrupted. (First run this
+    // test with pending updates to make sure they can be run successfully;
+    // then try again without pending updates to make sure that works too.)
+    variable_set('update_script_test_requirement_type', REQUIREMENT_WARNING);
+    drupal_set_installed_schema_version('update_script_test', drupal_get_installed_schema_version('update_script_test') - 1);
+    $this->drupalGet($this->update_url, array('external' => TRUE));
+    $this->assertText('This is a requirements warning provided by the update_script_test module.');
+    $this->clickLink('try again');
+    $this->assertNoText('This is a requirements warning provided by the update_script_test module.');
+    $this->drupalPost(NULL, array(), t('Continue'));
+    $this->drupalPost(NULL, array(), t('Apply pending updates'));
+    $this->assertText(t('The update_script_test_update_8000() update was executed successfully.'), t('End of update process was reached.'));
+    $this->drupalGet($this->update_url, array('external' => TRUE));
+    $this->assertText('This is a requirements warning provided by the update_script_test module.');
+    $this->clickLink('try again');
+    $this->assertNoText('This is a requirements warning provided by the update_script_test module.');
+    $this->drupalPost(NULL, array(), t('Continue'));
+    $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
+
+    // If there is a requirements error, it should be displayed even after
+    // clicking the link to proceed (since the problem that triggered the error
+    // has not been fixed).
+    variable_set('update_script_test_requirement_type', REQUIREMENT_ERROR);
+    $this->drupalGet($this->update_url, array('external' => TRUE));
+    $this->assertText('This is a requirements error provided by the update_script_test module.');
+    $this->clickLink('try again');
+    $this->assertText('This is a requirements error provided by the update_script_test module.');
+  }
+
   /**
    * Tests the effect of using the update script on the theme system.
    */
diff --git a/update.php b/update.php
index fb88a1ef48a97d8df9de83f492abda4f9017a605..1d6bc49c10869197264788571698783a20b24cf2 100644
--- a/update.php
+++ b/update.php
@@ -245,7 +245,8 @@ function update_info_page() {
   $output .= "<li>Install your new files in the appropriate location, as described in the handbook.</li>\n";
   $output .= "</ol>\n";
   $output .= "<p>When you have performed the steps above, you may proceed.</p>\n";
-  $output .= '<form method="post" action="update.php?op=selection&amp;token=' . $token . '"><p><input type="submit" value="Continue" class="form-submit" /></p></form>';
+  $form_action = check_url(drupal_current_script_url(array('op' => 'selection', 'token' => $token)));
+  $output .= '<form method="post" action="' . $form_action . '"><p><input type="submit" value="Continue" class="form-submit" /></p></form>';
   $output .= "\n";
   return $output;
 }
@@ -316,20 +317,26 @@ function update_extra_requirements($requirements = NULL) {
 }
 
 /**
- * Check update requirements and report any errors.
+ * Check update requirements and report any errors or (optionally) warnings.
+ *
+ * @param $skip_warnings
+ *   (optional) If set to TRUE, requirement warnings will be ignored, and a
+ *   report will only be issued if there are requirement errors. Defaults to
+ *   FALSE.
  */
-function update_check_requirements() {
+function update_check_requirements($skip_warnings = FALSE) {
   // Check requirements of all loaded modules.
   $requirements = module_invoke_all('requirements', 'update');
   $requirements += update_extra_requirements();
   $severity = drupal_requirements_severity($requirements);
 
-  // If there are issues, report them.
-  if ($severity == REQUIREMENT_ERROR) {
+  // If there are errors, always display them. If there are only warnings, skip
+  // them if the caller has indicated they should be skipped.
+  if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && !$skip_warnings)) {
     update_task_list('requirements');
     drupal_set_title('Requirements problem');
     $status_report = theme('status_report', array('requirements' => $requirements));
-    $status_report .= 'Check the error messages and <a href="' . check_url(request_uri()) . '">try again</a>.';
+    $status_report .= 'Check the messages and <a href="' . check_url(drupal_requirements_url($severity)) . '">try again</a>.';
     print theme('update_page', array('content' => $status_report));
     exit();
   }
@@ -375,8 +382,9 @@ function update_check_requirements() {
   // Set up theme system for the maintenance page.
   drupal_maintenance_theme();
 
-  // Check the update requirements for Drupal.
-  update_check_requirements();
+  // Check the update requirements for Drupal. Only report on errors at this
+  // stage, since the real requirements check happens further down.
+  update_check_requirements(TRUE);
 
   // Redirect to the update information page if all requirements were met.
   install_goto('update.php?op=info');
@@ -408,8 +416,12 @@ function update_check_requirements() {
 
   update_fix_compatibility();
 
-  // Check the update requirements for all modules.
-  update_check_requirements();
+  // Check the update requirements for all modules. If there are warnings, but
+  // no errors, skip reporting them if the user has provided a URL parameter
+  // acknowledging the warnings and indicating a desire to continue anyway. See
+  // drupal_requirements_url().
+  $skip_warnings = !empty($_GET['continue']);
+  update_check_requirements($skip_warnings);
 
   $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
   switch ($op) {
@@ -423,7 +435,12 @@ function update_check_requirements() {
 
     case 'Apply pending updates':
       if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) {
-        update_batch($_POST['start'], $base_url . '/update.php?op=results', $base_url . '/update.php');
+        // Generate absolute URLs for the batch processing (using $base_root),
+        // since the batch API will pass them to url() which does not handle
+        // update.php correctly by default.
+        $batch_url = $base_root . drupal_current_script_url();
+        $redirect_url = $base_root . drupal_current_script_url(array('op' => 'results'));
+        update_batch($_POST['start'], $redirect_url, $batch_url);
         break;
       }