diff --git src/wp-includes/nav-menu-template.php src/wp-includes/nav-menu-template.php
index 592522878f..4b31148632 100644
--- src/wp-includes/nav-menu-template.php
+++ src/wp-includes/nav-menu-template.php
@@ -196,24 +196,35 @@ function wp_nav_menu( $args = array() ) {
 	_wp_menu_item_classes_by_context( $menu_items );
 
 	$sorted_menu_items        = array();
+	$menu_items_tree          = array();
 	$menu_items_with_children = array();
 	foreach ( (array) $menu_items as $menu_item ) {
 		$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
+		$menu_items_tree[ $menu_item->ID ]           = $menu_item->menu_item_parent;
 		if ( $menu_item->menu_item_parent ) {
-			$menu_items_with_children[ $menu_item->menu_item_parent ] = true;
+			$menu_items_with_children[ $menu_item->menu_item_parent ] = 1;
+		}
+	}
+
+	// Calculate the depth of each menu item with children
+	foreach ( $menu_items_with_children as $menu_item_key => &$menu_item_depth ) {
+		$menu_item_parent = $menu_items_tree[ $menu_item_key ];
+		while ( $menu_item_parent ) {
+			$menu_item_depth  = $menu_item_depth + 1;
+			$menu_item_parent = $menu_items_tree[ $menu_item_parent ];
 		}
 	}
 
 	// Add the menu-item-has-children class where applicable.
 	if ( $menu_items_with_children ) {
 		foreach ( $sorted_menu_items as &$menu_item ) {
-			if ( isset( $menu_items_with_children[ $menu_item->ID ] ) ) {
+			if ( isset( $menu_items_with_children[ $menu_item->ID ] ) && ( $args->depth <= 0 || $menu_items_with_children[ $menu_item->ID ] < $args->depth ) ) {
 				$menu_item->classes[] = 'menu-item-has-children';
 			}
 		}
 	}
 
-	unset( $menu_items, $menu_item );
+	unset( $menu_items_tree, $menu_items_with_children, $menu_items, $menu_item );
 
 	/**
 	 * Filters the sorted list of menu item objects before generating the menu's HTML.
diff --git tests/phpunit/tests/menu/wp-nav-menu.php tests/phpunit/tests/menu/wp-nav-menu.php
new file mode 100644
index 0000000000..9b5bee99f8
--- /dev/null
+++ tests/phpunit/tests/menu/wp-nav-menu.php
@@ -0,0 +1,148 @@
+<?php
+
+class Test_Wp_Nav_Menu extends WP_UnitTestCase {
+
+	protected $menu_id        = 0;
+	protected $lvl0_menu_item = 0;
+	protected $lvl1_menu_item = 0;
+	protected $lvl2_menu_item = 0;
+
+	public function set_up() {
+		parent::set_up();
+
+		// Create nav menu.
+		$this->menu_id = wp_create_nav_menu( 'test' );
+
+		// Create lvl0 menu item.
+		$this->lvl0_menu_item = wp_update_nav_menu_item(
+			$this->menu_id,
+			0,
+			array(
+				'menu-item-title'  => 'Root menu item',
+				'menu-item-url'    => '#',
+				'menu-item-status' => 'publish',
+			)
+		);
+
+		// Create lvl1 menu item.
+		$this->lvl1_menu_item = wp_update_nav_menu_item(
+			$this->menu_id,
+			0,
+			array(
+				'menu-item-title'     => 'Lvl1 menu item',
+				'menu-item-url'       => '#',
+				'menu-item-parent-id' => $this->lvl0_menu_item,
+				'menu-item-status'    => 'publish',
+			)
+		);
+
+		// Create lvl2 menu item.
+		$this->lvl2_menu_item = wp_update_nav_menu_item(
+			$this->menu_id,
+			0,
+			array(
+				'menu-item-title'     => 'Lvl2 menu item',
+				'menu-item-url'       => '#',
+				'menu-item-parent-id' => $this->lvl1_menu_item,
+				'menu-item-status'    => 'publish',
+			)
+		);
+	}
+
+	public function tear_down() {
+		wp_delete_nav_menu( $this->menu_id );
+		parent::tear_down();
+	}
+
+	/**
+	 * Test all menu items containing children have the CSS class `menu-item-has-children` when displaying the menu
+	 * without specifying a custom depth.
+	 *
+	 * @ticket 28620
+	 */
+	public function test_wp_nav_menu_all_depth() {
+
+		// Render the menu with all its hierarchy.
+		$menu_html = wp_nav_menu(
+			array(
+				'menu' => $this->menu_id,
+				'echo' => false,
+			)
+		);
+
+		// Level 0 should be present in the HTML output and have the `menu-item-has-children` class.
+		$this->assertStringContainsString(
+			sprintf(
+				'<li id="menu-item-%1$d" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-%1$d">',
+				$this->lvl0_menu_item
+			),
+			$menu_html
+		);
+
+		// Level 1 should be present in the HTML output and have the `menu-item-has-children` class.
+		$this->assertStringContainsString(
+			sprintf(
+				'<li id="menu-item-%1$d" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-%1$d">',
+				$this->lvl1_menu_item
+			),
+			$menu_html
+		);
+
+		// Level 2 should be present in the HTML output and not have the `menu-item-has-children` class since it has no
+		// children.
+		$this->assertStringContainsString(
+			sprintf(
+				'<li id="menu-item-%1$d" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-%1$d">',
+				$this->lvl2_menu_item
+			),
+			$menu_html
+		);
+	}
+
+	/**
+	 * Test when displaying a menu with a custom depth, the last menu item doesn't have the CSS class
+	 * `menu-item-has-children` even if it's the case when displaying the full menu.
+	 *
+	 * @ticket 28620
+	 */
+	public function test_wp_nav_menu_custom_depth() {
+
+		// Render the menu limited to 1 level of hierarchy (Lvl0 + Lvl1).
+		$menu_html = wp_nav_menu(
+			array(
+				'menu'  => $this->menu_id,
+				'depth' => 2,
+				'echo'  => false,
+			)
+		);
+
+		// Level 0 should be present in the HTML output and have the `menu-item-has-children` class.
+		$this->assertStringContainsString(
+			sprintf(
+				'<li id="menu-item-%1$d" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-%1$d">',
+				$this->lvl0_menu_item
+			),
+			$menu_html
+		);
+
+		// Level 1 should be present in the HTML output and not have the `menu-item-has-children` class since its the
+		// last item to be rendered.
+		$this->assertStringContainsString(
+			sprintf(
+				'<li id="menu-item-%1$d" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-%1$d">',
+				$this->lvl1_menu_item
+			),
+			$menu_html
+		);
+
+		// Level 2 should not be present in the HTML output.
+		$this->assertStringNotContainsString(
+			sprintf(
+				'<li id="menu-item-%d"',
+				$this->lvl2_menu_item
+			),
+			$menu_html
+		);
+	}
+}
+
