diff --git src/wp-admin/includes/class-readme-header-parser.php src/wp-admin/includes/class-readme-header-parser.php
new file mode 100644
index 0000000000..ed1e172359
--- /dev/null
+++ src/wp-admin/includes/class-readme-header-parser.php
@@ -0,0 +1,391 @@
+<?php
+/**
+ * Based on WordPress.org Plugin Readme Parser.
+ *
+ * Parses a local `readme.txt` file returning the sanitized headers.
+ *
+ * @since 5.2.0
+ */
+class WP_Readme_Header_Parser {
+
+	/**
+	 * @var string
+	 */
+	public $name = '';
+
+	/**
+	 * @var array
+	 */
+	public $tags = array();
+
+	/**
+	 * @var string
+	 */
+	public $requires = '';
+
+	/**
+	 * @var string
+	 */
+	public $tested = '';
+
+	/**
+	 * @var string
+	 */
+	public $requires_php = '';
+
+	/**
+	 * @var array
+	 */
+	public $contributors = array();
+
+	/**
+	 * @var string
+	 */
+	public $stable_tag = '';
+
+	/**
+	 * @var string
+	 */
+	public $donate_link = '';
+
+	/**
+	 * @var string
+	 */
+	public $short_description = '';
+
+	/**
+	 * @var string
+	 */
+	public $license = '';
+
+	/**
+	 * @var string
+	 */
+	public $license_uri = '';
+
+	/**
+	 * These are the valid header mappings for the header.
+	 *
+	 * @var array
+	 */
+	private $valid_headers = array(
+		'tested'            => 'tested',
+		'tested up to'      => 'tested',
+		'requires'          => 'requires',
+		'requires at least' => 'requires',
+		'requires php'      => 'requires_php',
+		'tags'              => 'tags',
+		'contributors'      => 'contributors',
+		'donate link'       => 'donate_link',
+		'stable tag'        => 'stable_tag',
+		'license'           => 'license',
+		'license uri'       => 'license_uri',
+	);
+
+	/**
+	 * These plugin tags are ignored.
+	 *
+	 * @var array
+	 */
+	private $ignore_tags = array(
+		'plugin',
+		'wordpress',
+	);
+
+	/**
+	 * @param string $file
+	 * @return array $headers
+	 */
+	public function parse_readme( $file ) {
+		$contents = file_get_contents( $file );
+		if ( preg_match( '!!u', $contents ) ) {
+			$contents = preg_split( '!\R!u', $contents );
+		} else {
+			$contents = preg_split( '!\R!', $contents ); // regex failed due to invalid UTF8 in $contents, see #2298
+		}
+		$contents = array_map( array( $this, 'strip_newlines' ), $contents );
+
+		// Strip UTF8 BOM if present.
+		if ( 0 === strpos( $contents[0], "\xEF\xBB\xBF" ) ) {
+			$contents[0] = substr( $contents[0], 3 );
+		}
+
+		// Convert UTF-16 files.
+		if ( 0 === strpos( $contents[0], "\xFF\xFE" ) ) {
+			foreach ( $contents as $i => $line ) {
+				$contents[ $i ] = mb_convert_encoding( $line, 'UTF-8', 'UTF-16' );
+			}
+		}
+
+		$line       = $this->get_first_nonwhitespace( $contents );
+		$this->name = $this->sanitize_text( trim( $line, "#= \t\0\x0B" ) );
+
+		// Strip Github style header\n==== underlines.
+		if ( ! empty( $contents ) && '' === trim( $contents[0], '=-' ) ) {
+			array_shift( $contents );
+		}
+
+		// Handle readme's which do `=== Plugin Name ===\nMy SuperAwesomePlugin Name\n...`
+		if ( 'plugin name' == strtolower( $this->name ) ) {
+			$this->name = $line = $this->get_first_nonwhitespace( $contents );
+
+			// Ensure that the line read wasn't an actual header or description.
+			if ( strlen( $line ) > 50 || preg_match( '~^(' . implode( '|', array_keys( $this->valid_headers ) ) . ')\s*:~i', $line ) ) {
+				$this->name = false;
+				array_unshift( $contents, $line );
+			}
+		}
+
+		// Parse headers.
+		$headers = array();
+
+		$line = $this->get_first_nonwhitespace( $contents );
+		do {
+			$value = null;
+			if ( false === strpos( $line, ':' ) ) {
+
+				// Some plugins have line-breaks within the headers.
+				if ( empty( $line ) ) {
+					break;
+				} else {
+					continue;
+				}
+			}
+
+			$bits                = explode( ':', trim( $line ), 2 );
+			list( $key, $value ) = $bits;
+			$key                 = strtolower( trim( $key, " \t*-\r\n" ) );
+			if ( isset( $this->valid_headers[ $key ] ) ) {
+				$headers[ $this->valid_headers[ $key ] ] = trim( $value );
+			}
+		} while ( ( $line = array_shift( $contents ) ) !== null );
+		array_unshift( $contents, $line );
+
+		if ( ! empty( $headers['tags'] ) ) {
+			$this->tags = explode( ',', $headers['tags'] );
+			$this->tags = array_map( 'trim', $this->tags );
+			$this->tags = array_filter( $this->tags );
+			$this->tags = array_diff( $this->tags, $this->ignore_tags );
+			$this->tags = array_slice( $this->tags, 0, 5 );
+		}
+		if ( ! empty( $headers['requires'] ) ) {
+			$this->requires = $this->sanitize_requires_version( $headers['requires'] );
+		}
+		if ( ! empty( $headers['tested'] ) ) {
+			$this->tested = $this->sanitize_tested_version( $headers['tested'] );
+		}
+		if ( ! empty( $headers['requires_php'] ) ) {
+			$this->requires_php = $this->sanitize_requires_php( $headers['requires_php'] );
+		}
+		if ( ! empty( $headers['contributors'] ) ) {
+			$this->contributors = explode( ',', $headers['contributors'] );
+			$this->contributors = array_map( 'trim', $this->contributors );
+			$this->contributors = $this->sanitize_contributors( $this->contributors );
+		}
+		if ( ! empty( $headers['stable_tag'] ) ) {
+			$this->stable_tag = $this->sanitize_stable_tag( $headers['stable_tag'] );
+		}
+		if ( ! empty( $headers['donate_link'] ) ) {
+			$this->donate_link = $headers['donate_link'];
+		}
+		if ( ! empty( $headers['license'] ) ) {
+			// Handle the many cases of "License: GPLv2 - http://..."
+			if ( empty( $headers['license_uri'] ) && preg_match( '!(https?://\S+)!i', $headers['license'], $url ) ) {
+				$headers['license_uri'] = $url[1];
+				$headers['license']     = trim( str_replace( $url[1], '', $headers['license'] ), " -*\t\n\r\n" );
+			}
+			$this->license = $headers['license'];
+		}
+		if ( ! empty( $headers['license_uri'] ) ) {
+			$this->license_uri = $headers['license_uri'];
+		}
+
+		return $headers;
+	}
+
+	/**
+	 * @access protected
+	 *
+	 * @param string $contents
+	 * @return string
+	 */
+	protected function get_first_nonwhitespace( &$contents ) {
+		while ( ( $line = array_shift( $contents ) ) !== null ) {
+			$trimmed = trim( $line );
+			if ( ! empty( $trimmed ) ) {
+				break;
+			}
+		}
+
+		return $line;
+	}
+
+	/**
+	 * @access protected
+	 *
+	 * @param string $line
+	 * @return string
+	 */
+	protected function strip_newlines( $line ) {
+		return rtrim( $line, "\r\n" );
+	}
+
+	/**
+	 * @access protected
+	 *
+	 * @param string $text
+	 * @return string
+	 */
+	protected function sanitize_text( $text ) {
+		// not fancy
+		$text = strip_tags( $text );
+		$text = esc_html( $text );
+		$text = trim( $text );
+
+		return $text;
+	}
+
+	/**
+	 * Sanitize provided contributors to valid WordPress users
+	 *
+	 * @param array $users Array of user_login's or user_nicename's.
+	 * @return array Array of user_logins.
+	 */
+	protected function sanitize_contributors( $users ) {
+		foreach ( $users as $i => $name ) {
+			// Contributors should be listed by their WordPress.org Login name (Example: 'Joe Bloggs')
+			$user = get_user_by( 'login', $name );
+
+			// Or failing that, by their user_nicename field (Example: 'joe-bloggs')
+			if ( ! $user ) {
+				$user = get_user_by( 'slug', $name );
+			}
+
+			// In the event that something invalid is used, we'll ignore it (Example: 'Joe Bloggs (Australian Translation)')
+			if ( ! $user ) {
+				unset( $users[ $i ] );
+				$this->warnings['contributor_ignored'] = true;
+				continue;
+			}
+
+			// Overwrite whatever the author has specified with the sanitized nicename.
+			$users[ $i ] = $user->user_nicename;
+		}
+
+		return $users;
+	}
+
+	/**
+	 * Sanitize the provided stable tag to something we expect.
+	 *
+	 * @param string $stable_tag the raw Stable Tag line from the readme.
+	 * @return string The sanitized $stable_tag.
+	 */
+	protected function sanitize_stable_tag( $stable_tag ) {
+		$stable_tag = trim( $stable_tag );
+		$stable_tag = trim( $stable_tag, '"\'' ); // "trunk"
+		$stable_tag = preg_replace( '!^/?tags/!i', '', $stable_tag ); // "tags/1.2.3"
+		$stable_tag = preg_replace( '![^a-z0-9_.-]!i', '', $stable_tag );
+
+		// If the stable_tag begins with a ., we treat it as 0.blah.
+		if ( '.' == substr( $stable_tag, 0, 1 ) ) {
+			$stable_tag = "0{$stable_tag}";
+		}
+
+		return $stable_tag;
+	}
+
+	/**
+	 * Sanitizes the Requires PHP header to ensure that it's a valid version header.
+	 *
+	 * @param string $version
+	 * @return string The sanitized $version
+	 */
+	protected function sanitize_requires_php( $version ) {
+		$version = trim( $version );
+
+		// x.y or x.y.z
+		if ( $version && ! preg_match( '!^\d+(\.\d+){1,2}$!', $version ) ) {
+			$this->warnings['requires_php_header_ignored'] = true;
+			// Ignore the readme value.
+			$version = '';
+		}
+
+		return $version;
+	}
+
+	/**
+	 * Sanitizes the Tested header to ensure that it's a valid version header.
+	 *
+	 * @param string $version
+	 * @return string The sanitized $version
+	 */
+	protected function sanitize_tested_version( $version ) {
+		$version = trim( $version );
+
+		if ( $version ) {
+
+			// Handle the edge-case of 'WordPress 5.0' and 'WP 5.0' for historical purposes.
+			$strip_phrases = array(
+				'WordPress',
+				'WP',
+			);
+			$version       = trim( str_ireplace( $strip_phrases, '', $version ) );
+
+			// Strip off any -alpha, -RC, -beta suffixes, as these complicate comparisons and are rarely used.
+			list( $version, ) = explode( '-', $version );
+
+			if (
+				// x.y or x.y.z
+				! preg_match( '!^\d+\.\d(\.\d+)?$!', $version ) ||
+				// Allow plugins to mark themselves as compatible with Stable+0.1 (trunk/master) but not higher
+				defined( 'WP_CORE_STABLE_BRANCH' ) && ( (float) $version > (float) WP_CORE_STABLE_BRANCH + 0.1 )
+			) {
+				$this->warnings['tested_header_ignored'] = true;
+				// Ignore the readme value.
+				$version = '';
+			}
+		}
+
+		return $version;
+	}
+
+	/**
+	 * Sanitizes the Requires at least header to ensure that it's a valid version header.
+	 *
+	 * @param string $version
+	 * @return string The sanitized $version
+	 */
+	protected function sanitize_requires_version( $version ) {
+		$version = trim( $version );
+
+		if ( $version ) {
+
+			// Handle the edge-case of 'WordPress 5.0' and 'WP 5.0' for historical purposes.
+			$strip_phrases = array(
+				'WordPress',
+				'WP',
+				'or higher',
+				'and above',
+				'+',
+			);
+			$version       = trim( str_ireplace( $strip_phrases, '', $version ) );
+
+			// Strip off any -alpha, -RC, -beta suffixes, as these complicate comparisons and are rarely used.
+			list( $version, ) = explode( '-', $version );
+
+			if (
+				// x.y or x.y.z
+				! preg_match( '!^\d+\.\d(\.\d+)?$!', $version ) ||
+				// Allow plugins to mark themselves as requireing Stable+0.1 (trunk/master) but not higher
+				defined( 'WP_CORE_STABLE_BRANCH' ) && ( (float) $version > (float) WP_CORE_STABLE_BRANCH + 0.1 )
+			) {
+				$this->warnings['requires_header_ignored'] = true;
+				// Ignore the readme value.
+				$version = '';
+			}
+		}
+
+		return $version;
+	}
+}
diff --git src/wp-admin/includes/plugin.php src/wp-admin/includes/plugin.php
index 05e3861f17..3ac2b94d93 100644
--- src/wp-admin/includes/plugin.php
+++ src/wp-admin/includes/plugin.php
@@ -31,6 +31,8 @@
  *     Network: Optional. Specify "Network: true" to require that a plugin is activated
  *          across all sites in an installation. This will prevent a plugin from being
  *          activated on a single site when Multisite is enabled.
+ *     Requires WP: Optional. Specify the minimum required WordPress version.
+ *     Requires PHP: Optional. Specify the minimum required PHP version.
  *      * / # Remove the space to close comment
  *
  * Some users have issues with opening large files and manipulating the contents
@@ -46,6 +48,7 @@
  * reading.
  *
  * @since 1.5.0
+ * @since 5.2.0 Added `RequiresWP` and `RequiresPHP`.
  *
  * @param string $plugin_file Absolute path to the main plugin file.
  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
@@ -63,6 +66,8 @@
  *     @type string $TextDomain  Plugin textdomain.
  *     @type string $DomainPath  Plugins relative directory path to .mo files.
  *     @type bool   $Network     Whether the plugin can only be activated network-wide.
+ *     @type string $RequiresWP  Minimum required version of WordPress.
+ *     @type string $RequiresPHP Minimum required version of PHP.
  * }
  */
 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
@@ -77,6 +82,8 @@ function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
 		'TextDomain'  => 'Text Domain',
 		'DomainPath'  => 'Domain Path',
 		'Network'     => 'Network',
+		'RequiresWP'  => 'Requires WP',
+		'RequiresPHP' => 'Requires PHP',
 		// Site Wide Only is deprecated in favor of Network.
 		'_sitewide'   => 'Site Wide Only',
 	);
@@ -213,6 +220,43 @@ function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup
 	return $plugin_data;
 }
 
+/**
+ * Get and return plugin data used for validation.
+ *
+ * @uses `wp-admin/includes/class-readme-header-parser.php to parse local `readme.txt`.
+ * Alternately see if a plugin header `Requires WP` or `Requires PHP` exists and use that.
+ *
+ * @since 5.2.0
+ * @see validate_plugin_requirements()
+ *
+ * @param string $plugin_file Path to the plugin file relative to the plugins directory.
+ *
+ * @return object $plugin_data Object of plugin data for validation.
+ */
+function get_plugin_validation_data( $plugin_file ) {
+	$plugin_data = null;
+	require_once ABSPATH . '/wp-admin/includes/class-readme-header-parser.php';
+	$readme_file = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ) . '/readme.txt';
+	if ( file_exists( $readme_file ) ) {
+		$parser      = new WP_Readme_Header_Parser();
+		$plugin_data = (object) $parser->parse_readme( $readme_file );
+	}
+
+	/*
+	 * Plugin has no `readme.txt` file but might have
+	 * `Requires WP` and/or `Requires PHP` headers we can use.
+	 */
+	if ( null === $plugin_data ) {
+		$plugin_data               = new stdClass();
+		$plugin_data->file         = $plugin_file;
+		$plugin_headers            = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
+		$plugin_data->requires     = $plugin_headers['RequiresWP'];
+		$plugin_data->requires_php = $plugin_headers['RequiresPHP'];
+	}
+
+	return $plugin_data;
+}
+
 /**
  * Get a list of a plugin's files.
  *
@@ -675,6 +719,7 @@ function is_network_only_plugin( $plugin ) {
  * ensure that the success redirection will update the error redirection.
  *
  * @since 2.5.0
+ * @since 5.2.0 Test for WordPress version and PHP version compatibility.
  *
  * @param string $plugin       Path to the plugin file relative to the plugins directory.
  * @param string $redirect     Optional. URL to redirect to.
@@ -699,6 +744,10 @@ function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silen
 		return $valid;
 	}
 
+	if ( ! validate_plugin_requirements( $plugin ) ) {
+		return new WP_Error( 'plugin_activation_error', __( 'Plugin does not meet minimum WordPress and/or PHP requirements.' ) );
+	}
+
 	if ( ( $network_wide && ! isset( $current[ $plugin ] ) ) || ( ! $network_wide && ! in_array( $plugin, $current ) ) ) {
 		if ( ! empty( $redirect ) ) {
 			wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) ); // we'll override this later if the plugin can be included without fatal error
@@ -1199,6 +1248,24 @@ function validate_plugin( $plugin ) {
 	return 0;
 }
 
+/**
+ * Validate the plugin requirements for WP version and PHP version.
+ *
+ * @since 5.2.0
+ * @see activate_plugin()
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins directory.
+ *
+ * @return bool Default to true and if requirements met, false if not.
+ */
+function validate_plugin_requirements( $plugin ) {
+	$plugin_data  = get_plugin_validation_data( $plugin );
+	$wp_requires  = isset( $plugin_data->requires ) ? $plugin_data->requires : null;
+	$php_requires = isset( $plugin_data->requires_php ) ? $plugin_data->requires_php : null;
+
+	return is_wp_compatible( $wp_requires ) && is_php_compatible( $php_requires );
+}
+
 /**
  * Whether the plugin can be uninstalled.
  *
diff --git src/wp-includes/functions.php src/wp-includes/functions.php
index 214c134d01..b25bc6e74a 100644
--- src/wp-includes/functions.php
+++ src/wp-includes/functions.php
@@ -6830,3 +6830,30 @@ function wp_update_php_annotation() {
 	);
 	echo'</p>';
 }
+
+/**
+ * Check compatibility with current WordPress version.
+ *
+ * @since 5.2.0
+ *
+ * @param string $requires Minimum WordPress version from API.
+ *
+ * @return bool True if is compatible or empty, false if not.
+ */
+function is_wp_compatible( $requires ) {
+	$wp_version = get_bloginfo( 'version' );
+	return empty( $requires ) || version_compare( $wp_version, $requires, '>=' );
+}
+
+/**
+ * Check compatibility with current PHP version.
+ *
+ * @since 5.2.0
+ *
+ * @param string $requires Minimum PHP version from API.
+ *
+ * @return bool True if is compatible or empty, false if not.
+ */
+function is_php_compatible( $requires ) {
+	return empty( $requires ) || version_compare( phpversion(), $requires, '>=' );
+}
