diff --git a/core/modules/system/src/EventSubscriber/GroupRouteSubscriber.php b/core/modules/system/src/EventSubscriber/GroupRouteSubscriber.php
new file mode 100644
index 0000000000..338359f9ba
--- /dev/null
+++ b/core/modules/system/src/EventSubscriber/GroupRouteSubscriber.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Drupal\system\EventSubscriber;
+
+use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Alters routes, which are parents for other routes.
+ */
+class GroupRouteSubscriber extends RouteSubscriberBase {
+
+  /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
+   * The menu link tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuLinkTree;
+
+  /**
+   * The user storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $userStorage;
+
+  /**
+   * The user role storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $userRoleStorage;
+
+  /**
+   * Constructs a new GroupRouteSubscriber.
+   *
+   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
+   *   The access manager.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
+   *   The menu link tree service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(AccessManagerInterface $access_manager, MenuLinkTreeInterface $menu_link_tree, EntityTypeManagerInterface $entity_type_manager) {
+    $this->accessManager = $access_manager;
+    $this->menuLinkTree = $menu_link_tree;
+    $this->userStorage = $entity_type_manager->getStorage('user');
+    $this->userRoleStorage = $entity_type_manager->getStorage('user_role');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterRoutes(RouteCollection $collection): void {
+    /** @var \Drupal\user\Entity\Role[] $roles */
+    $roles = $this->userRoleStorage->loadMultiple();
+    $route_accessible_roles = [];
+
+    foreach ($roles as $role_id => $role) {
+      if (!$role->hasPermission('access administration pages')) {
+        unset($roles[$role_id]);
+      }
+    }
+
+    if (empty($roles)) {
+      return;
+    }
+
+    foreach ($collection->all() as $route_id => $route) {
+      if (!$route->hasOption('_group_route')) {
+        continue;
+      }
+
+      // Perform check.
+      $this->checkAccess($route_accessible_roles, $route_id, $roles);
+      if (!empty($route_accessible_roles)) {
+        $route->setRequirement('_role', implode('+', $route_accessible_roles));
+      }
+    }
+  }
+
+  /**
+   * Checks route and its children.
+   *
+   * @param array $route_accessible_roles
+   *   Array with role names, which are allowed to access route.
+   * @param string $route_id
+   *   Route ID.
+   * @param \Drupal\user\Entity\Role[] $roles
+   *   User's role to be added in array.
+   * @param string|null $role_id
+   *   Array with test users for access checking.
+   */
+  protected function checkAccess(array &$route_accessible_roles, string $route_id, array $roles = [], string $role_id = NULL): void {
+    // Load menu links and check if route has any accessible children.
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot($route_id)
+      ->excludeRoot()
+      ->setTopLevelOnly()
+      ->onlyEnabledLinks();
+
+    $tree = $this->menuLinkTree->load(NULL, $parameters);
+    if (empty($tree)) {
+      $route_accessible_roles[] = $role_id;
+      return;
+    }
+
+    foreach ($tree as $element_id => $element) {
+      $url = $element->link->getUrlObject();
+      foreach ($roles as $role) {
+        if (in_array($role->id(), $route_accessible_roles, TRUE)) {
+          continue;
+        }
+
+        /** @var \Drupal\user\UserInterface $user */
+        $user = $this->userStorage->create(['roles' => $role]);
+        if (!$this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $user)) {
+          continue;
+        }
+
+        // Check if it is a _group_route with inaccessible children.
+        $this->checkAccess($route_accessible_roles, $element_id, $roles, $role->id());
+      }
+    }
+  }
+
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index ffca932ba6..16b99e3d9b 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -45,6 +45,8 @@ system.admin_structure:
     _title: 'Structure'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_reports:
   path: '/admin/reports'
@@ -53,6 +55,8 @@ system.admin_reports:
     _title: 'Reports'
   requirements:
     _permission: 'access site reports'
+  options:
+    _group_route: TRUE
 
 system.admin_config_media:
   path: '/admin/config/media'
@@ -61,6 +65,8 @@ system.admin_config_media:
     _title: 'Media'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_services:
   path: '/admin/config/services'
@@ -69,6 +75,8 @@ system.admin_config_services:
     _title: 'Web services'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_development:
   path: '/admin/config/development'
@@ -77,6 +85,8 @@ system.admin_config_development:
     _title: 'Development'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_regional:
   path: '/admin/config/regional'
@@ -85,6 +95,8 @@ system.admin_config_regional:
     _title: 'Regional and language'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_search:
   path: '/admin/config/search'
@@ -93,6 +105,8 @@ system.admin_config_search:
     _title: 'Search and metadata'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_system:
   path: '/admin/config/system'
@@ -101,6 +115,8 @@ system.admin_config_system:
     _title: 'System'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_ui:
   path: '/admin/config/user-interface'
@@ -109,6 +125,8 @@ system.admin_config_ui:
     _title: 'User interface'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_workflow:
   path: '/admin/config/workflow'
@@ -117,6 +135,8 @@ system.admin_config_workflow:
     _title: 'Workflow'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.admin_config_content:
   path: '/admin/config/content'
@@ -125,6 +145,8 @@ system.admin_config_content:
     _title: 'Content authoring'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.cron:
   path: '/cron/{key}'
@@ -132,6 +154,7 @@ system.cron:
     _controller: '\Drupal\system\CronController::run'
   options:
     no_cache: TRUE
+    _group_route: TRUE
   requirements:
     _access_system_cron: 'TRUE'
 
@@ -140,6 +163,8 @@ system.admin_compact_page:
   defaults:
     _controller: '\Drupal\system\Controller\SystemController::compactPage'
     mode: 'off'
+  options:
+    _group_route: TRUE
   requirements:
     _permission: 'access administration pages'
 
@@ -464,6 +489,8 @@ system.admin_config:
     _title: 'Configuration'
   requirements:
     _permission: 'access administration pages'
+  options:
+    _group_route: TRUE
 
 system.batch_page.html:
   path: '/batch'
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 31abbfeac1..865674dc8d 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -23,6 +23,11 @@ services:
     class: Drupal\system\EventSubscriber\AdminRouteSubscriber
     tags:
       - { name: event_subscriber }
+  system.group_route.route_subscriber:
+    class: Drupal\system\EventSubscriber\GroupRouteSubscriber
+    arguments: ['@access_manager', '@menu.link_tree', '@entity_type.manager']
+    tags:
+      - { name: event_subscriber }
   theme.negotiator.system.batch:
     class: Drupal\system\Theme\BatchNegotiator
     arguments: ['@batch.storage', '@request_stack']
diff --git a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
index 57415a37d3..0eec8c883c 100644
--- a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
+++ b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
@@ -17,13 +17,20 @@ class MenuAccessTest extends BrowserTestBase {
    *
    * @var array
    */
-  protected static $modules = ['block', 'menu_test'];
+  protected static $modules = ['block', 'menu_test', 'toolbar'];
 
   /**
    * {@inheritdoc}
    */
   protected $defaultTheme = 'stark';
 
+  /**
+   * The route builder.
+   *
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routerBuilder;
+
   /**
    * {@inheritdoc}
    */
@@ -31,6 +38,7 @@ protected function setUp(): void {
     parent::setUp();
 
     $this->drupalPlaceBlock('local_tasks_block');
+    $this->routerBuilder = $this->container->get('router.builder');
   }
 
   /**
@@ -70,4 +78,41 @@ public function testMenuBlockLinksAccessCheck() {
     $this->assertSession()->linkByHrefNotExists('foo/asdf/c');
   }
 
+  /**
+   * Tests menu access as per user role permissions.
+   */
+  public function testMenuAccessByUserRole() {
+    // Create an admin user.
+    $adminUser = $this->drupalCreateUser([], NULL, TRUE);
+
+    // Create a user with 'administer menu' permission.
+    $menuAdmin = $this->drupalCreateUser([
+      'access administration pages',
+      'view the administration theme',
+      'access toolbar',
+      'administer menu',
+    ]);
+
+    // Create a user that has access to the toolbar.
+    $webUser = $this->drupalCreateUser([
+      'access administration pages',
+      'view the administration theme',
+      'access toolbar',
+    ]);
+
+    $this->routerBuilder->rebuild();
+
+    $this->drupalLogin($adminUser);
+    $this->assertSession()->elementExists('css', '.toolbar-icon-system-admin-structure');
+    $this->assertSession()->elementExists('css', '.toolbar-icon-entity-user-collection');
+
+    $this->drupalLogin($menuAdmin);
+    $this->assertSession()->elementExists('css', '.toolbar-icon-system-admin-structure');
+    $this->assertSession()->elementNotExists('css', '.toolbar-icon-entity-user-collection');
+
+    $this->drupalLogin($webUser);
+    $this->assertSession()->elementNotExists('css', '.toolbar-icon-system-admin-structure');
+    $this->assertSession()->elementNotExists('css', '.toolbar-icon-entity-user-collection');
+  }
+
 }
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index dc618eed8a..63e9f16a21 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -20,7 +20,8 @@ user.admin_index:
     _title: 'People'
   requirements:
     _permission: 'access administration pages'
-
+  options:
+    _group_route: TRUE
 entity.user.admin_form:
   path: '/admin/config/people/accounts'
   defaults:
