Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Sharing Videos Online Securely with HLS encoding and CloudFront Security And Distribution


This is the tutorial series for you if you wish to publish video content online with the following requirements:

  • Private access - The videos need to be only accessible by people logging into your website / app (cannot be shared with others).
  • Fast Distribution - Those videos need to be published using a content distribution network (CDN) so that you don't have to worry about bandwidth limitations on your server or where the users are located around the world.
  • Automatic Video Quality Variability - The videos should be streamed to the user in a quality most appropriate to the user's screen resolution and internet connection speed.


The steps to do this are covered in the individual tutorials below. If they aren't a link, they will become one soon.

  1. Create a private S3 bucket
  2. Set CORS on the bucket:
        "AllowedHeaders": [
        "AllowedMethods": [
        "AllowedOrigins": [
        "ExposeHeaders": []

The original XML that used to work, from which that JSON was built, is below:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="">

Ensure the following headers are whitelisted

  1. Create an Elastic Transcoder Pipeline.
  2. Create a Cloudfront Key Pair
  3. Create presets for the HLS Videos
  4. Upload the files to S3
  5. Transcode the videos
  6. Create a CloudFront distribution.
  7. Upload Certificate for CloudFront
  8. Generate and use signed cookies in your code.


Seeing as I never have time to do this properly in separate posts, I am going to dump all my debugging pain points here for now and hopefully refactor this again later, but I need to put it here before I forget.


When calling the AWS SDK getSignedCookie() method, your Resource should include full cloudfront domain, not just a path. E.g.*. Also note that you need to end with a * if you want to serve up multiple files. If you use a prefix (no star), then it won't work and expects a specific file (won't serve up a folder). Don't forget that should be your CNAME to the domain.

If using the programster/aws-wrapper package, then it's like:

$signedCookieCustomPolicy = $cloudfrontClient->getSignedCookie(
    600, //seconds cookie should auth for.
    __DIR__ . '/path/to/cloudfront.private.pem',


If you are using PHP to set cookies, you want to use code like so:

    0, // never expire (relying on the policy expiration)
    "/", // this needs to be / or the path of the content in cloudfront, not the page serving this content.
    true, // encrypted
    true // http(s) only (not Javascript)
  • Path - you want to provide a value of / for the path. If you leave it as the default (empty string), then PHP will set it to a path baed on your web URI, which is NOT what you want, since this path is going to relate to the path in the S3 bucket.
  • $domain - You want this to be the base domain of your site's E.g., and not something like *, Signed cookies relies on you setting the cookie on something like and using a CNAME on another subdomain that points to the CDN. E.g.

CloudFront Settings

Edit your cloudfront distribution and go to the Origins and Origin Groups tab and select the single row before clicking Edit.

Ensure Restrict Bucket Access is set to Yes.

With regards to Grant Read Permissions on Bucket, you will need this to have been done once already against the identity you are using. You may wish to just check your buckets settings under Permissions > Bucket Policy and you should see something like the following in there:

           "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EB0U5IRU9HSLZ"

Now, in order to ensure cloudfront doesn't serve up content without authorized cookies or pre-signed URLs you need to go over to the Behaviours tab and edit in there. Towards the bottom is is a setting called Restrict Viewer Access (Use Signed URLs or Signed Cookies), which you need to set to Yes.

Last updated: 27th September 2021
First published: 27th October 2019