diff --git a/src/EventSubscriber/CacheControlOverrideSubscriber.php b/src/EventSubscriber/CacheControlOverrideSubscriber.php
index 558b4ab..4a2ea96 100644
--- a/src/EventSubscriber/CacheControlOverrideSubscriber.php
+++ b/src/EventSubscriber/CacheControlOverrideSubscriber.php
@@ -7,6 +7,7 @@ namespace Drupal\cache_control_override\EventSubscriber;
 use Drupal\Core\Cache\CacheableResponseInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Site\Settings;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\Event\ResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
@@ -63,7 +64,23 @@ final class CacheControlOverrideSubscriber implements EventSubscriberInterface {
       return;
     }
 
-    $maxAge = (int) $response->getCacheableMetadata()->getCacheMaxAge();
+    /*
+     * Drupal core sets $build[#cache]['max-age'] = 0 on most 403/404
+     * responses because it has no data about cache invalidation.
+     * @see https://www.drupal.org/node/2920529
+     * But Drupal core still stores these pages in its own cache for a special
+     * duration (cache_ttl_4xx setting).
+     * @see \Drupal\page_cache\StackMiddleware\PageCache::storeResponse
+     * We use that special duration too, so that these error pages can remain
+     * in a shared reverse-proxy cache for a few moments.
+     */
+    $cache_ttl_4xx = Settings::get('cache_ttl_4xx', 3600);
+    if ($cache_ttl_4xx > 0 && $response->isClientError()) {
+      $maxAge = (int) $cache_ttl_4xx;
+    }
+    else {
+      $maxAge = (int) $response->getCacheableMetadata()->getCacheMaxAge();
+    }
 
     // We treat permanent cache max-age as default therefore we don't override
     // the max-age.
diff --git a/src/PageCache/DenyOnCacheControlOverride.php b/src/PageCache/DenyOnCacheControlOverride.php
index 0351b91..e0f5d90 100644
--- a/src/PageCache/DenyOnCacheControlOverride.php
+++ b/src/PageCache/DenyOnCacheControlOverride.php
@@ -29,6 +29,20 @@ final class DenyOnCacheControlOverride implements ResponsePolicyInterface {
     }
 
     if ($response->getCacheableMetadata()->getCacheMaxAge() === 0) {
+
+      /*
+       * Drupal core sets $build[#cache]['max-age'] = 0 on most 403/404
+       * responses because it has no data about cache invalidation.
+       * @see https://www.drupal.org/node/2920529
+       * But Drupal core still stores these pages in its own cache for a special
+       * duration (cache_ttl_4xx setting).
+       * @see \Drupal\page_cache\StackMiddleware\PageCache::storeResponse
+       * We want to keep this behavior.
+       */
+      if ($response->isClientError()) {
+        return NULL;
+      }
+
       // @todo This will affect users using Internal Page Cache as well, find a way to document that.
       return static::DENY;
     }
