Skip to main content
skill CX Codex

cdn-setup — Claude Code skill: what it does, how to install

Configures CloudFront and Cloudflare CDNs for static asset delivery, caching rules, and cache invalidation. Useful when deploying web apps and edge routing.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill cdn-setup -a claude-code
Open on GitHub

What cdn-setup does

The skill provides step-by-step CLI commands and configurations for setting up content delivery networks. It includes AWS CLI recipes to deploy CloudFront distributions using Origin Access Control (OAC), enforce HTTPS redirects, and handle SPA routing by mapping 404 responses back to index.html. It also details Cloudflare API calls for managing zones, purge requests, and Cache Rules for static asset extensions.

Infrastructure automation is covered through Terraform 1.5+ configurations. The configuration separates cached static behaviors from unmanaged /api/* paths with disabled caching. It also provides a CloudFront Functions snippet for edge URL rewrites.

Deployment expects a domain with DNS control and an existing origin like an S3 or R2 bucket, which means you cannot test the distributions in an empty sandbox without cloud credentials.

What it helps with

  • configure an AWS CloudFront distribution with S3 Origin Access Control
  • write Terraform configurations for CDN edge routing and API pass-through
  • create Cloudflare Cache Rules and purge caches via API
  • set up lightweight URL rewrites using CloudFront Functions

Example prompts for the agent

  • Create an AWS CloudFront distribution config for an S3 bucket with SPA fallback
  • Write a Terraform configuration for CloudFront with caching disabled on /api/*
  • Purge Cloudflare cache for specific static asset URLs using the API

How to install cdn-setup

1Skills CLI (Vercel)

npx skills add BagelHole/DevOps-Security-Agent-Skills --skill cdn-setup -a claude-code

2Manually: put the files in place

~/.claude/skills/cdn-setup/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/cdn-setup/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about cdn-setup

How to install cdn-setup in Claude Code?

Run npx skills add BagelHole/DevOps-Security-Agent-Skills --skill cdn-setup -a claude-code.

What does cdn-setup do?

It provides operational patterns and templates to configure AWS CloudFront and Cloudflare, manage edge caching, and run cache invalidations.

What prerequisites are required before using these setups?

You need a domain with DNS access, an origin server or S3/R2 bucket, configured AWS CLI for CloudFront, and Terraform 1.5+ if using IaC.

Source text

SKILL.md · MIT
---
name: cdn-setup
description: Configure CDNs for content delivery. Set up CloudFront, Cloudflare, and Fastly. Use when optimizing global content delivery.
license: MIT
metadata:
  author: devops-skills
  version: "1.0"
---

# CDN Setup

Configure content delivery networks for fast, reliable global asset delivery with proper caching, invalidation, and security.

## When to Use

- Serving static assets (JS, CSS, images, fonts) globally with low latency.
- Offloading traffic from origin servers to reduce compute costs.
- Adding TLS termination and DDoS protection at the edge.
- Implementing geo-based routing or content restrictions.
- Accelerating API responses with edge caching.

## Prerequisites

- Domain with DNS management access.
- Origin server or S3/R2 bucket with content to serve.
- AWS CLI configured (for CloudFront).
- Cloudflare account with zone configured (for Cloudflare CDN).
- Terraform 1.5+ (for infrastructure-as-code examples).

## AWS CloudFront

### Create a Distribution via CLI

```bash
# Create an S3 origin distribution with OAC (Origin Access Control)
aws cloudfront create-distribution --distribution-config '{
  "CallerReference": "my-site-'$(date +%s)'",
  "Comment": "Production site CDN",
  "Enabled": true,
  "Origins": {
    "Quantity": 1,
    "Items": [{
      "Id": "s3-origin",
      "DomainName": "my-bucket.s3.us-east-1.amazonaws.com",
      "OriginPath": "",
      "S3OriginConfig": {
        "OriginAccessIdentity": ""
      },
      "OriginAccessControlId": "E2QWRUHAPOMQZL"
    }]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "s3-origin",
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": {
      "Quantity": 2,
      "Items": ["GET", "HEAD"]
    },
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "Compress": true
  },
  "DefaultRootObject": "index.html",
  "PriceClass": "PriceClass_100",
  "ViewerCertificate": {
    "ACMCertificateArn": "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
    "SSLSupportMethod": "sni-only",
    "MinimumProtocolVersion": "TLSv1.2_2021"
  },
  "Aliases": {
    "Quantity": 1,
    "Items": ["www.example.com"]
  },
  "CustomErrorResponses": {
    "Quantity": 1,
    "Items": [{
      "ErrorCode": 404,
      "ResponseCode": "200",
      "ResponsePagePath": "/index.html",
      "ErrorCachingMinTTL": 10
    }]
  }
}'
```

### Cache Invalidation

```bash
# Invalidate specific paths
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --paths "/index.html" "/css/*" "/js/*"

# Invalidate everything (costs apply per path)
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --paths "/*"

# Check invalidation status
aws cloudfront get-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --id I1A2B3C4D5E6F7

# List recent invalidations
aws cloudfront list-invalidations --distribution-id E1A2B3C4D5E6F7
```

### CloudFront Functions (Lightweight Edge Logic)

```javascript
// URL rewrite function — add index.html to directory requests
function handler(event) {
  var request = event.request;
  var uri = request.uri;

  if (uri.endsWith('/')) {
    request.uri += 'index.html';
  } else if (!uri.includes('.')) {
    request.uri += '/index.html';
  }

  return request;
}
```

### CloudFront with Terraform

```hcl
# cloudfront.tf
resource "aws_cloudfront_distribution" "site" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  aliases             = ["www.example.com"]
  price_class         = "PriceClass_100"

  origin {
    domain_name              = aws_s3_bucket.site.bucket_regional_domain_name
    origin_id                = "s3-origin"
    origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "s3-origin"

    cache_policy_id          = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
    origin_request_policy_id = "88a5eaf4-2fd4-4709-b370-b4c650ea3fcf" # CORS-S3Origin

    viewer_protocol_policy = "redirect-to-https"
    compress               = true
  }

  # SPA fallback
  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = "/index.html"
  }

  # API pass-through (no caching)
  ordered_cache_behavior {
    path_pattern     = "/api/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "api-origin"

    cache_policy_id          = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
    origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac" # AllViewerExceptHostHeader

    viewer_protocol_policy = "https-only"
  }

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.cert.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

resource "aws_cloudfront_origin_access_control" "oac" {
  name                              = "s3-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}
```

## Cloudflare CDN

### Zone Setup

```bash
# Add a zone
curl -X POST "https://api.cloudflare.com/client/v4/zones" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"example.com","jump_start":true}'

# Get zone ID
ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \
  -H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id')
```

### Cache Rules (Replacing Page Rules)

```bash
# Create a cache rule for static assets
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_cache_settings/entrypoint" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "expression": "(http.request.uri.path.extension in {\"css\" \"js\" \"png\" \"jpg\" \"woff2\" \"svg\"})",
        "action": "set_cache_settings",
        "action_parameters": {
          "cache": true,
          "browser_ttl": { "mode": "override_origin", "default": 2592000 },
          "edge_ttl": { "mode": "override_origin", "default": 86400 }
        },
        "description": "Cache static assets aggressively"
      }
    ]
  }'
```

### Purge Cache

```bash
# Purge everything
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"purge_everything":true}'

# Purge specific URLs
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files":["https://example.com/style.css","https://example.com/app.js"]}'

# Purge by prefix
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefixes":["https://example.com/images/"]}'
```

## Cache Headers on Origin

### nginx Cache Headers

```nginx
# Immutable hashed assets (fingerprinted filenames)
location ~* \.(js|css)$ {
    if ($uri ~* "\.[a-f0-9]{8,}\.(js|css)$") {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    expires 7d;
    add_header Cache-Control "public, must-revalidate";
}

# Images and fonts
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff2|ttf)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

# HTML — always revalidate
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}

# API responses — no caching
location /api/ {
    add_header Cache-Control "no-store, no-cache";
    add_header Vary "Authorization, Accept";
}
```

### Cache-Control Cheat Sheet

| Header | Meaning |
|--------|---------|
| `public, max-age=31536000, immutable` | Cache for 1 year, never revalidate (hashed assets) |
| `public, max-age=86400, must-revalidate` | Cache 1 day, check freshness after |
| `private, max-age=600` | Browser cache only, 10 min (user-specific content) |
| `no-cache` | Always revalidate with origin before serving |
| `no-store` | Never cache (sensitive data) |
| `s-maxage=3600` | CDN caches for 1 hour, overrides `max-age` for shared caches |

## Cache Warming

```bash
# Warm cache for critical pages after deployment
#!/bin/bash
URLS=(
  "https://www.example.com/"
  "https://www.example.com/products"
  "https://www.example.com/about"
  "https://www.example.com/css/main.abc123.css"
  "https://www.example.com/js/app.def456.js"
)

for url in "${URLS[@]}"; do
  curl -s -o /dev/null -w "%{http_code} %{time_total}s %{url_effective}\n" "$url"
done
```

## Monitoring Cache Performance

```bash
# Check cache status from response headers
curl -sI https://www.example.com/style.css | grep -i -E "cf-cache|x-cache|age|cache-control"

# Expected headers:
# cf-cache-status: HIT (Cloudflare)
# x-cache: Hit from cloudfront (CloudFront)
# age: 3600 (seconds since cached)

# CloudFront cache hit ratio
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name CacheHitRate \
  --dimensions Name=DistributionId,Value=E1A2B3C4D5E6F7 \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average
```

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| `cf-cache-status: DYNAMIC` | No cache rule matches or Cache-Control prevents it | Set `s-maxage` or create a cache rule for the path |
| Cache hit ratio below 50% | Low TTLs or high URL cardinality (query strings) | Increase TTL; strip unnecessary query strings in cache key |
| Stale content after deploy | Old objects still cached at edge | Invalidate; use content-hashed filenames to avoid this entirely |
| CORS errors through CDN | CDN strips or caches wrong `Vary` header | Add `Vary: Origin` and configure origin request policy to forward `Origin` |
| 502 errors from CDN | Origin down or timeout | Check origin health; increase CDN origin timeout settings |
| Mixed content warnings | CDN serves HTTPS but origin links use HTTP | Set `viewer-protocol-policy: redirect-to-https`; fix origin URLs |
| High invalidation costs | Purging `/*` on every deploy | Use fingerprinted filenames; only invalidate `index.html` |

## Related Skills

- [dns-management](../dns-management/) - DNS records for CDN CNAME setup
- [cloudflare-pages](../../cloudflare/cloudflare-pages/) - Cloudflare's built-in CDN for Pages projects
- [reverse-proxy](../reverse-proxy/) - Origin server configuration behind CDN
- [load-balancing](../load-balancing/) - Multi-origin CDN backends

© devops-skills · MIT · BagelHole/DevOps-Security-Agent-Skills

Similar skills

Everything for DevOps and infrastructure →

From the same set127

pci-dss-compliance A skill that maps PCI DSS v4.0 requirements onto your project: pick the right SAQ, scope the CDE, plan the controls. CX Codex +1 327installs cloudflare-zero-trust A skill for securing internal apps and SSH access with Cloudflare Zero Trust without running a VPN. Covers cloudflared tunnels, IdP policies, and device checks. CX Codex +1 325installs ai-pipeline-orchestration A skill that helps the agent build pipelines for data ingestion, model training, batch inference and RAG indexing on Prefect, Airflow or Dagster. CX Codex +1 325installs rag-infrastructure A skill for building RAG infrastructure: document ingestion, embeddings, vector stores, hybrid search and grounded LLM answers. CX Codex +1 325installs azure-devops A skill that lets your agent write and fix Azure DevOps YAML pipelines: build, test, deploy to Azure. Useful when CI/CD lives in Azure Pipelines. CX Codex +1 325installs circleci Generates and tunes CircleCI pipelines in .circleci/config.yml. Useful for automating builds, tests, caching, and deployments across environments. CX Codex +1 323installs gcp-audit-logs A skill that walks you through Cloud Audit Logs in GCP: turning on data access logs, exporting them to BigQuery, and querying suspicious events. CX Codex +1 324installs platform-engineering A skill for building an internal developer platform: Backstage portal, Crossplane self-service infrastructure and golden paths. Useful when teams drown in manual environment setup. CX Codex +1 322installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor