Our work relates to Debian 13, php8.4, Nginx. We simulate the same ease-of-use logging in as an EC2 AWS instance.



A combined overview and implementation architecture and practical examples


Before we can detail a solution for using an S3 Bucket with an AWS CDN and the WordPress Media Library, we need to outline the architecture and examples for prerequisite works. IT Linux Skill Level and problem solving skills should be high. The examples show Nginx and Debian 13, but we are aware that the alternative is Apache2, or use of EC2 Linux where we can use the aws-sdk via an attached IAM Role, thus avoiding any use of AWS CLI credentials. As we are using credentials we need and IAM User with policies and your secret keys activated for use with the .aws/ profile. You should know how to create these for the user.

The use of a CDN means we do not need to use a complex WP Plugin which can slow the CDN down too much when used on a platform outside of AWS, such as Akamai/Linode. We will use the CDN for media library images only – jpg, jpeg, png, webp.

The final code (not in this first article) will give ability to switch back to the Media Library on the local WP server, or switch back to the CDN. We are not caching css or js into the cdn at this time. We also create webp on the fly if required, and store the full original image in ./uploads/original. Using S3 and a CDN offloads processing from the web server to Amazon instead.

I must be blunt, this is highly specific, and is designed to use a fast performance for images that we were not able to achieve via WP Plugins accessing the AWS CDN due to many operations and complexities behind the scenes with plugins. THis is not saying one should not use these, but we saw in what we tested, slowness from Akamai/Linode to an AWS CDN, hence this development.

This should only be used on a development server and well tested before going into a production mode, and problems be solved with AI.

It is helpful to pre-configure as much as you possibly can before continuing with other details. For example, if you configure as much of the DNS records as you can, Amazon SES will recognise you domain validation more quickly while you cut and paste the CNAME records it provides you.

Architecture Specification

Below is our formal document, written as a combined overview + implementation specification, using practical examples while keeping it reusable. This is not intended to be a final document or exhaistvie.


WordPress Media Library CDN Integration

Infrastructure Prerequisites & AWS Configuration (nginx / Debian 13 / Linode)


1. Overview

This document outlines the infrastructure and AWS configuration required to support a custom WordPress Media Library integration using an S3-backed CDN via CloudFront.

It is written for engineers with solid general IT skills and working knowledge of:

  • AWS services (S3, CloudFront, IAM, ACM, Route/DNS concepts)
  • Linux systems (Debian-based)
  • nginx and PHP-FPM
  • DNS management

While this implementation is based on:

  • Debian 13 (Linode/Akamai)
  • nginx + PHP 8.4-FPM

…the architectural principles apply broadly (e.g. Apache2 or Amazon EC2 with IAM Roles). Alternative platform notes may be added in later revisions.


2. Architecture Summary

At a high level, the system works as follows:

User Browser
     ↓
cdn.DOMAIN_NAME (CloudFront)
     ↓
S3 Bucket (media storage)
     ↑
WordPress (uploads via IAM credentials)

Key Components

Component Purpose
WordPress (nginx) Uploads media to S3
S3 Bucket Stores media files
CloudFront CDN layer serving cached media
ACM Certificate Enables HTTPS for CDN
IAM User Grants WordPress access to S3 + CloudFront
DNS Routes traffic and validates services

3. Environment & Naming Conventions

To keep documentation reusable, the following placeholders are used:

Placeholder Description
DOMAIN_NAME Root domain (e.g. example.com)
CDN_DOMAIN CDN subdomain (e.g. cdn.example.com)
LINODE_IPV4 Public IPv4 address of server
ACCOUNT_ID AWS account ID
DISTRIBUTION_ID CloudFront distribution ID
S3_BUCKET_NAME S3 bucket (e.g. cdn.example.com)

4. Base Server Prerequisites (High-Level)

Before integrating AWS services, the following must already be working:

4.1 Web Stack

  • nginx installed and configured
  • PHP 8.4-FPM (with tuned www.conf, opcache, memory settings)
  • MariaDB configured and optimised
  • WordPress installed and functioning

4.2 SSL (Origin Server)

  • Let’s Encrypt certificates for:
    • DOMAIN_NAME
    • www.DOMAIN_NAME
  • Automated renewal (certbot)

4.3 Supporting Services (Recommended)

  • Memcached (or Redis)
  • phpMyAdmin (optional)
  • Proper file permissions for WordPress

Note: These are prerequisites. This document does not cover their setup in detail.


5. DNS Configuration

All DNS entries should be set to DNS only (no proxying) with a TTL of ~1 hour unless otherwise required.

5.1 Core A Records

A     DOMAIN_NAME        LINODE_IPV4
A     www                LINODE_IPV4

5.2 CAA Records (Certificate Authority Restrictions)

CAA   DOMAIN_NAME   0 issue "letsencrypt.org"
CAA   DOMAIN_NAME   0 issue "amazontrust.com"
CAA   DOMAIN_NAME   0 issuewild "letsencrypt.org"
CAA   DOMAIN_NAME   0 issuewild "amazontrust.com"
CAA   DOMAIN_NAME   0 iodef "mailto:admin@DOMAIN_NAME"

5.3 CDN (CloudFront) DNS

CNAME CDN_DOMAIN    CLOUDFRONT_DOMAIN

Example:

cdn.example.com → dxxxxxxxxxxxx.cloudfront.net

5.4 ACM Validation Record (Required)

Created automatically by AWS Certificate Manager:

CNAME _TOKEN.CDN_DOMAIN → _VALIDATION.acm-validations.aws

5.5 SES (Email) DNS Records (Referenced Only)

These are required if using AWS SES:

MX   DOMAIN_NAME        inbound-smtp.REGION.amazonaws.com
MX   mail.DOMAIN_NAME   feedback-smtp.REGION.amazonses.com

TXT  DOMAIN_NAME        "v=spf1 include:amazonses.com ~all"
TXT  mail.DOMAIN_NAME   "v=spf1 include:amazonses.com ~all"

TXT  _dmarc.DOMAIN_NAME "v=DMARC1; p=none;"

Additional SES domain verification CNAME records are also required.

Full SES setup is outside the scope of this document but must exist.


6. AWS Region Strategy (Important)

AWS services used in this setup span multiple regions:

Service Region Reason
ACM (CloudFront cert) us-east-1 (North Virginia) Required by AWS for CloudFront
SES us-west-2 (Oregon) Closest supported SES region for AU
S3 Bucket ap-southeast-2 (Sydney) Lowest latency and cost
CloudFront Global Edge distribution

Key Notes

  • CloudFront only supports ACM certificates from us-east-1
  • S3 should be closest to your origin/server
  • SES region is chosen based on availability (not all regions support SES)

7. AWS Certificate Manager (ACM)

Create a certificate for:

CDN_DOMAIN

Steps (Summary)

  1. Request public certificate
  2. Use DNS validation
  3. Add provided CNAME record to DNS
  4. Wait for validation

8. S3 Bucket Configuration

8.1 Bucket

Name: S3_BUCKET_NAME (e.g. cdn.example.com)
Region: ap-southeast-2

8.2 Bucket Policy

{
  "Version": "2012-10-17",
  "Id": "PolicyForCDN",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::S3_BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
        }
      }
    },
    {
      "Sid": "AllowWordPressMediaManagement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNT_ID:user/wordpress-media"
      },
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::S3_BUCKET_NAME",
        "arn:aws:s3:::S3_BUCKET_NAME/*"
      ]
    }
  ]
}

9. CloudFront Distribution

9.1 General Settings

  • Alternate domain: CDN_DOMAIN
  • SSL Certificate: ACM certificate (us-east-1)
  • Security Policy: TLSv1.2-2021
  • HTTP Version: HTTP/2
  • IPv6: optional (can be disabled if not used)

9.2 Origin

Origin Domain:
S3_BUCKET_NAME.s3.ap-southeast-2.amazonaws.com
  • Origin Access: Origin Access Control (OAC) (recommended)
  • Do NOT expose S3 publicly

9.3 Behaviour

Setting Value
Path Pattern *
Allowed Methods GET, HEAD
Viewer Protocol Policy Redirect HTTP → HTTPS
Compression Enabled
Cache Policy Caching Optimized
Response Headers CORS with preflight + security headers

Note: AWS has restricted custom cache tuning unless using higher-tier plans.


10. IAM User Configuration

10.1 User

Name: wordpress-media

Used by WordPress (via AWS credentials).


10.2 Policy: S3 Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WordPressMediaBucketAccess",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::S3_BUCKET_NAME",
        "arn:aws:s3:::S3_BUCKET_NAME/*"
      ]
    }
  ]
}

10.3 Policy: CloudFront Invalidation

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CloudFrontInvalidationAccess",
      "Effect": "Allow",
      "Action": "cloudfront:CreateInvalidation",
      "Resource": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
    }
  ]
}

This enables future cache invalidation support (not yet fully implemented).


11. AWS Credentials on Linode

Because this environment is not EC2, IAM Roles cannot be used.

Instead, credentials are stored locally:

/home/nginx/.aws/credentials

Example:

[wordpress-media]
aws_access_key_id = ACCESS_KEY
aws_secret_access_key = SECRET_KEY
region = ap-southeast-2

Important Notes

  • File permissions must be restricted
  • nginx/php-fpm must have access
  • This is less secure than IAM Roles (to be improved in future EC2 version)

12. nginx / PHP-FPM Integration Notes

  • Ensure PHP-FPM (www.conf) allows access to:
    • /home/nginx/.aws
  • Adjust:
    • open_basedir
    • file permissions

Full configuration will be covered in a separate document.


13. Security Considerations

This setup follows good baseline practices, but:

  • IAM user credentials are stored on disk (risk vs IAM Role)
  • Bucket is restricted via:
    • CloudFront OAC
    • IAM user policy
  • TLS enforced via CloudFront
  • CAA records restrict certificate authorities

Future Improvements

  • Migrate to EC2 with IAM Role
  • Restrict IAM access by IP (LINODE_IPV4)
  • Implement signed URLs if needed
  • Review policies periodically

14. Key Takeaways

  • This is not just a plugin install — it requires coordinated setup across:
    • DNS
    • AWS (multiple services)
    • Linux/nginx/PHP
  • Region selection in AWS is not arbitrary
  • Security is a balance between:
    • practicality (Linode + IAM user)
    • best practice (IAM roles, tighter policies)

15. Next Documents

Planned follow-ups:

  1. Linux / nginx / PHP-FPM Configuration Details
  2. WordPress mu-plugin (CDN integration code)
  3. Advanced CDN behaviour & invalidation strategy

Disclaimer: This content is provided as reference only and reflects practical experience at the time of writing. Technology and best practices change, so examples may require modification. No warranty is provided. Always test configurations on a development system before using them in production.