diff --git a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
index f99f8c5a42971b5c76e541eb30d249e5c0fd758f..995ce9780306fd2e9c91f48597d9356640be49f1 100644
--- a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
+++ b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Template;
 
+use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\PhpStorage\PhpStorageFactory;
 
@@ -17,6 +18,11 @@
  */
 class TwigPhpStorageCache implements \Twig_CacheInterface {
 
+  /**
+   * The maximum length for each part of the cache key suffix.
+   */
+  const SUFFIX_SUBSTRING_LENGTH = 25;
+
   /**
    * The cache object used for auto-refresh via mtime.
    *
@@ -67,20 +73,28 @@ protected function storage() {
    * {@inheritdoc}
    */
   public function generateKey($name, $className) {
-    $hash = hash('sha256', $className);
-
     if (strpos($name, '{# inline_template_start #}') === 0) {
       // $name is an inline template, and can have characters that are not valid
-      // for a filename. $hash is unique for each inline template so we just use
-      // the generic name 'inline-template' here.
+      // for a filename. $suffix is unique for each inline template so we just
+      // use the generic name 'inline-template' here.
       $name = 'inline-template';
     }
     else {
       $name = basename($name);
     }
 
-    // The first part is what is invalidated.
-    return $this->templateCacheFilenamePrefix . '_' . $name . '_' . $hash;
+    // Windows (and some encrypted Linux systems) only support 255 characters in
+    // a path. On Windows a requirements error is displayed and installation is
+    // blocked if Drupal's public files path is longer than 120 characters.
+    // Thus, to always be less than 255, file paths may not be more than 135
+    // characters long. Using the default PHP file storage class, the Twig cache
+    // file path will be 124 characters long at most, which provides a margin of
+    // safety.
+    $suffix = substr($name, 0, self::SUFFIX_SUBSTRING_LENGTH) . '_';
+    $suffix .= substr(Crypt::hashBase64($className), 0, self::SUFFIX_SUBSTRING_LENGTH);
+
+    // The cache prefix is what gets invalidated.
+    return $this->templateCacheFilenamePrefix . '_' . $suffix;
   }
 
   /**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ca9781c6072b668be64a9310590cc688957633ff..27abfa009bad5a822994a8b87a81d19c7233d09c 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -904,6 +904,23 @@ function system_requirements($phase) {
     }
   }
 
+  // Installations on Windows can run into limitations with MAX_PATH if the
+  // Drupal root directory is too deep in the filesystem. Generally this shows
+  // up in cached Twig templates and other public files with long directory or
+  // file names. There is no definite root directory depth below which Drupal is
+  // guaranteed to function correctly on Windows. Since problems are likely
+  // with more than 100 characters in the Drupal root path, show an error.
+  if (substr(PHP_OS, 0, 3) == 'WIN') {
+    $depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath()));
+    if ($depth > 120) {
+      $requirements['max_path_on_windows'] = [
+        'title' => t('Windows installation depth'),
+        'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+  }
+
   return $requirements;
 }
 
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
index c3e7af6a3c8b5d20f59f188df28ec0b9ab31ad70..ba7a3cd8acc9fb8c9b637ee234043558e990f667 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
@@ -2,8 +2,10 @@
 
 namespace Drupal\KernelTests\Core\Theme;
 
+use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Template\TwigPhpStorageCache;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -87,7 +89,7 @@ public function testInlineTemplate() {
 
     $cache = $environment->getCache();
     $class = $environment->getTemplateClass($name);
-    $expected = $prefix . '_inline-template' . '_' . hash('sha256', $class);
+    $expected = $prefix . '_inline-template_' . substr(Crypt::hashBase64($class), 0, TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH);
     $this->assertEqual($expected, $cache->generateKey($name, $class));
   }
 
@@ -116,6 +118,20 @@ public function testCacheFilename() {
     // static cache.
     $environment = \Drupal::service('twig');
 
+    // A template basename greater than the constant
+    // TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH should get truncated.
+    $cache = $environment->getCache();
+    $long_name = 'core/modules/system/templates/block--system-messages-block.html.twig';
+    $this->assertGreaterThan(TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH, strlen(basename($long_name)));
+    $class = $environment->getTemplateClass($long_name);
+    $key = $cache->generateKey($long_name, $class);
+    $prefix = $environment->getTwigCachePrefix();
+    // The key should consist of the prefix, an underscore, and two strings
+    // each truncated to length TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH
+    // separated by an underscore.
+    $expected = strlen($prefix) + 2 + 2 * TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH;
+    $this->assertEquals($expected, strlen($key));
+
     $original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig');
     \Drupal::getContainer()->set('twig', NULL);