Serverless WordPress architecture on AWS
Tags: serverless, wordpress, aws, lambda
Published: 2021-08-17 15:35:14

---
title: Serverless WordPress architecture on AWS
published: true
tags: serverless, wordpress, aws, lambda
canonical_url: https://blog.ymirapp.com/serverless-wordpress-aws/
---

<!-- wp:paragraph -->
<p><a href="https://blog.ymirapp.com/serverless-php/">Serverless PHP</a> is a new exciting technology that has the potential to remove a lot of the burden of hosting PHP applications. One type of application that has the most to gain from serverless PHP is <a href="https://en.wikipedia.org/wiki/WordPress">WordPress</a>. Serverless PHP eases the burden of scaling WordPress while offering the same performance benefits that you'd get with a top tier host.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To understand how serverless PHP works with WordPress, we'll look at the current state of the modern WordPress server architecture. This architecture has evolved a lot over the last decade. (You can read more about it <a href="https://carlalexander.ca/modern-wordpress-server-stack/">here</a>.) Gone are the days of just hosting a WordPress site with an <a href="https://en.wikipedia.org/wiki/Apache_server">Apache server</a>! There's a lot more to hosting a WordPress site now.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The good news is that hosting a serverless WordPress site looks a lot like it does with a modern WordPress server stack. The big change is that you're going to replace a lot of the architecture components with services from a <a href="https://en.wikipedia.org/wiki/Cloud_computing">cloud</a> provider.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, the services that you'll use and how they fit together will vary from one cloud provider to another. With serverless PHP, the most popular cloud provider is <a href="https://en.wikipedia.org/wiki/Amazon_Web_Services">AWS</a>. That's why we'll focus on serverless WordPress architecture on AWS.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It's also worth noting that this is the same architecture that you'd get using <a href="https://ymirapp.com">Ymir</a>. The only difference is that Ymir takes care of managing it all for you. (That's also why it's a <a href="https://en.wikipedia.org/wiki/DevOps">DevOps</a> platform.) But if you don't mind putting all the pieces together yourself or with the help of another tool (such as the <a href="https://en.wikipedia.org/wiki/Serverless_Framework">serverless framework</a>, this article will help you achieve that.</p>
<!-- /wp:paragraph -->

<!-- wp:more -->
<!--more-->
<!-- /wp:more -->

<!-- wp:heading {"level":1} -->
<h1>The modern WordPress server architecture</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Let's begin with the modern WordPress server architecture. What does it like? Below is a diagram that shows the big picture of what's going on when a browser makes a request for a WordPress page.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":102,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/modern-server-stack-1024x252.png" alt="" class="wp-image-102"/><figcaption><a href="https://blog.ymirapp.com/uploads/2021/05/modern-server-stack.png">Full size</a></figcaption></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2>Caching before PHP</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>First, the <a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message">HTTP request</a> your browser makes is going to hit a <a href="https://en.wikipedia.org/wiki/Web_cache">HTTP cache</a>. This HTTP cache could be a combination of a <a href="https://en.wikipedia.org/wiki/Content_delivery_network">content delivery network</a> (also known as CDN) like <a href="https://en.wikipedia.org/wiki/Cloudflare">Cloudflare</a>, a dedicated server like <a href="https://en.wikipedia.org/wiki/Varnish_(software)">Varnish</a> or a page caching plugin. But whatever you're using, the goal is the same.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You want to cache WordPress responses.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>That's because if PHP (and by extension WordPress) is a lot slower than your HTTP cache. So you want your HTTP responses to come from the HTTP cache as much as possible. If the HTTP cache can't respond to your request, then it'll forward the request to WordPress.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Caching on the PHP side</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Once you hit PHP, things are a bit more straightforward. The higher the PHP version, the faster WordPress runs. That's why you always try to use the highest PHP version that WordPress supports.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The last point of optimization comes when WordPress makes queries to the <a href="https://en.wikipedia.org/wiki/MySQL">MySQL database</a>. These queries often account for most of the slowness that WordPress will have responding to your request. To make WordPress faster, you want to reduce the number of queries WordPress makes as much as possible.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You can achieve this by using an <a href="https://codex.wordpress.org/Class_Reference/WP_Object_Cache">object cache plugin</a>. An object cache plugin stores the results of a MySQL query in a high performance persistent cache. (Usually <a href="https://en.wikipedia.org/wiki/Memcached">Memcached</a> or <a href="https://en.wikipedia.org/wiki/Redis">Redis</a>.) It then checks the cache before performing a query and returns the result if it was present. (Much like the HTTP cache does for our HTTP requests.)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This drastically reduces the time that WordPress takes to respond to a request. Especially if your WordPress project has plugins that make a lot of queries such as with WooCommerce. That's why it's an essential component of the modern WordPress server architecture.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":1} -->
<h1>Serverless WordPress architecture</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that we've gone over the modern WordPress server architecture, let's look how it translates to serverless. The reason we went over it is that the essential components stay the same. The difference is that all these components are going to be handled by specific AWS services.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":105,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-1024x138.png" alt="" class="wp-image-105"/><figcaption><a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress.png" data-type="URL" data-id="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress.png">Full size</a></figcaption></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2>HTTP cache using CloudFront</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>So let's go back to our browser, the first thing that your HTTP request is going to hit is <a href="https://en.wikipedia.org/wiki/Amazon_CloudFront">CloudFront</a>. CloudFront is the AWS content delivery network. You can use it to do a lot of different things for your serverless WordPress site.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>WordPress sites only use CloudFront (or any other CDN) to cache assets like CSS/JS files and media files. But you can also use it as a HTTP cache. This is where the real performance gains happen.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Using CloudFront as a HTTP cache will significantly reduce the response time of your WordPress site. But because CloudFront is also a content delivery network, this reduction applies to all visitors regardless of where they are in the world. This is a big difference compared to most WordPress HTTP cache solutions which aren't globally distributed.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you'd rather not use CloudFront to do page caching, you can also configure it to just cache CSS/JS files and media files. You're also free to not use CloudFront and either use another content delivery network or none. But beware that performance won't be nearly as good without a content delivery network.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>On the way to Lambda: API Gateway or Elastic Load Balancing</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If CloudFront can't respond to a request, it'll forward the request to your serverless PHP application hosted on AWS Lambda. Now, much like a regular PHP application, a serverless PHP application isn't exposed to the internet. You need to use another service to act as an intermediary between the two.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>With a regular PHP application, this would be the role of your web server. But with serverless PHP, there's no web server per se. Instead, you need to use one of two services to communicate with AWS Lambda. These are <a href="https://aws.amazon.com/api-gateway/">API Gateway</a> or <a href="https://aws.amazon.com/elasticloadbalancing/">Elastic Load Balancing</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In general, you'll want to use an API gateway. (That's why it's the one shown on the diagram.) You should only use a load balancer is when you're dealing with hundreds of million of requests per month. (For example, you'll save several thousand dollars per month between serving a billion requests with a load balancer compared to an API gateway.) But most WordPress sites don't get that much traffic so using an API gateway is the cheaper option.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>API gateway types</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>AWS also has two different API gateways: <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html">HTTP API and REST API</a>. You can use either of them. That said, there are some important differences between the two.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First, there's the question of cost. The REST API costs more than the HTTP API. That's because the REST API has more features than the HTTP API.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>What are these extra features? Well, it offers http-to-https redirection. But this isn't necessary if you use CloudFront to do page caching. It'll handle the http-to-https redirection.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It also offers edge optimized caching. But this is also something that CloudFront handles for us. In fact, you can't use both REST API edge optimized caching and CloudFront at the same time.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A REST API also supports wildcard subdomains. This is only important if you want to host a subdomain <a href="https://wordpress.org/support/article/glossary/#multisite">multisite</a> installation. Otherwise, you don't need it either. (You can also work around the HTTP API limitation with wildcard subdomains by using <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html">CloudFront functions</a>.)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This is all to show you that most of time you don't need the additional REST API features. So you're better off paying less and using the HTTP API instead.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Lambda</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Once the request reaches either an API gateway or a load balancer, it gets converted to an event. This event triggers the Lambda function to run.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This Lambda function has two components. The PHP runtime which converts the event to a PHP <a href="https://en.wikipedia.org/wiki/FastCGI">FastCGI</a> request. The other one is your WordPress project which is what the FastCGI request will run.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, you can create a PHP runtime but it's probably better to use an existing one. <a href="https://github.com/brefphp/bref">Bref</a> is the popular open source PHP runtime for AWS Lambda so it's a good choice to pick. Otherwise, <a href="https://github.com/ymirapp/php-runtime">Ymir's PHP runtime is also open source</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>ElastiCache</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If you're using an object cache, you'll need a Memcached or Redis cache. <a href="https://aws.amazon.com/elasticache/">ElastiCache</a> is the AWS service that manages those. So you'll need to create one if you want to use a persistent object cache.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you do decide to use one, be aware that you'll need to put your Lambda function on a private <a href="https://en.wikipedia.org/wiki/Subnetwork">subnet</a> to access it. This, in turn, will require a <a href="https://en.wikipedia.org/wiki/Network_address_translation">NAT</a> gateway. You can also replace the NAT gateway with a specific EC2 instance that acts as the gateway. (You can read about how to do it <a href="https://www.kabisa.nl/tech/cost-saving-with-nat-instances/">here</a>.)</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>VPC</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This is a good time to bring up the <a href="https://en.wikipedia.org/wiki/Virtual_private_cloud">virtual private cloud</a> also known as <a href="https://aws.amazon.com/vpc/">VPC</a>. AWS requires that certain resources such as your cache and database (which we'll see next!) be connected to a VPC. Some have to be on a private subnet (network without access to the internet) while others can also be on a public subnet (network with access to the internet).</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If all you don't use ElastiCache, your interactions with a VPC are minimal. You'll just need a public subnet for the RDS database. Here's an updated diagram showing the VPC without ElastiCache.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":108,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet-1024x187.png" alt="" class="wp-image-108"/><figcaption><a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet.png" data-type="URL" data-id="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-public-subnet.png">Full size</a></figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Things are a more complicated if you need to use private resources like ElastiCache. This is where you need to add the NAT gateway discussed earlier. But you also need to change certain resources to use the private subnet such as your Lambda function. (By default, Lambda Functions don't need a subnet.)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You'll also need a <a href="https://en.wikipedia.org/wiki/Bastion_host">bastion host</a> to connect to the resources. A bastion host is an EC2 instance that acts as a bridge to the private subnet. For example, a bastion host is necessary if you want to connect to your database server if it's on the private subnet.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":109,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet-1024x257.png" alt="" class="wp-image-109"/><figcaption><a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet.png" data-type="URL" data-id="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-private-subnet.png">Full size</a></figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Above is a diagram showing the same architecture with ElastiCache, NAT gateway, bastion host and the private subnet. As you can see, there are more moving parts. It's also more complicated in terms of VPC configuration.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>RDS</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The last component of our serverless WordPress architecture is the database. <a href="https://aws.amazon.com/rds/">RDS</a> is the service that manages <a href="https://en.wikipedia.org/wiki/Relational_database">relational databases</a> like <a href="https://en.wikipedia.org/wiki/MySQL">MySQL</a> and <a href="https://en.wikipedia.org/wiki/MariaDB">MariaDB</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you use a public subnet, your database is going to be publicly accessible. This isn’t much a security problem as long as you don’t share the database server address. But this makes the use of a strong password when creating it even more important.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>That said, if it’s on a private subnet, you should set a strong password anyway! But you don’t have to worry about the database being publicly accessible. However, like we mentioned with the VPC, you’ll need to create a bastion host to connect to it.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":1} -->
<h1>What about media uploads?</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>One thing we haven't talked about yet is uploads. What happens to uploads when you don't have a server? Well, that part is a lot more complicated than what we've seen so far!</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":111,"sizeSlug":"large","linkDestination":"none","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><img src="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3-1024x173.png" alt="" class="wp-image-111"/><figcaption><a href="https://blog.ymirapp.com/uploads/2021/05/serverless-wordpress-s3.png">Full size</a></figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Above is an updated diagram showing you what our serverless WordPress architecture looks like with media uploads. Things start off the same with requests going through CloudFront. Once the request hits CloudFront, it'll decide whether this is a serverless PHP request or a request for a media file. (Not just an uploaded file)</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If it's a media file, the request instead goes to <a href="https://aws.amazon.com/s3/">S3</a> which is the most well known AWS service. It's a file storage service that lets you store files in the cloud inexpensively. There are a lot of WordPress plugins that let you upload files to S3.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>How most S3 plugins work</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>That said, there are some notable differences when you use S3 with serverless WordPress compared to a plugin. The biggest one is how you send your WordPress media files to S3. With a plugin, it looks like this:</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":112,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/s3-plugin-flow-1024x299.png" alt="" class="wp-image-112"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>You upload a media file to your WordPress server like you would normally without a plugin. The plugin itself takes care to upload the file to S3 once it's on the server. Most of the time, this happens in the background using a <a href="https://developer.wordpress.org/plugins/cron/">WordPress cron</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>How does file uploads work with S3 and serverless WordPress?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>With serverless, you can't do that because there's no server to upload the file to first. Instead, you need to upload it to S3 right away. The proper way to do that is to use a <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html">signed URL</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":113,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="https://blog.ymirapp.com/uploads/2021/05/s3-plugin-upload-flow-1024x360.png" alt="" class="wp-image-113"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>A signed URL is a temporary URL that you can use to perform an operation on S3. So what you need to do is contact S3 and it'll create a signed URL. You then use this signed URL to upload the file to S3 without compromising your security.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This is a lot harder to do in practice since WordPress doesn't make it easy to take over this process. You also don't have direct access to the files on the server to create the different file sizes either. So you need to develop a way to create those as well.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This is by far the more complicated aspect of the serverless WordPress architecture. It's not really because of anything specific with AWS. It's just because WordPress is an <a href="https://en.wikipedia.org/wiki/Legacy_system">old piece of software</a>. Its creators designed it assuming you'd always have a server to upload files to. (The same with most S3 upload plugins as well.) There's no simple way to tell it you don't want to do it.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>That said, WordPress is very extensible. You can write a plugin (like the <a href="https://github.com/ymirapp/wordpress-plugin">Ymir one</a>) that can hijack the WordPress media upload process. (Another plugin that supports direct upload is <a href="https://en-ca.wordpress.org/plugins/ilab-media-tools/">Media Cloud</a>.) That's what's necessary for it to work with serverless.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":1} -->
<h1>Different, but also familiar</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>As you can see, a serverless WordPress installation isn't quite the same as a modern WordPress server architecture. But it's grounded in the same fundamentals. It's just that you have to rely on services from cloud providers like AWS.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But that's also what a lot of hosts already do. They rely on services like a content delivery network (CloudFront), managed databases (RDS) and managed Redis caches (ElastiCache). So there are some similar elements between the two.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But as the issue with media uploads highlighted, a lot of the architectural challenges with serverless come from WordPress itself. It's not that it can't run on Lambda. It's that some of its inner workings assume there will always be a server present.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>That said, overcoming these issues can be worthwhile. This is especially true if you need to scale WordPress or, even more so, WooCommerce. Serverless offers unparalleled scaling potential. That's also why it's the <a href="https://blog.ymirapp.com/why-serverless-perfect-hosting-woocommerce/">ideal platform for WooCommerce</a>.</p>
<!-- /wp:paragraph -->