diff --git src/wp-includes/class-wp-comment-query.php src/wp-includes/class-wp-comment-query.php
index 25b7fcf1a8..67f165ea07 100644
--- src/wp-includes/class-wp-comment-query.php
+++ src/wp-includes/class-wp-comment-query.php
@@ -257,6 +257,7 @@ class WP_Comment_Query {
 	 *                                                   Default true.
 	 *     @type bool         $update_comment_post_cache Whether to prime the cache for comment posts.
 	 *                                                   Default false.
+	 *     @type bool         $do_comment_feed_filters   Whether do comment feed filters. Default false.
 	 * }
 	 */
 	public function __construct( $query = '' ) {
@@ -305,6 +306,7 @@ class WP_Comment_Query {
 			'cache_domain' => 'core',
 			'update_comment_meta_cache' => true,
 			'update_comment_post_cache' => false,
+			'do_comment_feed_filters' => false,
 		);
 
 		if ( ! empty( $query ) ) {
@@ -481,7 +483,7 @@ class WP_Comment_Query {
 	 * @global wpdb $wpdb WordPress database abstraction object.
 	 */
 	protected function get_comment_ids() {
-		global $wpdb;
+		global $wp_query, $wpdb;
 
 		// Assemble clauses related to 'comment_approved'.
 		$approved_clauses = array();
@@ -649,6 +651,18 @@ class WP_Comment_Query {
 			}
 		}
 
+		if ( $this->query_vars['do_comment_feed_filters'] ) {
+			/**
+			 * Filters the LIMIT clause of the comments feed query before sending.
+			 *
+			 * @since 2.8.0
+			 *
+			 * @param string $climits The JOIN clause of the query.
+			 * @param WP_Query $wp_query The WP_Query global.
+			 */
+			$limits = apply_filters( 'comment_feed_limits', $limits, $wp_query );
+		}
+
 		if ( $this->query_vars['count'] ) {
 			$fields = 'COUNT(*)';
 		} else {
@@ -841,6 +855,18 @@ class WP_Comment_Query {
 			}
 		}
 
+		if ( $this->query_vars['do_comment_feed_filters'] ) {
+			/**
+			 * Filters the LIMIT clause of the comments feed query before sending.
+			 *
+			 * @since 2.8.0
+			 *
+			 * @param string $climits The JOIN clause of the query.
+			 * @param WP_Query $wp_query The WP_Query global.
+			 */
+			$join = apply_filters( 'comment_feed_join', $join, $wp_query );
+		}
+
 		if ( ! empty( $this->query_vars['date_query'] ) && is_array( $this->query_vars['date_query'] ) ) {
 			$this->date_query = new WP_Date_Query( $this->query_vars['date_query'], 'comment_date' );
 			$this->sql_clauses['where']['date_query'] = preg_replace( '/^\s*AND\s*/', '', $this->date_query->get_sql() );
@@ -868,14 +894,50 @@ class WP_Comment_Query {
 
 		$this->filtered_where_clause = $where;
 
+		if ( $this->query_vars['do_comment_feed_filters'] ) {
+			/**
+			 * Filters the WHERE clause of the comments feed query before sending.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param string $cwhere The WHERE clause of the query.
+			 * @param WP_Query $wp_query The WP_Query global.
+			 */
+			$where = apply_filters( 'comment_feed_where', $where, $wp_query );
+		}
+
 		if ( $where ) {
 			$where = 'WHERE ' . $where;
 		}
 
+		if ( $this->query_vars['do_comment_feed_filters'] ) {
+			/**
+			 * Filters the GROUP BY clause of the comments feed query before sending.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param string $cgroupby The GROUP BY clause of the query.
+			 * @param WP_Query $wp_query The WP_Query global.
+			 */
+			$groupby = apply_filters( 'comment_feed_groupby', $groupby, $wp_query );
+		}
+
 		if ( $groupby ) {
 			$groupby = 'GROUP BY ' . $groupby;
 		}
 
+		if ( $this->query_vars['do_comment_feed_filters'] ) {
+			/**
+			 * Filters the ORDER BY clause of the comments feed query before sending.
+			 *
+			 * @since 2.8.0
+			 *
+			 * @param string $corderby The ORDER BY clause of the query.
+			 * @param WP_Query $wp_query The WP_Query global.
+			 */
+			$orderby = apply_filters( 'comment_feed_orderby', $orderby, $wp_query );
+		}
+
 		if ( $orderby ) {
 			$orderby = "ORDER BY $orderby";
 		}
diff --git src/wp-includes/class-wp-query.php src/wp-includes/class-wp-query.php
index e20d574409..ef8b5631c5 100644
--- src/wp-includes/class-wp-query.php
+++ src/wp-includes/class-wp-query.php
@@ -2427,91 +2427,6 @@ class WP_Query {
 			$limits = 'LIMIT ' . $pgstrt . $q['posts_per_page'];
 		}
 
-		// Comments feeds
-		if ( $this->is_comment_feed && ! $this->is_singular ) {
-			if ( $this->is_archive || $this->is_search ) {
-				$cjoin = "JOIN {$wpdb->posts} ON ({$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID) $join ";
-				$cwhere = "WHERE comment_approved = '1' $where";
-				$cgroupby = "{$wpdb->comments}.comment_id";
-			} else { // Other non singular e.g. front
-				$cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
-				$cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1'";
-				$cgroupby = '';
-			}
-
-			if ( !$q['suppress_filters'] ) {
-				/**
-				 * Filters the JOIN clause of the comments feed query before sending.
-				 *
-				 * @since 2.2.0
-				 *
-				 * @param string   $cjoin The JOIN clause of the query.
-				 * @param WP_Query &$this The WP_Query instance (passed by reference).
-				 */
-				$cjoin = apply_filters_ref_array( 'comment_feed_join', array( $cjoin, &$this ) );
-
-				/**
-				 * Filters the WHERE clause of the comments feed query before sending.
-				 *
-				 * @since 2.2.0
-				 *
-				 * @param string   $cwhere The WHERE clause of the query.
-				 * @param WP_Query &$this  The WP_Query instance (passed by reference).
-				 */
-				$cwhere = apply_filters_ref_array( 'comment_feed_where', array( $cwhere, &$this ) );
-
-				/**
-				 * Filters the GROUP BY clause of the comments feed query before sending.
-				 *
-				 * @since 2.2.0
-				 *
-				 * @param string   $cgroupby The GROUP BY clause of the query.
-				 * @param WP_Query &$this    The WP_Query instance (passed by reference).
-				 */
-				$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( $cgroupby, &$this ) );
-
-				/**
-				 * Filters the ORDER BY clause of the comments feed query before sending.
-				 *
-				 * @since 2.8.0
-				 *
-				 * @param string   $corderby The ORDER BY clause of the query.
-				 * @param WP_Query &$this    The WP_Query instance (passed by reference).
-				 */
-				$corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
-
-				/**
-				 * Filters the LIMIT clause of the comments feed query before sending.
-				 *
-				 * @since 2.8.0
-				 *
-				 * @param string   $climits The JOIN clause of the query.
-				 * @param WP_Query &$this   The WP_Query instance (passed by reference).
-				 */
-				$climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option('posts_per_rss'), &$this ) );
-			}
-			$cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
-			$corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
-
-			$comments = (array) $wpdb->get_results("SELECT $distinct {$wpdb->comments}.* FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits");
-			// Convert to WP_Comment
-			$this->comments = array_map( 'get_comment', $comments );
-			$this->comment_count = count($this->comments);
-
-			$post_ids = array();
-
-			foreach ( $this->comments as $comment )
-				$post_ids[] = (int) $comment->comment_post_ID;
-
-			$post_ids = join(',', $post_ids);
-			$join = '';
-			if ( $post_ids ) {
-				$where = "AND {$wpdb->posts}.ID IN ($post_ids) ";
-			} else {
-				$where = "AND 0";
-			}
-		}
-
 		$pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' );
 
 		/*
@@ -2875,29 +2790,23 @@ class WP_Query {
 			$this->posts = apply_filters_ref_array( 'posts_results', array( $this->posts, &$this ) );
 		}
 
-		if ( !empty($this->posts) && $this->is_comment_feed && $this->is_singular ) {
-			/** This filter is documented in wp-includes/query.php */
-			$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
-
-			/** This filter is documented in wp-includes/query.php */
-			$cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
-
-			/** This filter is documented in wp-includes/query.php */
-			$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
-			$cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
-
-			/** This filter is documented in wp-includes/query.php */
-			$corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
-			$corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
+		if ( ! empty( $this->posts ) && $this->is_comment_feed ) {
+			$comment_args = array(
+				'orderby'                   => 'comment_date_gmt',
+				'order'                     => 'DESC',
+				'status'                    => 'approve',
+				'post__in'                  => wp_list_pluck( $this->posts, 'ID' ),
+				'no_found_rows'             => false,
+				'number'                    => get_option( 'posts_per_rss' ),
+				'update_comment_meta_cache' => false,
+				'do_comment_feed_filters'   => ! $q['suppress_filters'],
+			);
 
-			/** This filter is documented in wp-includes/query.php */
-			$climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option('posts_per_rss'), &$this ) );
+			$comment_args = apply_filters( 'comments_feed_query_args', $comment_args );
 
-			$comments_request = "SELECT {$wpdb->comments}.* FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
-			$comments = $wpdb->get_results($comments_request);
-			// Convert to WP_Comment
-			$this->comments = array_map( 'get_comment', $comments );
-			$this->comment_count = count($this->comments);
+			$comment_query       = new WP_Comment_Query( $comment_args );
+			$this->comments      = $comment_query->comments;
+			$this->comment_count = count( $this->comments );
 		}
 
 		// Check post status to determine if post should be displayed.
diff --git tests/phpunit/includes/utils.php tests/phpunit/includes/utils.php
index 96d9badaa1..75b3c897f5 100644
--- tests/phpunit/includes/utils.php
+++ tests/phpunit/includes/utils.php
@@ -202,7 +202,7 @@ function xml_find($tree /*, $el1, $el2, $el3, .. */) {
 		return $out;
 
 	for ($i=0; $i<count($tree); $i++) {
-#		echo "checking '{$tree[$i][name]}' == '{$a[0]}'\n";
+#		echo 'checking ' . $tree[$i]['name'] . " == '{$a[0]}'\n";
 #		var_dump($tree[$i]['name'], $a[0]);
 		if ($tree[$i]['name'] == $a[0]) {
 #			echo "n == {$n}\n";
@@ -408,7 +408,7 @@ class wpdb_exposed_methods_for_testing extends wpdb {
  */
 function benchmark_pcre_backtracking( $pattern, $subject, $strategy ) {
 	$saved_config = ini_get( 'pcre.backtrack_limit' );
-	
+
 	// Attempt to prevent PHP crashes.  Adjust these lower when needed.
 	if ( version_compare( phpversion(), '5.4.8', '>' ) ) {
 		$limit = 1000000;
@@ -420,7 +420,7 @@ function benchmark_pcre_backtracking( $pattern, $subject, $strategy ) {
 	for( $i = 4; $i <= $limit; $i *= 2 ) {
 
 		ini_set( 'pcre.backtrack_limit', $i );
-		
+
 		switch( $strategy ) {
 		case 'split':
 			preg_split( $pattern, $subject );
diff --git tests/phpunit/tests/feed/rss2.php tests/phpunit/tests/feed/rss2.php
index f0d7919812..3650bf3ad1 100644
--- tests/phpunit/tests/feed/rss2.php
+++ tests/phpunit/tests/feed/rss2.php
@@ -67,13 +67,28 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 
 	/**
 	 * This is a bit of a hack used to buffer feed content.
+	 *
+	 * @since 4.9.0 Introduced `$feed_type` parameter.
+	 *
+	 * @param string $feed_type Feed type. 'main' or 'comments'. Default 'main'.
+	 * @return string
 	 */
-	function do_rss2() {
+	function do_rss2( $feed_type = 'main' ) {
+		switch ( $feed_type ) {
+			case 'comments' :
+				$feed_template_path = ABSPATH . 'wp-includes/feed-rss2-comments.php';
+				break;
+
+			default :
+				$feed_template_path = ABSPATH . 'wp-includes/feed-rss2.php';
+				break;
+		}
+
 		ob_start();
 		// Nasty hack! In the future it would better to leverage do_feed( 'rss2' ).
 		global $post;
 		try {
-			@require(ABSPATH . 'wp-includes/feed-rss2.php');
+			@require( $feed_template_path );
 			$out = ob_get_clean();
 		} catch (Exception $e) {
 			$out = ob_get_clean();
@@ -360,7 +375,7 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 		$this->assertTrue( have_comments() );
 
 		// Check to see if we have the expected XML output from the feed template.
-		$feed = $this->do_rss2();
+		$feed = $this->do_rss2( 'comments' );
 
 		$xml = xml_to_array( $feed );
 
@@ -369,6 +384,11 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 
 		// There should only be one <rss> child element.
 		$this->assertEquals( 1, count( $rss ) );
+
+		// Comment limits obey 'posts_per_rss' count.
+		$comments = xml_find( $xml, 'rss', 'channel', 'item' );
+		$posts_per_rss = (int) get_option( 'posts_per_rss' );
+		$this->assertCount( $posts_per_rss, $comments );
 	}
 
 	/*
@@ -406,6 +426,11 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 	 * @ticket 30210
 	 */
 	function test_valid_single_post_comment_feed_endpoint() {
+		$comment_ids = self::factory()->comment->create_many( 3, array(
+			'comment_post_ID' => self::$posts[0],
+			'comment_approved' => '1',
+		) );
+
 		// An example of an valid date archive feed endpoint.
 		$this->go_to( get_post_comments_feed_link( self::$posts[0] ) );
 
@@ -416,7 +441,7 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 		$this->assertTrue( have_posts() );
 
 		// Check to see if we have the expected XML output from the feed template.
-		$feed = $this->do_rss2();
+		$feed = $this->do_rss2( 'comments' );
 
 		$xml = xml_to_array( $feed );
 
@@ -425,6 +450,10 @@ class Tests_Feeds_RSS2 extends WP_UnitTestCase {
 
 		// There should only be one <rss> child element.
 		$this->assertEquals( 1, count( $rss ) );
+
+		// There should be three comments.
+		$comments = xml_find( $xml, 'rss', 'channel', 'item' );
+		$this->assertCount( 3, $comments );
 	}
 
 	/*
