Index: src/wp-admin/edit.php
===================================================================
--- src/wp-admin/edit.php	(revision 49022)
+++ src/wp-admin/edit.php	(working copy)
@@ -134,6 +134,11 @@
 			break;
 		case 'untrash':
 			$untrashed = 0;
+
+			if ( isset( $_GET['doaction'] ) && ( 'undo' === $_GET['doaction'] ) ) {
+				add_filter( 'wp_untrash_post_status', 'wp_untrash_post_set_previous_status', 10, 3 );
+			}
+
 			foreach ( (array) $post_ids as $post_id ) {
 				if ( ! current_user_can( 'delete_post', $post_id ) ) {
 					wp_die( __( 'Sorry, you are not allowed to restore this item from the Trash.' ) );
@@ -146,6 +151,9 @@
 				$untrashed++;
 			}
 			$sendback = add_query_arg( 'untrashed', $untrashed, $sendback );
+
+			remove_filter( 'wp_untrash_post_status', 'wp_untrash_post_set_previous_status', 10, 3 );
+
 			break;
 		case 'delete':
 			$deleted = 0;
@@ -419,6 +427,18 @@
 		$ids        = preg_replace( '/[^0-9,]/', '', $_REQUEST['ids'] );
 		$messages[] = '<a href="' . esc_url( wp_nonce_url( "edit.php?post_type=$post_type&doaction=undo&action=untrash&ids=$ids", 'bulk-posts' ) ) . '">' . __( 'Undo' ) . '</a>';
 	}
+
+	if ( 'untrashed' === $message && isset( $_REQUEST['ids'] ) ) {
+		$ids = explode( ',', $_REQUEST['ids'] );
+
+		if ( 1 === count( $ids ) && current_user_can( 'edit_post', $ids[0] ) ) {
+			$messages[] = sprintf(
+				'<a href="%1$s">%2$s</a>',
+				esc_url( get_edit_post_link( $ids[0] ) ),
+				esc_html( get_post_type_object( get_post_type( $ids[0] ) )->labels->edit_item )
+			);
+		}
+	}
 }
 
 if ( $messages ) {
Index: src/wp-admin/post.php
===================================================================
--- src/wp-admin/post.php	(revision 49022)
+++ src/wp-admin/post.php	(working copy)
@@ -291,7 +291,14 @@
 			wp_die( __( 'Error in restoring the item from Trash.' ) );
 		}
 
-		wp_redirect( add_query_arg( 'untrashed', 1, $sendback ) );
+		$sendback = add_query_arg(
+			array(
+				'untrashed' => 1,
+				'ids'       => $post_id,
+			),
+			$sendback
+		);
+		wp_redirect( $sendback );
 		exit;
 
 	case 'delete':
Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 49022)
+++ src/wp-includes/functions.php	(working copy)
@@ -1193,6 +1193,7 @@
 		'error',
 		'hotkeys_highlight_first',
 		'hotkeys_highlight_last',
+		'ids',
 		'locked',
 		'message',
 		'same',
Index: src/wp-includes/post.php
===================================================================
--- src/wp-includes/post.php	(revision 49022)
+++ src/wp-includes/post.php	(working copy)
@@ -3232,6 +3232,7 @@
  * Restore a post or page from the Trash.
  *
  * @since 2.9.0
+ * @since 5.6.0 An untrashed post is now returned to `draft` status by default.
  *
  * @param int $post_id Optional. Post ID. Default is ID of the global $post.
  * @return WP_Post|false|null Post data on success, false or null on failure.
@@ -3243,17 +3244,23 @@
 		return $post;
 	}
 
+	$post_id = $post->ID;
+
 	if ( 'trash' !== $post->post_status ) {
 		return false;
 	}
 
+	$previous_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
+
 	/**
 	 * Filters whether a post untrashing should take place.
 	 *
 	 * @since 4.9.0
+	 * @since 5.6.0 The `$previous_status` parameter was added.
 	 *
-	 * @param bool|null $untrash Whether to go forward with untrashing.
-	 * @param WP_Post   $post    Post object.
+	 * @param bool|null $untrash         Whether to go forward with untrashing.
+	 * @param WP_Post   $post            Post object.
+	 * @param string    $previous_status The status of the post at the point where it was trashed.
 	 */
 	$check = apply_filters( 'pre_untrash_post', null, $post );
 	if ( null !== $check ) {
@@ -3264,12 +3271,29 @@
 	 * Fires before a post is restored from the Trash.
 	 *
 	 * @since 2.9.0
+	 * @since 5.6.0 The `$previous_status` parameter was added.
 	 *
-	 * @param int $post_id Post ID.
+	 * @param int    $post_id         Post ID.
+	 * @param string $previous_status The status of the post at the point where it was trashed.
 	 */
-	do_action( 'untrash_post', $post_id );
+	do_action( 'untrash_post', $post_id, $previous_status );
 
-	$post_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
+	/**
+	 * Filter the status that a post gets when it is restored from the trash (untrashed).
+	 *
+	 * By default, posts that are restored will be assigned a status of 'draft'. Return the value of `$previous_status`
+	 * in order to assign the status that the post had before it was trashed. The `wp_untrash_post_set_previous_status()`
+	 * function is available for this.
+	 *
+	 * Prior to WordPress 5.6, restored posts were given their original status.
+	 *
+	 * @since 5.6.0
+	 *
+	 * @param string $new_status      The new status of the post being restored.
+	 * @param int    $post_id         The ID of the post being restored.
+	 * @param string $previous_status The status of the post at the point where it was trashed.
+	 */
+	$post_status = apply_filters( 'wp_untrash_post_status', 'draft', $post_id, $previous_status );
 
 	delete_post_meta( $post_id, '_wp_trash_meta_status' );
 	delete_post_meta( $post_id, '_wp_trash_meta_time' );
@@ -3291,10 +3315,12 @@
 	 * Fires after a post is restored from the Trash.
 	 *
 	 * @since 2.9.0
+	 * @since 5.6.0 The `$previous_status` parameter was added.
 	 *
-	 * @param int $post_id Post ID.
+	 * @param int    $post_id         Post ID.
+	 * @param string $previous_status The status of the post at the point where it was trashed.
 	 */
-	do_action( 'untrashed_post', $post_id );
+	do_action( 'untrashed_post', $post_id, $previous_status );
 
 	return $post;
 }
@@ -7495,3 +7521,19 @@
 	 */
 	return apply_filters( 'wp_get_original_image_url', $original_image_url, $attachment_id );
 }
+
+/**
+ * Filter callback which sets the status of an untrashed post to its previous status.
+ *
+ * This can be used as a callback on the `wp_untrash_post_status` filter.
+ *
+ * @since 5.6.0
+ *
+ * @param string $new_status      The new status of the post being restored.
+ * @param int    $post_id         The ID of the post being restored.
+ * @param string $previous_status The status of the post at the point where it was trashed.
+ * @return string The status of the post at the point where it was trashed.
+ */
+function wp_untrash_post_set_previous_status( $new_status, $post_id, $previous_status ) {
+	return $previous_status;
+}
Index: tests/phpunit/tests/post/wpInsertPost.php
===================================================================
--- tests/phpunit/tests/post/wpInsertPost.php	(revision 49022)
+++ tests/phpunit/tests/post/wpInsertPost.php	(working copy)
@@ -159,12 +159,58 @@
 		);
 
 		wp_untrash_post( $about_page_id );
+		wp_update_post(
+			array(
+				'ID'          => $about_page_id,
+				'post_status' => 'publish',
+			)
+		);
 
 		$this->assertSame( 'about', get_post( $another_about_page_id )->post_name );
 		$this->assertSame( 'about-2', get_post( $about_page_id )->post_name );
 	}
 
 	/**
+	 * @ticket 23022
+	 * @dataProvider data_various_post_statuses
+	 */
+	function test_untrashing_a_post_should_always_restore_it_to_draft_status( $post_status ) {
+		$page_id = self::factory()->post->create(
+			array(
+				'post_type'   => 'page',
+				'post_status' => $post_status,
+			)
+		);
+
+		wp_trash_post( $page_id );
+		wp_untrash_post( $page_id );
+
+		$this->assertSame( 'draft', get_post( $page_id )->post_status );
+	}
+
+	/**
+	 * @ticket 23022
+	 * @dataProvider data_various_post_statuses
+	 */
+	function test_wp_untrash_post_status_filter_restores_post_to_correct_status( $post_status ) {
+		add_filter( 'wp_untrash_post_status', 'wp_untrash_post_set_previous_status', 10, 3 );
+
+		$page_id = self::factory()->post->create(
+			array(
+				'post_type'   => 'page',
+				'post_status' => $post_status,
+			)
+		);
+
+		wp_trash_post( $page_id );
+		wp_untrash_post( $page_id );
+
+		remove_filter( 'wp_untrash_post_status', 'wp_untrash_post_set_previous_status', 10, 3 );
+
+		$this->assertSame( $post_status, get_post( $page_id )->post_status );
+	}
+
+	/**
 	 * Data for testing the ability for users to set the post slug.
 	 *
 	 * @return array Array of test arguments.
@@ -184,6 +230,28 @@
 	}
 
 	/**
+	 * Data for testing post statuses.
+	 *
+	 * @return array Array of test arguments.
+	 */
+	function data_various_post_statuses() {
+		return array(
+			array(
+				'draft',
+			),
+			array(
+				'pending',
+			),
+			array(
+				'private',
+			),
+			array(
+				'publish',
+			),
+		);
+	}
+
+	/**
 	 * Test contributor making changes to the pending post slug.
 	 *
 	 * @ticket 42464
