Streaming and Delivery on AWS

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Baseline date: 2026-10-07. Stored video that MediaConvert has packaged as HLS or DASH, see MediaConvert, is a set of static files in S3. The simplest delivery is therefore S3 behind CloudFront, with access control by signed cookies. The packaging concepts are in video streaming: HLS, DASH and CMAF, and CloudFront itself in CloudFront.

S3 and CloudFront for stored video

flowchart LR P["Player"] -->|master.m3u8, segments, with signed cookies| CF["CloudFront bookshelf-cdn"] CF -->|OAC| M["S3 bookshelf-media-111122223333"] A["Media API"] -->|sets cookies after authorization| P
  • Keep bookshelf-media-111122223333 private, and let only CloudFront read it through an origin access control (OAC).

  • Add a behaviour for /videos/* that requires signed cookies (a trusted key group), with a cache policy that caches segments for a long time, and playlists briefly when they can change. VOD playlists with EXT-X-ENDLIST never change and can be cached for as long as the segments.

  • Attach a response headers policy with CORS for the player’s origin (Access-Control-Allow-Origin, Access-Control-Allow-Headers including Range), since hls.js and dash.js fetch cross-origin, see video playback on the web.

  • Set correct Content-Type metadata on the objects (application/vnd.apple.mpegurl for .m3u8, video/mp2t for .ts, video/mp4 for .mp4). MediaConvert sets them when it writes the files.

  • Enable compression for playlists (text), not for segments (already compressed).

Signed cookies and signed URLs

CloudFront can restrict a behaviour to viewers who present a valid signature, made with a private key whose public half is registered in a key group. Two forms exist, see serving private content with signed URLs and signed cookies:

  • Signed URL. Protects one file. It is wrong for HLS and DASH, because the player requests many segment URLs derived from the playlist, and none of them carries the signature.

  • Signed cookies. Protect a path pattern such as https://cdn.example.com/videos/42/* with a custom policy. The browser sends the three cookies CloudFront-Policy, CloudFront-Signature and CloudFront-Key-Pair-Id with every request to that domain, so the playlist and all segments are authorised. Use this for video.

Setup:

openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

aws cloudfront create-public-key --public-key-config \
  "CallerReference=bookshelf-video-1,Name=bookshelf-video,EncodedKey=$(cat public_key.pem)"
aws cloudfront create-key-group --key-group-config "Name=bookshelf-video,Items=<public-key-id>"
# then set the key group as a trusted key group on the /videos/* cache behaviour

Keep the private key in Secrets Manager, and have the media API sign after it has authorised the user. The cookies must be set for a domain the browser sends to CloudFront: serve the CDN from a subdomain of the site (cdn.example.com) and set the cookie Domain to the parent so it is sent, with Secure, HttpOnly and SameSite set as your architecture allows. Cross-site cookies need SameSite=None; Secure.

Python

import base64
import datetime

from botocore.signers import CloudFrontSigner
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

KEY_PAIR_ID = "K2JCJMDEHXQW5F"               # the public key ID in CloudFront
with open("private_key.pem", "rb") as f:
    PRIVATE_KEY = serialization.load_pem_private_key(f.read(), password=None)


def _sign(message: bytes) -> bytes:
    return PRIVATE_KEY.sign(message, padding.PKCS1v15(), hashes.SHA1())    # CloudFront requires RSA SHA-1


def _cloudfront_b64(data: bytes) -> str:
    return base64.b64encode(data).replace(b"+", b"-").replace(b"=", b"_").replace(b"/", b"~").decode()


def signed_cookies(media_id: str, hours: int = 2) -> dict[str, str]:
    expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=hours)
    signer = CloudFrontSigner(KEY_PAIR_ID, _sign)
    policy = signer.build_policy(f"https://cdn.example.com/videos/{media_id}/*", expires).encode()
    return {
        "CloudFront-Policy": _cloudfront_b64(policy),
        "CloudFront-Signature": _cloudfront_b64(_sign(policy)),
        "CloudFront-Key-Pair-Id": KEY_PAIR_ID,
    }

Reference: boto3 CloudFront and the CloudFrontSigner class in botocore (botocore.signers).

Node.js

import { getSignedCookies } from '@aws-sdk/cloudfront-signer';

export function signedCookies(mediaId, privateKey, keyPairId) {
  const url = `https://cdn.example.com/videos/${mediaId}/*`;
  const policy = JSON.stringify({
    Statement: [{ Resource: url, Condition: { DateLessThan: { 'AWS:EpochTime': Math.floor(Date.now() / 1000) + 7200 } } }],
  });
  return getSignedCookies({ keyPairId, privateKey, policy });
  // { 'CloudFront-Policy': '...', 'CloudFront-Signature': '...', 'CloudFront-Key-Pair-Id': '...' }
}

Java and .NET

The AWS SDK for Java 2.x has CloudFrontUtilities (package software.amazon.awssdk.services.cloudfront), whose getCookiesForCustomPolicy takes a CustomSignerRequest with the resource URL, the private key, the key pair ID and the expiration, and returns the three cookie values. The AWS SDK for .NET has AmazonCloudFrontCookieSigner with GetCookiesForCustomPolicy. Check the class and method names in the API documentation of the SDK version you install: CloudFrontUtilities and AWS SDK for .NET API reference.

The backend decides who gets cookies, see video streaming, and an API route returns them in Set-Cookie headers. Signing does not stop a viewer from saving the video: for that, DRM is needed.

AWS Elemental MediaPackage v2

AWS Elemental MediaPackage takes a stream of segments (from MediaLive or another packager input) and just-in-time packages it into HLS, DASH or CMAF origin endpoints, with DRM, time-shifted viewing, ad markers and CDN authorisation. In MediaPackage v2 the main resources are a channel group, channels and origin endpoints, and a CloudFront distribution sits in front of the endpoints. It is mainly a live and live-to-VOD service. For plain VOD from files that MediaConvert has already packaged, S3 and CloudFront is simpler and cheaper. Use MediaPackage when you need one source in several formats, DRM, or time-shift. Check the user guide for the current VOD support and the differences from v1.

Live streaming: MediaLive and IVS

  • AWS Elemental MediaLive is the broadcast-grade live encoder: it ingests RTMP, SRT or other contribution streams and produces ABR renditions for MediaPackage.

  • Amazon Interactive Video Service (IVS) is a managed live streaming service with low latency and player SDKs, with much less to configure, which suits social and interactive streams.

Both are outside the stored-video path of this section.