### Introduction

Static WordPress websites are fast, cheap, and secure. By exporting your site into static files and hosting them on [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) + [Amazon CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html), you remove PHP/MySQL overhead and drastically shrink the attack surface. But there’s a catch: once your site is static, the native _WordPress authentication_ no longer works. How do you secure premium downloads, member dashboards, or course pages?

**Solution:** the [_WordPress Static Site Guardian_](https://github.com/smartcloudsol/wordpress-static-site-guardian/), deployed from the [AWS Serverless Application Repository (SAR)](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). It provides:-   CloudFront **signed cookie** protection for selected URL paths
-   **API Gateway + Lambda** endpoints to issue and clear cookies
-   **KMS + SSM** backed key management
-   No-code integration with the [Gatey](https://wpsuite.io/gatey/) plugin for Amazon Cognito login

#### What this stack does

-   Hosts your static export on S3/CloudFront and restricts paths using [CloudFront signed cookies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html).
-   Issues/clears cookies via [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) + [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html).
-   Automatically provisions [Route 53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) DNS records (when enabled) — so you don’t have to edit DNS manually.
-   Integrates with [Gatey](https://wpsuite.io/gatey/) so Sign-In/Sign-Out call the cookie endpoints — **no JavaScript needed**.

#### Static vs Dynamic WordPress

 Feature | Dynamic WP (PHP/MySQL) | Static WP (S3 + CloudFront) |
| --- | --- | --- |
 **Speed** | Server-rendered, depends on PHP/DB | CDN edge-cached, ultra-fast |
 **Security** | Core/plugins = larger attack surface | Minimal surface (static files) |
 **Scalability** | Bound by server resources | Virtually unlimited via CloudFront |
 **Authentication** | Built-in WP login | SAR + Gatey (signed cookies) |
 **Maintenance** | Patching, DB backups | File sync + cache invalidation |

[Deploy from AWS SAR →](https://us-east-1.console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:637423296378:applications/wordpress-static-site-guardian)

[Try Gatey Free →](https://wordpress.org/plugins/gatey/)

### Prerequisites

-   Valid [ACM](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) certificate in 
    ```
    us-east-1
    ```
     (covers apex domain + API subdomain)
-   Installed and configured [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
-   CloudFront key pair (generate below)
-   Registered domain (e.g. via [Route 53](https://aws.amazon.com/route53/))

[![AWS Certificate Manager showing issued SSL certificate for domain and subdomain.](https://wpsuite.io/wp-content/uploads/2025/09/acm.png "Issue ACM Certificate for WordPress Static Site Domain")](https://wpsuite.io/wp-content/uploads/2025/09/acm.png)

ACM console confirming SSL certificates for kirodev.wpsuite.io and its wildcard subdomain, required for CloudFront and API Gateway.

#### Generate the CloudFront key pair

Use the provided script to generate an RSA key pair and store the private key securely in SSM (KMS-encrypted). You’ll get the 
```
KmsKeyId
```
 and 
```
PublicKeyContent
```
 to pass into the stack.

```bash
# Download the key generation script
curl -O https://raw.githubusercontent.com/smartcloudsol/wordpress-static-site-guardian/refs/heads/main/scripts/generate-cloudfront-keypair.sh
chmod +x generate-cloudfront-keypair.sh

# Generate keys and store in AWS
./generate-cloudfront-keypair.sh --name my-wordpress-keys --region us-east-1
```

Copy

[![AWS CLI terminal showing CloudFront key pair generation for WordPress Static Site Guardian deployment.](https://wpsuite.io/wp-content/uploads/2025/09/generate-keypair.png "Generate CloudFront Key Pair in AWS CLI for WordPress Static Site Guardian")](https://wpsuite.io/wp-content/uploads/2025/09/generate-keypair.png)

Running the generate-cloudfront-keypair.sh script to create a CloudFront key pair and KMS-backed private key for signed cookie authentication.

References: [AWS SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) • [Signed cookies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html)

### Deploy from AWS SAR

1.  Open **AWS Console → Serverless Application Repository** and search for _WordPress Static Site Guardian_ or follow this [link](https://us-east-1.console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:637423296378:applications/wordpress-static-site-guardian).
2.  Fill parameters:  -   ```
          DomainName
          ```
           → _example.com_
      6.   ```
          ApiDomainName
          ```
           → _api.example.com_
      10.   ```
          CertificateArn
          ```
           (ACM in _us-east-1_)
      14.   ```
          KmsKeyId
          ```
           and 
          ```
          PublicKeyContent
          ```
           from the script
      22.   ```
          ProtectedPaths
          ```
           → e.g. 
          ```
          /members,/downloads
          ```
          
      30.   ```
          SigninPagePath
          ```
           → e.g. 
          ```
          /sign-in
          ```
          
      38.   ```
          CreateDNSRecords
          ```
           → _true_ to auto-create Route 53 DNS records
    
3.  Click **Deploy**. The stack provisions S3, CloudFront, API Gateway, Lambda, KMS/SSM, and (optionally) Route 53 records.

[![AWS SAR deployment screen for WordPress Static Site Guardian application.](https://wpsuite.io/wp-content/uploads/2025/09/sar.png "Deploy WordPress Static Site Guardian from AWS SAR")](https://wpsuite.io/wp-content/uploads/2025/09/sar.png)

Reviewing and configuring application settings before deploying the WordPress Static Site Guardian stack from the AWS Serverless Application Repository.

[![AWS CloudFormation console showing successful SAR stack deployment.](https://wpsuite.io/wp-content/uploads/2025/09/deployment.png "Verify WordPress Static Site Guardian Stack in CloudFormation")](https://wpsuite.io/wp-content/uploads/2025/09/deployment.png)

The CloudFormation deployment timeline confirms that the WordPress Static Site Guardian SAR stack completed successfully.

More: [Deploying apps from SAR](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/using-apps.html)

### Upload the static WordPress export

-   Export your site (Simply Static, WP2Static, etc.).
-   Upload to the provisioned S3 bucket under the configured 
    ```
    wwwroot
    ```
     prefix (e.g. with [AWS CLI 
    ```
    s3 sync
    ```
    ](https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html)).
-   Invalidate CloudFront after updates ([docs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html)).

**Heads-up:** The stack expects the static site under a non-empty S3 prefix (e.g. 
```
wwwroot/
```
). Upload content to 
```
s3://<bucket>/wwwroot/
```
, not the bucket root.

[![WordPress Simply Static plugin generating a static export with logs.](https://wpsuite.io/wp-content/uploads/2025/09/simply-static.png "Export WordPress to Static Files with Simply Static")](https://wpsuite.io/wp-content/uploads/2025/09/simply-static.png)

The Simply Static plugin dashboard showing an export log, including generated pages, transferred files, and the destination URL.

[![AWS CLI uploading static WordPress export to S3 bucket with s3 sync.](https://wpsuite.io/wp-content/uploads/2025/09/s3-upload.png "Upload Static WordPress Files to S3 with AWS CLI")](https://wpsuite.io/wp-content/uploads/2025/09/s3-upload.png)

Using the aws s3 sync command to push the exported WordPress site into the wwwroot prefix of the SAR-provisioned S3 bucket.

### Integrate with Gatey (no JavaScript required)

In WordPress open **Gatey → Settings → API** and configure:-   **API**: 
    ```
    backend
    ```
    
-   **Sign In Path**: 
    ```
    /issue-cookie
    ```
    
-   **Sign Out Path**: 
    ```
    /issue-cookie?action=signout
    ```
    
-   **With Credentials**: enabled
On sign-in Gatey calls 
```
/issue-cookie
```
 to set CloudFront signed cookies; on sign-out it calls 
```
/issue-cookie?action=signout
```
 to clear them. No custom JS is needed — hooks run from the admin UI. Helpful: [Gatey product page](https://wpsuite.io/gatey/)

[![WordPress Gatey plugin API and hook settings for CloudFront signed cookie integration.](https://wpsuite.io/wp-content/uploads/2025/09/gatey.png "Configure Gatey API Hooks for WordPress Static Site Guardian")](https://wpsuite.io/wp-content/uploads/2025/09/gatey.png)

Gatey settings page showing API configuration (backend) and hooks for Sign In and Sign Out paths pointing to /issue-cookie.

### End-to-end test

1.  Visit a public page → loads normally.
2.  Visit a protected path → redirects to your 
    ```
    /sign-in
    ```
     page.
3.  Sign in via Gatey (Cognito) → the API issues the cookie → reload grants access.
4.  Open a protected download → served with CloudFront signed cookies.
5.  Sign out → cookie cleared → access revoked.

### Cookie lifetime, domain & CORS

-   Align cookie TTL with your Cognito [refresh token](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-tokens.html) policy (commonly ~30 days).
-   Scope cookies to the apex domain so they’re valid for the site and API subdomain.
-   API Gateway should allow credentials and your site origin in CORS ([guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html)).

### Monitoring & rollback

-   Inspect [CloudWatch Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) for Lambda + API Gateway.
-   Watch CloudFront metrics (hit/miss, spikes in 403).
-   Rollback by redeploying a previous template version if needed.

### Pro Tips & Common Mistakes

#### Pro tips

-   Automate export → S3 sync → CloudFront invalidation via CI/CD ([CodePipeline](https://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html) or GitHub Actions).
-   Add custom 403/404 pages for a polished UX.
-   Alert on 401/403 spikes using CloudWatch alarms.
-   Keep separate stacks for staging vs production.

#### Common mistakes

-   Uploading files to the bucket root instead of 
    ```
    wwwroot/
    ```
    .
-   ACM certificate not in 
    ```
    us-east-1
    ```
     (CloudFront requires it).
-   Mismatched cookie TTL vs Cognito refresh token lifetime.
-   Forgetting to enable “With Credentials” in Gatey API settings.

### Pre-publish checklist

-   ACM certificate issued in 
    ```
    us-east-1
    ```
     for domain + API subdomain.
-   CloudFront key pair generated; 
    ```
    KmsKeyId
    ```
     + 
    ```
    PublicKeyContent
    ```
     recorded.
-   SAR stack deployed with 
    ```
    CreateDNSRecords=true
    ```
     (or DNS mapped manually).
-   Static site uploaded under 
    ```
    wwwroot/
    ```
    ; CloudFront invalidated.
-   Gatey API hooks set to 
    ```
    /issue-cookie
    ```
     and 
    ```
    /issue-cookie?action=signout
    ```
     with credentials.
-   E2E test: login → cookie → protected access → logout → revoked.

### Key takeaways

-   Static WordPress keeps speed and security — SAR + signed cookies bring back access control.
-   One deploy: S3, CloudFront, API Gateway, Lambda, KMS/SSM, and optional Route 53 auto-DNS.
-   Gatey connects Cognito to cookie issuance with admin-configured hooks — no custom JS.