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_NAMEwww.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)
- Request public certificate
- Use DNS validation
- Add provided CNAME record to DNS
- 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:
- Linux / nginx / PHP-FPM Configuration Details
- WordPress mu-plugin (CDN integration code)
- Advanced CDN behaviour & invalidation strategy

