---
title: Amazon CloudFront with Drupal 8
description: Since I wrote my first review of CloudFront, Amazon has added support for three essential features...
---

[Blog ](https://www.metaltoad.com/blog)

# [Amazon CloudFront with Drupal 8](https://www.metaltoad.com/blog/amazon-cloudfront-drupal-8)

 Written by [Metal Toad Staff](https://www.metaltoad.com/blog/author/metal-toad-staff) | May 14, 2015 12:00:00 AM

 Filed under:

- [Drupal](https://www.metaltoad.com/help/drupal)
- [Content Delivery Network (CDN)](https://www.metaltoad.com/help/cdn)
- [Drupal 8](https://www.metaltoad.com/help/drupal8)
- [AWS CloudFront](https://www.metaltoad.com/help/aws-cloudfront)
- [Amazon Web Services (AWS)](https://www.metaltoad.com/help/amazon-web-services)

*This article is one of Metal Toad's **[Top 20 Drupal Tips](https://www.metaltoad.com/help/top-20-drupal-tips)**. Enjoy!*

Since I wrote my [first review of CloudFront](https://www.metaltoad.com/blog/amazon-cloudfront-drupal) in 2012, [AWS](https://www.metaltoad.com/blog/what-is-aws) has added support for three essential features:

- [Dynamic content with POST, PUT and other HTTP methods](http://aws.amazon.com/about-aws/whats-new/2013/10/15/amazon-cloudfront-now-supports-put-post-and-other-http-methods/)
- [Custom SSL certifications with Server Name Indication (SNI)](http://aws.amazon.com/about-aws/whats-new/2014/03/05/amazon-cloudront-announces-sni-custom-ssl/)   
   This is important because with SNI, there's no need for a dedicated IPs (and the associated $600 per month fee)
- [Wildcard cookies](http://aws.amazon.com/about-aws/whats-new/2014/10/07/amazon-cloudront-announces-wildcard-cookies-options-caching/)

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](https://www.drupal.org/node/2364011)  
 As a result, some additional behaviors are needed to work around this. These settings instruct CloudFront to forward *all* client headers for specific paths:

### Domain-sharding

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](https://www.stevesouders.com/blog/2013/09/05/domain-sharding-revisited/) approach described in my previous post, so we need a little D8 code.

#### `mt_custom.info.yml`

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

#### `mt_custom.services.yml`

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

#### `mt_custom.module`

```
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') == 'www.metaltoad.com' &&
    \Drupal::currentUser()->isAnonymous() &&
    !\Drupal::request()->isSecure()) {
 
    // Multiple hostnames to parallelize downloads.
    $shard = crc32($uri) % 4 + 1;
    $cdn = "http://static$shard.metaltoad.com";
 
    $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'];
      }
    }
  }
}
```

#### `src/EventSubscriber/MTCustomSubscriber.php`

```
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*.metaltoad.com
     */
    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';
        exit();
      }
    }
  }
 
  /**
   * {@inheritdoc}
   */
  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('checkForCloudFront');
    return $events;
  }
 
  public function __construct(AccountInterface $account) {
    $this->account = $account;
  }
 
}
```

[View full post](https://www.metaltoad.com/blog/amazon-cloudfront-drupal-8)

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Metal Toad Staff"
  },
  "dateModified" : "2023-12-06T23:28:12.432Z",
  "datePublished" : "2015-05-14T00:00:00Z",
  "headline" : "Amazon CloudFront with Drupal 8",
  "image" : {
    "@type" : "ImageObject",
    "height" : 500,
    "url" : "https://6104926.fs1.hubspotusercontent-na1.net/hubfs/6104926/Imported_Blog_Media/aws-cloudfront-logo-blog-header.png",
    "width" : 870
  },
  "mainEntityOfPage" : "https://www.metaltoad.com/blog/amazon-cloudfront-drupal-8",
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "height" : 60.0,
      "url" : "https://6104926.fs1.hubspotusercontent-na1.net/hubfs/6104926/social-suggested-images/6104926.fs1.hubspotusercontent-na1.nethub6104926hubfsMetal_Toad_Logo_orange_horizontal-50-1.jpg",
      "width" : 240.00002
    },
    "name" : "Blog"
  }
}
```