Amazon CloudFront with Drupal 8

Since I wrote my first review of CloudFront in 2012, Amazon has added support for three essential features:

What this means is that CloudFront is no longer just for static content; it's fully capable of delivering content from a dynamic CMS like Drupal. Here are the configs, step-by-step:

Configure your distribution and origin

This is fairly straightforward. I reccomend using a CNAME for your origin (which could be a single instance, or an elastic load balancer). Ideally, your origin URL should not be accessible from the open internet for serveral reasons:

  • Prevent the origin URL from getting crawled by search engines
  • Pevent DDoS attacks from being able to bypass the CDN
  • Prevent spoofing of the X-Forwarded-For header

Configure a default behavior

Noteworthy settings are:

  • "use origin cache headers" - This means CloudFront will honor the page lifetime set on /admin/config/development/performance within Drupal.
  • Whitelist "Host" and "CloudFront-Forwarded-Proto". This allows virtual hosts, and any SSL redirect logic on the origin to function correctly.
  • Whitelist your site's session cookie.

Drupal 8 workarounds

One of the remaining Drupal 8 critical issues interferes with CloudFront:
[meta] External caches mix up response formats on URLs where content negotiation is in use
As a result, some additional behaviors are needed to work around this. These settings instruct CloudFront to forward all client headers for specific paths:


If you plan to use a single domain for your entire site, you're done! On this site, we decided to keep the domain-sharding approach described in my previous post, so we need a little D8 code.

name: Metal Toad Custom
description: Stuff that doesn't fit anywhere else.
package: Custom
type: module
core: 8.x

    class: Drupal\mt_custom\EventSubscriber\MTCustomSubscriber
    arguments: ['@current_user']
      - {name: event_subscriber}


use Drupal\Component\Utility\UrlHelper;
 * Implements hook_file_url_alter().
function mt_custom_file_url_alter(&$uri) {
  // Route static files to Amazon CloudFront, for anonymous users only.
  if (\Drupal::request()->server->get('HTTP_HOST') == '' &&
    \Drupal::currentUser()->isAnonymous() &&
    !\Drupal::request()->isSecure()) {
    // Multiple hostnames to parallelize downloads.
    $shard = crc32($uri) % 4 + 1;
    $cdn = "http://static$";
    $scheme = file_uri_scheme($uri);
    if ($scheme == 'public') {
      $wrapper = file_stream_wrapper_get_instance_by_scheme('public');
      $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
      $uri = "$cdn/" . UrlHelper::encodePath($path);
    else if (!$scheme && strpos($uri, '//') !== 0) {
      $uri = "$cdn/" . UrlHelper::encodePath($uri);
 * Implements hook_css_alter().
function mt_custom_css_alter(&$css) {
  // Mangle the paths slightly so that Drupal\Core\Asset\AssetDumper will generate
  // different keys on HTTPS.  Necessary because CDN URL varies by protocol.
  if (\Drupal::request()->isSecure()) {
    foreach ($css as $key => $file) {
      if ($file['type'] === 'file') {
        $css[$key]['data'] = './' . $css[$key]['data'];


namespace Drupal\mt_custom\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Session\AccountInterface;
class MTCustomSubscriber implements EventSubscriberInterface {
  protected $account;
  public function checkForCloudFront(GetResponseEvent $event) {
    $req = $event->getRequest();
     * Make sure Amazon CloudFront doesn't serve dynamic content
     * from static*
    if (strstr($req->server->get('HTTP_HOST'), 'static')) {
      if (!strstr($req->getPathInfo(), 'files/styles')) {
        header("HTTP/1.0 404 Not Found");
        print '404 Not Found';
   * {@inheritdoc}
  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('checkForCloudFront');
    return $events;
  public function __construct(AccountInterface $account) {
    $this->account = $account;


header() and exit() should be avoided..instead use Symfony Response objects

$response = new Response('404 Not Found', Response::HTTP_NOT_FOUND);


Really nice tutorial.

Sorry to be a numpty, but please correct me if I'm wrong...

So, as I understand it, this allows Amazon's CloudFront to cache some of Drupal's HTML pages?

Yes, HTML is cached for anonymous users (as long as there is no session cookie). The max-age header will be respected, so the TTL will be whatever Drupal's "Page cache maximum age" is set to. (We typically set this quite low, in the 1 to 5 minute range). This allows for quick propagation of content changes, yet will still give a good hit rate under a heavy workload (e.g. if we someday make the front page of Reddit).

Do you have a tutorial for Drupal7 and AWS Cloudfront? I have a HTTPS enabled e-commerce site hosted in Ireland and I am looking for using AWS CDN features.


Do you have Drupal7 with AWS CloudFront tutorial?

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?