Magento 2 and AWS
AWS provides the building blocks for enterprise-grade Magento 2 hosting, but assembling them correctly requires deep knowledge of both the platform and the cloud. This case study details our production-proven AWS architecture, including the Terraform module boundaries that prevent blast radius issues, Aurora scaling strategies for variable traffic, and the CloudFront configuration pitfalls that are specific to Magento's cookie and caching behavior.
Terraform Module Boundaries
Infrastructure as Code is only as good as its module design. A single monolithic Terraform configuration that manages VPC, databases, compute, and CDN in one state file creates a dangerous blast radius — a change to CloudFront cache behavior shouldn't appear in the same terraform plan as your RDS instance configuration. One wrong approval and you're modifying your production database alongside a CDN tweak.
We split the infrastructure into discrete, independently deployable Terraform modules that reflect Magento's operational layers. The networking module manages VPC, subnets (public, private, data), NAT gateways, VPC endpoints, and security group definitions. The data module handles RDS Aurora instances, ElastiCache clusters, S3 buckets, and their associated IAM policies. The compute module manages EKS cluster configuration, node groups, Karpenter provisioners, and Kubernetes addon installations. The CDN module handles CloudFront distributions, WAF web ACLs, and Route53 DNS records.
Each module has its own state file stored in S3 with DynamoDB state locking. Module outputs are shared via Terraform remote state data sources — the compute module reads VPC subnet IDs from the networking module's state, the CDN module reads the S3 bucket ARN from the data module's state. This creates explicit dependency declarations that are version-controlled and auditable.
Environment promotion becomes trivial with this structure. Staging and production use the same Terraform modules with different tfvars files. The only differences between environments are instance sizes, replica counts, and domain names. When a change is validated in staging, promoting it to production is a PR that updates the production tfvars file — same code, different parameters, full peer review.
Every Terraform change goes through a CI pipeline: terraform plan runs automatically on PR creation, the plan output is posted as a PR comment for review, and terraform apply only runs after merge to the main branch. This prevents any infrastructure change from reaching production without peer review and explicit approval.
Aurora Serverless & Compute Layer
RDS Aurora Serverless v2 is a legitimate and cost-effective option for Magento stores with non-uniform traffic patterns. Most e-commerce sites have predictable daily cycles: business-hours traffic from 8am–10pm, followed by quiet overnight periods. Traditional provisioned instances force you to size for peak and pay for idle capacity during off-hours.
Aurora Serverless v2 scales in ACU (Aurora Capacity Units) increments, adjusting compute and memory allocation within seconds as traffic changes. A store that needs 8 ACUs during business hours and 2 ACUs overnight pays only for what it uses. Over a month, this typically saves 30–40% compared to an equivalently sized provisioned instance.
The key gotcha with Aurora Serverless is max_connections. Aurora's connection limit scales with ACUs, but connection pools (ProxySQL, application-level pools) don't automatically adjust when ACUs scale up or down. If the application maintains 200 persistent connections and Aurora scales down to a level that only supports 150, connections start failing. We solve this by setting a conservative max_connections parameter that works at the minimum ACU level and using ProxySQL to multiplex application connections over a smaller number of database connections.
Aurora's read replicas handle read-heavy Magento workloads (category browsing, search, product detail pages) while the writer instance handles transactions (checkout, admin operations). PHP's database configuration routes SELECT queries to read replicas and INSERT/UPDATE/DELETE to the writer — Magento supports this natively via its deployment configuration.
The EKS compute layer runs on managed node groups with Karpenter optimizing instance selection. Karpenter's consolidation feature periodically evaluates whether running pods could fit on fewer, more appropriately sized nodes — preventing the gradual accumulation of underutilized instances that plagues static node group configurations. Fargate handles batch processing and cron jobs that have unpredictable resource requirements, eliminating the need to provision dedicated nodes for sporadic workloads.
CloudFront Magento-Aware Configuration
CloudFront is AWS's CDN and the natural choice for serving Magento's static assets and potentially caching dynamic pages. But the default CloudFront configuration will cause serious problems with Magento if not carefully customized — including the infamous scenario of serving one customer's shopping cart to another.
The root issue is cookies. Magento sets several cookies on every response: PHPSESSID for the session, form_key for CSRF protection, mage-cache-storage for client-side cache management, and potentially dozens of others from third-party modules. Default CloudFront behavior forwards all cookies to the origin and includes them in the cache key. This means every unique cookie combination creates a separate cache entry — effectively defeating caching entirely for logged-in users.
Worse, if CloudFront is configured to ignore cookies for caching (a common 'fix'), it serves cached pages that contain another user's session data. The category page cached from User A's request (which includes User A's mini-cart, their name in the header, and their CSRF form_key) gets served to User B. This is both a security vulnerability and a functional disaster.
The correct configuration requires Magento-specific behavior rules. Static assets (pub/static/*, media/*) get aggressive caching with all cookies stripped — these resources are genuinely static and session-independent. Dynamic pages get cookie whitelisting: only forward the specific cookies that affect page content (store, currency, customer_group) and exclude session cookies entirely. Pages that are always personalized (/checkout/*, /customer/*, /rest/*, /graphql) bypass CloudFront entirely and go directly to the origin.
We also configure CloudFront to forward the X-Magento-Vary header, which Magento uses to signal cache variations (different content for different customer groups, different store views). This header, combined with the whitelisted cookies, forms the cache key that correctly separates public catalog pages from customer-specific content.
The Accept-Encoding header must be forwarded to enable origin-level compression negotiation. CloudFront's automatic compression handles gzip and Brotli for responses without a Content-Encoding header, but Magento's built-in compression (when enabled) can conflict with CloudFront's compression if the header forwarding isn't configured correctly.
Security & GitOps Deployment
Security in a Magento AWS deployment spans multiple layers, and each layer must be configured with Magento's specific attack surface in mind. The most common attack vectors for Magento stores are credential stuffing on customer login, brute-force on admin login, SQL injection via poorly sanitized search queries, and Magecart-style JavaScript injection targeting checkout pages.
AWS WAF provides the first line of defense with rate-limiting rules that detect and block credential stuffing attempts. We configure separate rate limits for /customer/account/loginPost (customer login) and the admin URL (which should always be a custom path, not /admin). IP reputation filtering blocks known malicious IPs, and managed rule groups detect common SQL injection and XSS patterns.
Secrets management uses AWS Secrets Manager with automatic rotation for database credentials, API keys, and encryption keys. Magento's env.php configuration reads sensitive values from environment variables injected by Kubernetes, which in turn reads them from Secrets Manager via the External Secrets Operator. This means no credentials are stored in the Docker image, the Git repository, or the Kubernetes manifests — they exist only in Secrets Manager and in-memory within the running container.
VPC endpoints keep traffic between AWS services internal to the AWS network. S3, SQS, Secrets Manager, and ECR are all accessed via VPC endpoints rather than public internet endpoints. This eliminates data exfiltration risk via the public internet path and improves latency by avoiding the NAT gateway for AWS service traffic.
Application deployment follows GitOps principles via ArgoCD. The desired state of the Kubernetes cluster is declared in a Git repository containing Helm value files and Kustomize overlays. ArgoCD continuously reconciles the cluster's actual state against the declared state — if someone manually runs kubectl apply (which shouldn't happen), ArgoCD reverts the change within its sync interval. This provides a complete audit trail of every deployment: who approved the PR, what changed, when it was merged, and when ArgoCD applied it to the cluster.
CloudTrail and GuardDuty provide security monitoring and threat detection. GuardDuty's anomaly detection catches unusual API call patterns (like someone enumerating S3 buckets or attempting to escalate IAM privileges) and triggers automated responses via EventBridge rules — including immediate Slack notification and, for critical findings, automatic security group changes that isolate compromised resources.
Results
- Infrastructure provisioning reduced from days to 30 minutes
- 30–40% cost savings with Aurora Serverless and Karpenter spot instances
- RPO of 1 second, RTO under 15 minutes with Aurora cross-region replication
- Zero misconfigurations reaching production via GitOps pipeline
- Eliminated customer data leakage via Magento-aware CloudFront behavior rules
- Automated credential rotation with zero application downtime
Want to discuss a similar challenge? Get in touch →