diff --git src/wp-admin/includes/class-core-upgrader.php src/wp-admin/includes/class-core-upgrader.php
index 7ef49f1f1c9..d7305589bf4 100644
--- src/wp-admin/includes/class-core-upgrader.php
+++ src/wp-admin/includes/class-core-upgrader.php
@@ -161,6 +161,7 @@ class Core_Upgrader extends WP_Upgrader {
 		}
 		$wp_filesystem->chmod( $wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE );
 
+		wp_opcache_invalidate( ABSPATH . 'wp-admin/includes/update-core.php' );
 		require_once ABSPATH . 'wp-admin/includes/update-core.php';
 
 		if ( ! function_exists( 'update_core' ) ) {
diff --git src/wp-admin/includes/file.php src/wp-admin/includes/file.php
index 08215e5d2b2..ec01eb799a5 100644
--- src/wp-admin/includes/file.php
+++ src/wp-admin/includes/file.php
@@ -498,9 +498,7 @@ function wp_edit_theme_plugin_file( $args ) {
 	if ( false === $written ) {
 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
 	}
-	if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) {
-		opcache_invalidate( $real_file, true );
-	}
+	wp_opcache_invalidate( $real_file, true );
 
 	if ( $is_active && 'php' === $extension ) {
 
@@ -608,9 +606,7 @@ function wp_edit_theme_plugin_file( $args ) {
 
 			// Roll-back file change.
 			file_put_contents( $real_file, $previous_content );
-			if ( function_exists( 'opcache_invalidate' ) ) {
-				opcache_invalidate( $real_file, true );
-			}
+			wp_opcache_invalidate( $real_file, true );
 
 			if ( ! isset( $result['message'] ) ) {
 				$message = __( 'Something went wrong.' );
@@ -1743,6 +1739,7 @@ function copy_dir( $from, $to, $skip_list = array() ) {
 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
 				}
 			}
+			wp_opcache_invalidate( $to . $filename );
 		} elseif ( 'd' === $fileinfo['type'] ) {
 			if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
 				if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
@@ -2277,3 +2274,75 @@ function wp_print_request_filesystem_credentials_modal() {
 	</div>
 	<?php
 }
+
+/**
+ * Attempt to clear the opcode cache for an individual PHP file.
+ *
+ * This function can be called safely without having to check the file extension
+ * or availability of the OPcache extension.
+ *
+ * Whether or not invalidation is possible is cached to improve performance.
+ *
+ * @since 5.5
+ *
+ * @link https://www.php.net/manual/en/function.opcache-invalidate.php
+ *
+ * @param string $filepath Path to the file, including extension, for which the opcode cache is to be cleared.
+ * @param bool   $force    Invalidate even if the modification time is not newer than the file in cache. Default `false`.
+ *
+ * @return bool `true` if opcache was invalidated for `$filepath`, or there was nothing to invalidate.
+ *              `false` if opcache invalidation is not available, or is disabled via filter.
+ */
+function wp_opcache_invalidate( $filepath, $force = false ) {
+	static $can_invalidate = null;
+
+	/*
+	 * Check to see if WordPress is able to run `opcache_invalidate()` or not, and cache the value.
+	 *
+	 * First, check to see if the function is available to call, then if the host has restricted
+	 * the ability to run the function to avoid a PHP warning.
+	 *
+	 * `opcache.restrict_api` can specify the path for files allowed to call `opcache_invalidate()`.
+	 *
+	 * If the host has this set, check whether the path in `opcache.restrict_api` matches
+	 * the beginning of the path of the origin file.
+	 *
+	 * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but
+	 * `realpath()` is necessary because `SCRIPT_FILENAME` can be a relative path
+	 * when run from CLI.
+	 *
+	 * For more details, see:
+	 * - https://www.php.net/manual/en/opcache.configuration.php
+	 * - https://www.php.net/manual/en/reserved.variables.server.php
+	 * - https://core.trac.wordpress.org/ticket/36455
+	 */
+	if ( $can_invalidate === null ) {
+		$can_invalidate = function_exists( 'opcache_invalidate' ) &&
+			( ! ini_get( 'opcache.restrict_api' ) ||
+				stripos( realpath( $_SERVER['SCRIPT_FILENAME'] ), ini_get( 'opcache.restrict_api' ) ) === 0 );
+	}
+
+	// If invalidation is not available, return early.
+	if ( ! $can_invalidate ) {
+		return false;
+	}
+
+	// Verify that file to be invalidated has a PHP extension.
+	if ( ! preg_match( '/\.(?:php)$/i', $filepath ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters whether to invalidate a file from the opcode cache.
+	 *
+	 * @since 5.5
+	 *
+	 * @param bool   $will_invalidate Whether WordPress will invalidate `$filename`. Default `true`.
+	 * @param string $filename        The PHP filename to invalidate.
+	 */
+	if ( apply_filters( 'wp_opcache_invalidate_file', true, $filepath ) ) {
+		return opcache_invalidate( $filepath, $force );
+	}
+
+	return false;
+}
diff --git src/wp-admin/includes/update-core.php src/wp-admin/includes/update-core.php
index 935f1a73036..d7af50f3ebe 100644
--- src/wp-admin/includes/update-core.php
+++ src/wp-admin/includes/update-core.php
@@ -1318,14 +1318,18 @@ function update_core( $from, $to ) {
  *
  * Assumes that WP_Filesystem() has already been called and setup.
  *
- * This is a temporary function for the 3.1 -> 3.2 upgrade, as well as for those
- * upgrading to 3.7+.
+ * This is a standalone copy of the `copy_dir()` function that is used to
+ * upgrade the core files. It is placed here so that the version of this
+ * function from the *new* WordPress version will be called.
+ *
+ * It was initially added for the 3.1 -> 3.2 upgrade.
  *
  * @ignore
  * @since 3.2.0
  * @since 3.7.0 Updated not to use a regular expression for the skip list.
  *
  * @see copy_dir()
+ * @link https://core.trac.wordpress.org/ticket/17173
  *
  * @global WP_Filesystem_Base $wp_filesystem
  *
@@ -1355,6 +1359,11 @@ function _copy_dir( $from, $to, $skip_list = array() ) {
 					return new WP_Error( 'copy_failed__copy_dir', __( 'Could not copy file.' ), $to . $filename );
 				}
 			}
+
+			// `wp_opcache_invalidate()` only exists in WordPress 5.5, so don't run it when upgrading to 5.5.
+			if ( function_exists( 'wp_opcache_invalidate') ) {
+				wp_opcache_invalidate( $to . $filename );
+			}
 		} elseif ( 'd' === $fileinfo['type'] ) {
 			if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
 				if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
