Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Setting up a WordPress site on AWS

Setting up a WordPress site on AWS

This tutorial walks through setting up AWS infrastructure for WordPress, starting at creating an AWS account. We'll manually provision a single EC2 instance (i.e an AWS virtual machine) to run WordPress using Nginx, PHP-FPM, and MySQL.

This tutorial assumes you're relatively comfortable on the command line and editing system configuration files. It is intended for folks who want a high-level of control and understanding of their infrastructure. It will take about half an hour if you don't Google away at some point.

If you experience any difficulties or have any feedback, leave a comment. 🐬

Coming soon: I'll write another tutorial on a high availability setup for WordPress on AWS, including load-balancing multiple application servers in an auto-scaling group and utilizing RDS.

About AWS

Amazon Web Services (AWS) offers cloud computing services, including everything necessary to run a WordPress site. This is similar to a web hosting company service, with a few differences:

  • You have a high-level of control over the infrastructure. e.g. You can edit your php.ini file as well as setting up a load balancer to distribute load to multiple application servers.
  • Server resources are easy to provision and resize. Need to increase a server's RAM from 1GB to 30GB? Not a problem.
  • You only pay for the resources you use. Don't lock yourself in to a high priced plan for more resources that you might not need.

Create an AWS account

Sign up for an AWS account. The Free Pricing Tier includes a year of AWS usage to a basic set of server resources for free.

Log into the AWS console

Log into the AWS console, a web-based administrative interface for AWS.

Create an IAM user account

IAM (Identity and Access Management) is the AWS service for managing user access to other services and server resources.

When you log into the AWS web console with your main Amazon account, you're using AWS in root user mode, which is fine for our purposes.

In order to use the AWS command line interface in this tutorial, we'll create an IAM user rather than using the root user's credentials.

From the AWS Console homepage, under Administration & Security, click Identity & Access Management to go to the IAM dashboard.

Click Users to see a list of all registered IAM users for the AWS account, which should be empty. Click Create New Users, and create a user account for yourself and download the access key details. You'll use access key credentials to authenticate your computer with AWS.

An IAM Group defines a set of permissions. Users are assigned to groups, granting the user permissions.

While still in the IAM console, click Groups to see a list of all registered IAM Groups for the AWS account, which should be empty. Create a new IAM Group called "Administrators". Attach the AdministratorAccess policy to the group, which provides full access to all services.

Return to the Users list and add your user account to the "Administrators" group.

Install and configure the AWS command line interface

The AWS command line interface is a unified tool for managing services from your computer's terminal. Most AWS management tasks (e.g. creating a new EC2 instance) can be performed in the web console or on the command line. Which to use for any task will depend on workflow preferences.

Install the AWS command line interface for your computer's terminal.

Run aws configure, and supply the access key credentials for the IAM user created earlier. This authenticates your computer under that user account.

Set a default AWS region during this configuration. AWS regions are different data centers around the world you can choose to host resources. Your default is probably "us-west-2"; check the region dropdown in the menu bar in the web console to confirm this.

Create an EC2 key pair

An EC2 key pair is a cryptographic public and private key pair required to authorize ssh access to an EC2 instance. A key pair is tied to an IAM user. To authorize access to an EC instance, a private key exists on your computer which matches a public key associated with the instance.

If the folder ~/.ssh doesn't exist on your computer, create it. This is where ssh credential files are stored.

mkdir ~/.ssh

Create a key pair with the AWS CLI. Replace {{KEY_NAME}} with a key name and private key filename. I called mine "aws-eric".

aws ec2 create-key-pair --key-name {{KEY_NAME}} --query 'KeyMaterial' --output text > ~/.ssh/{{KEY_NAME}}.pem

This creates a new private key file in the ~/.ssh directory.

Change the file permissions for the private key so that only your user can read it.

chmod 400 ~/.ssh/{{KEY_NAME}}.pem

Create an EC2 instance

EC2 (Elastic Compute Cloud) is the service for managing generic-use virtual machines called EC2 instances. We'll create an EC2 instance to run as the WordPress web server.

From the AWS console homepage, click EC2 to enter the EC2 console. Click Instances to see a list of all EC2 instances for the account, which should be empty.

Click Launch Instance.

In "Step 1: Choose an AMI", we need to choose a disk image to launch an operating system onto the instance. Select the "Amazon Linux AMI (HVM)".

In "Step 2: Choose Instance Type", select the "t2.micro".

Continue through the wizard using the defaults.

In "Step 6: Configure Security Group", create a new Security Group called "WordPressApplicationServer". A Security Group is a firewall that limits traffic to an instance. Add a rule to allow SSH access, and under the Source column, only allow access from your IP. Add a rule to allow HTTP access from anywhere. Add a rule to allow HTTPS access from anywhere.

Review and Launch the instance. When prompted, choose the key pair created previously to authorize access to the instance. Booting will take a moment. Return to the instance list table to check its state.

SSH into the EC2 instance

From the instance list table, click the newly launched instance to open a details pane. Using the Public DNS (i.e. the hostname) listed here, login via ssh in a terminal.


The default system user account for Amazon Linux is "ec2-user," which has sudo access.

Update system packages

Update all system packages that may be out of date in the distribution.

sudo yum update

Download WordPress

The directory root for the site which Nginx serve files from will live in /sites/{{SITE_DOMAIN}}.com/public. Create the folder.

sudo mkdir -p /sites/{{SITE_DOMAIN}}.com/public

Download the latest stable WordPress version into the folder.

cd /sites/{{SITE_DOMAIN}}.com/public
sudo wget
sudo tar zxf latest.tar.gz
cd wordpress
sudo cp -rpf * ../
cd ../
sudo rm -rf wordpress/ latest.tar.gz

Install and configure Nginx

Nginx is a high-performance web server and reverse proxy.

sudo yum install nginx

Nginx installs with a basic configuration in /etc/nginx/. Overwrite this with more pragmatic defaults from HTML5 Boilerplate's nginx config.

We'll use git to checkout the H5BP nginx config git repository, so we'll install git to do that.

sudo yum install git
cd /etc
sudo mv nginx nginx-previous
sudo git clone nginx

Nginx creates a system user "nginx" to run the web server process. Update the nginx.conf file appropriately.

cd /etc/nginx
sudo vi nginx.conf
# Run as a less privileged user for security reasons.
user nginx nginx;

Change ownership of the site directory and its entire contents to the "nginx" system user.

sudo chown -R nginx:nginx /sites/{{SITE_DOMAIN}}.com/public

H5BP's Nginx configuration is langauge-agnostic — it doesn't include configuration for running scripts for any programming languages. Copy the fastcgi_params file from the default nginx configuration which we'll need to run a site via PHP-FPM.

sudo cp /etc/nginx-previous/fastcgi_params /etc/nginx

Create the folder for where Nginx logs will be written.

sudo  mkdir -p /usr/share/nginx/logs/

Nginx configuration needs to be written to route requests for the domain appropriately for the WordPress site. Inside the sites-available folder, create a new configuration file.

sudo vi /etc/nginx/sites-available/{{SITE_DOMAIN}}.com

Use the boilerplate below for the configuration. Replace {{SITE_DOMAIN}} with your site's domain name.

# Define the microcache path.
fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=microcache:100m inactive=60m;

# Redirect http traffic to https.
server {
  listen [::]:80;
  listen 80;

  server_name {{SITE_DOMAIN}}.com;

  return 301 https://{{SITE_DOMAIN}}.com$request_uri;

server {
  listen 443 ssl;

  server_name {{SITE_DOMAIN}}.com;

  # Include defaults for allowed SSL/TLS protocols and handshake caches.
  include h5bp/directive-only/ssl.conf;

  # config to enable HSTS(HTTP Strict Transport Security)
  # to avoid ssl stripping
  add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";

  ssl_certificate_key /etc/sslmate/{{SITE_DOMAIN}}.com.key;
  ssl_certificate /etc/sslmate/{{SITE_DOMAIN}}.com.chained.crt;

  # Path for static files
  root /sites/{{SITE_DOMAIN}}.com/public;

  #Specify a charset
  charset utf-8;

  # Include the basic h5bp config set
  include h5bp/basic.conf;
  location / {
    index index.php;
    try_files $uri $uri/ /index.php?$args;

  location ~ \.php$ {
    fastcgi_cache  microcache;
    fastcgi_cache_key $scheme$host$request_method$request_uri;
    fastcgi_cache_valid 200 304 10m;
    fastcgi_cache_use_stale updating;
    fastcgi_max_temp_file_size 1M;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME   $document_root$fastcgi_script_name;
    include        fastcgi_params;

    # Local variables to track whether to serve a microcached page or not.
    set $no_cache_set 0;
    set $no_cache_get 0;

    # If a request comes in with a X-Nginx-Cache-Purge: 1 header, do not grab from cache
    # But note that we will still store to cache
    # We use this to proactively update items in the cache!
    if ( $http_x_nginx_cache_purge ) {
      set $no_cache_get 1;
    # If the user has a user logged-in cookie, circumvent the microcache.
    if ( $http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
      set $no_cache_set 1;
      set $no_cache_get 1;

    # fastcgi_no_cache means "Do not store this proxy response in the cache"
    fastcgi_no_cache $no_cache_set;
    # fastcgi_cache_bypass means "Do not look in the cache for this request"
    fastcgi_cache_bypass $no_cache_get;

Create a symlink in the sites-enabled folder to enable the site.

sudo ln -s /etc/nginx/sites-available/{{SITE_DOMAIN}}.com /etc/nginx/sites-enabled/{{SITE_DOMAIN}}.com 

Always start Nginx when the system boots.

sudo chkconfig nginx on

We haven't started the Nginx web server, and won't just yet. An SSL certificate for the site's domain needs to be in place first, otherwise we'll encounter a fatal Nginx error when starting Nginx.

Register a domain

AWS is a domain registrar. Route 53 is the service for domain registration and DNS management.

From the AWS Console homepage, under Networking, click Route 53 to go to the Route 53 dashboard.

In the navigation menu, click Registered Domains to see the list of all domains registered for the AWS account, which should be empty. Click the Register Domain, and follow through the wizard to register a new domain.

Create an Elastic IP address

The domain should have DNS configured to point traffic to the EC2 instance. IP addresses of EC2 instances can change, so we can't create an A record to point the domain directly at the instance. We'll create an Elastic IP address, which we can set our domain name to resolve to, which in turn will direct traffic to the EC2 instance.

In the EC2 Console, Click Elastic IPs to see a list of all allocated Elastic IPs, which should be empty. Click Allocate New Address and create one. Select the new IP and click Associate Address. Select the instance created earlier to associate the IP with.

Configure the Domain to point to the Elastic IP

A Hosted Zone is a group of DNS records which define how the domain name will work. In the Route 53 console, click Hosted Zones to see all hosted zones, which should be empty. Click the Create Hosted Zone button. Enter the site's domain name and click Create.

Now in the context of the new hosted zone, click Create Record Set. Ensure the type is "A" for A Record. In the "Value" field, enter the Elastic IP Address created previously. This resolves traffic to the domain Elastic IP and thereby the EC2 instance.

If traffic comes in expecting the site under www.{{SITE_DOMAIN}}.com, this traffic should be accepted and redirected to a non-"www" version of the URL. Click Create a Record Set. Set the "name" to www.{{SITE_DOMAIN}}.COM. Set the "type" to "CNAME". Set "Value" to {{SITE_DOMAIN}}.COM} and save the record set. We'll handle the redirect at the nginx routing layer.

Create an SSL certificate

Severing your site over HTTPS is an absolute necessity to guarantee your users a basic amount of confidentiality and authenticity.

In the Nginx configuration for the site we created, the server is listening for traffic over the HTTPS port (443). We also stipulated the location of SSL certificates
(e.g. /etc/sslmate/{{SITE_DOMAIN}}.com.chained.crt). This assumes we'll register an SSL certificate with sslmate, which we'll do now.

Go to sslmate and register for an account.

The SSL certificate and key need to live on the EC2 instance. While SSHed into the instance, install the sslmate command line utility.

sudo wget -P /etc/yum.repos.d
sudo wget -P /etc/pki/rpm-gpg
sudo yum install sslmate

Buy an SSL certificate for the domain.

sudo sslmate buy {{SITE_DOMAIN}}.com

sslmate will email the contact of your choosing associated with the domain name. Open the email and follow the authorization link.

The sslmate program will remain open until the authorization email is confirmed. After it does, it will alert you of the key and certificate files it created.

Waiting for ownership confirmation...

Your certificate is ready for use!

           Private key: /etc/sslmate/{{SITE_DOMAIN}}.com.key
           Certificate: /etc/sslmate/{{SITE_DOMAIN}}.com.crt
     Certificate chain: /etc/sslmate/{{SITE_DOMAIN}}.com.chain.crt
Certificate with chain: /etc/sslmate/{{SITE_DOMAIN}}.com.chained.crt

## Start Nginx

Now that the SSL certificates exist, start Nginx.

sudo service nginx start

## Install PHP

We'll run PHP with the [PHP-FPM package](

sudo yum install php-fpm php-mysql

The PHP-FPM configuration assumes the user invoking it will be "apache". Replace "apache" with "nginx" in the configuration file.

sudo vi /etc/php-fpm.d/www.conf

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache Choosed to be able to access some dir as httpd
user = nginx
; RPM: Keep a group allowed to write in log dir.
group = nginx

Start the PHP-FPM server.

sudo service php-fpm start

THe PHP-FPM server should always be on — tell the system to initialize it on startup.

sudo chkconfig php-fpm on

## Install and configure MySQL

Install the MySQL system package.

sudo yum install mysql-server

Start the MySQL daemon.

sudo service mysqld start

Always start the MySQL daemon when the system boots.

sudo chkconfig mysqld on

Run the MySQL secure installation to establish basic security settings.

sudo /usr/bin/mysql_secure_installation

Enter a secure root password. Remove anonymous users. Disable root login remotely. Remove test database and access to it. Reload privilege tables now.

Log into the MySQL interactive shell, create a database and a MySQL user with limited privileges. Replace `{{WP_DATABASE_NAME}}`, `{{WP_DATABASE_USER}}`, and `{{WP_DATABASE_USER_PASSWORD}}` with your preferred credentials.

mysql -u root -p


## Configure WordPress via the web installer

Visit your site in the browser, which will load the WordPress five minute install screen. Configure the install with the database credentials created earlier.

## Follow-up

At this point the WordPress install is running. There are a few other administrative activities to fine-tune the install.

## Install Memcached

[Memcached]( is an object caching system which can be used to speed up PHP script processing by avoiding expensive database queries with WordPress' Caching API. Read more about [Memcached and WordPress]( 

Install the Memcached system package. 

yum install memcached

Always start Memcached when the system boots.

sudo chkconfig memcached on

Start Memcached.

sudo service memcached start

Install the PHP Memcache package.

sudo yum install php-pear php-pecl-memcache

Restart Nginx and PHP-FPM.

sudo service php-fpm restart
sudo service nginx restart

Install the [WordPress Memcached Drop-in]( by copying `object-cache.php` into the `wp-content` folder.

## Install Fail2Ban

[Fail2Ban]( watches application logs for malicious activity and bans IP addresses from interacting with your server if any mischief is found. This will protect you from brute force attacks.

Install the Fail2ban.

sudo yum install fail2ban

Start Fail2Ban.

sudo service fail2ban start

Always start Fail2Ban when the system boots.

sudo chkconfig fail2ban on

Fail2Ban comes with configuration for basic services like ssh and MySQL, but not Nginx. [Configure fail2ban for Nginx](

## Purging Nginx microcache

The Nginx configuration we set stores full-page caches in the microcache for 10 minutes by default. If you edit a post in WordPress, that cache will not be busted automatically. In the same config, we set the cache to purge when a specific HTTP header is sent. Use [this plugin]( to hit the cache purge endpoint whenever a post is edited.

Awesome, very detailed article.

Man thank a lot! I just setup an EC2 server with WordPress. Cool! But I can't try it yet because the domain name doesn't propagate yet. Anyway thanks! I'll bookmark this page for future reference.

jjeaton commented Jul 3, 2015

This is great. Looking forward to the next post. I recently set up a site on AWS with multiple application servers balanced by ELB. I'd love to know how you solved some of the following if you can share: Sharing filesystems between servers, sharing uploads between servers, managing caching for multiple servers (separate caching servers or memcached on each app server?) and how you managed that within WordPress as well, cache invalidation when a post is saved, etc.

I followed every step meticulously, except the domain stuff, and my site won't load.

Domain issue:

  • domain registered already with other provider
  • I set the A record for the domain to the elastic IP for the EC2 instance
    Is THAT right?

the only other thing is that I got kicked from ssh a couple times, and had to come back to things. i'm pretty sure I got back to the correct directory each time, but...

can I suggest that you add some indication in the steps about what the working dir should be at each point?
and what the final directory structure should look like. I KNOW i got the SSL cert installed in the wrong place at one point, for example.

I didn't associate the elastic IP with the ec2 instance.
Everything is good now.


ericandrewlewis commented Jul 6, 2015

@adammichaelwood — sorry about the turbulence, glad that you finally got it set up correctly.

All the bash commands are in absolute path format, so working directory at any point shouldn't be an issue.


ericandrewlewis commented Jul 6, 2015


Sharing filesystems between servers, sharing uploads between servers

S3 as an out-of-the-box solution for a shared filesystem with the S3 and Cloudfront plugin. Your application runs application files (and runtime temporary files), any static file should be pushed to S3.

managing caching for multiple servers (separate caching servers or memcached on each app server?)

Memcached on a dedicated server/cluster that the app server shares.

Getting a 522 error on Cloudflare when I try transfer a domain using this setup. Everything appears to be set up correctly via SSH but stuck at "Configure WordPress via the web installer", it simply won't show up even though I've reconfigured CloudFlare DNS to point to my EIP. Any ideas?

An mtr shows ??? as the host and 100% loss of the first three hops so clearly something is amiss but not sure what?

EDIT: Updated my computers HOSTS file to ignore cloudflare IPs and connect directly to Amazon for testing but instead the connection just times out.


ericandrewlewis commented Jul 7, 2015

@danielmcclure — It sounds like something is misconfigured on the Elastic IP or the EC2 instance.

Is your domain configured with an A record to the Elastic IP? What happens if you run "nslookup {{}}"?

Is the Elastic IP associated with the instance?

Is the right security group applied to the instance?

Does the security group allow all inbound HTTP (port 80) and HTTPS (port 443) traffic?

Can you load the EC2 instance's public DNS in your browser? What happens?

Well now I feel stupid! Security group was set for HTTP/SSH but not HTTPS...
I tried so many things and managed to not think of that. Thanks for the gist, do you have a tipjar?


ericandrewlewis commented Jul 8, 2015

Pay it forward 💸

Can you provide some additional info about nginx config for multisite?

I have tried looking at some other guides that cover that, but I had already set this up this way, and different tutorials approaches make it difficult to figure what, if anything, needs to change for multisite to work, starting from this configuration.


ericandrewlewis commented Jul 9, 2015

@adammichaelwood Setting up multisite depends on how the site URLs in your multisite are configured.

Subfolder-based multisite

The WordPress Codex page on Nginx includes the following snippet:

# Rewrite multisite '.../wp-.*' and '.../*.php'.
if (!-e $request_filename) {
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
    rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;

The request is "rewritten" before Nginx decides which location block to process the request with. So you can have as your main site and as a site in your network. Site-specific requests to wp-admin/ (e.g. and core PHP files (e.g. invoke the correct script on the server, which will always exist in one location (e.g. /sites/ Without this rewriting, the request would 404 as /sites/ does not exist.

Sub-domain and arbitrary domain-based Multisite

If you want to use subdomains or multi-domain approaches to multisite, you'll want to add a separate Nginx server {} block for each domain. This is because Nginx only allows one set of ssl certificates per server {} block, pointing both domains to the same directory root.

Since you'll be duplicating a lot of configuration code, you may want to separate the boilerplate that is required for each domain (like Jeremy did for VVV) and include it into your server {} blocks as necessary.

jgraup commented Dec 8, 2015

Thanks for this BTW!

Have you tried this with PHP 7? WordCamp was all about it this weekend.

Also, just curious if you think it's too soon for Let's Encrypt with this setup? It's experimental but hard to beat free SSL certs.

great guide!

how to update php version? (with this guide i got 5.3.29)

also how to add also phpmyadmin ?

I can't access it
I always got the wordpress site

can you please explain?
thank you very much

Thanks for the detailed guide! I am very new to linux and the command line (in fact learning it for the first time while following your tutorial). I would like to know if you could add a step to install phpmyadmin as I want to use that tool instead of managing the database with the command line?

Thanks again!

An updated memcache object
the wordpress plugin didn't work for me.

knluu commented Aug 3, 2016


I have an ELB in front of an EC2 instance that's running multisite WordPress. When I put Cloudfront in front of it, I get this error message:


Any help would be appreciated.

Thanks for this BTW!
Have you tried this with PHP 7? WordCamp was all about it this weekend.
Also, just curious if you think it's too soon for Let's Encrypt with this setup? It's experimental but hard to beat free SSL certs.

Yes I have it working with php7.0-fpm and I'm using Let's Encrypts certificate although when you go to my site you will only see the CloudFlare cert since they provide it for free.

Thanks for the the article Eric. Great work.

Eric - this is great - appreciate you writing this up. 2 things:

  1. Clicking on the link for the punching memcache 404s at the bottom of the instructions (
  2. Would this configuration work with W3 Total Cache out of the box?

Thanks again, this is the best set of instructions I've seen...

I'm looking forward to the "tutorial on a high availability setup". When are you planning to release it?

lxm7 commented Dec 27, 2016

Did anyone else hit a wall with the 2 min WP installation via the browser? Done this countless times, but in this instance it can recognise user and password but cant select the named database?

Could be anything to do with permissions, user settings, or the latest php56-* modules?

Awesome collation of steps here in getting this set up. Thanks

FYI For anyone that tries to use certbot via Let's Encrypt, the certbox command will not work until you comment out

  ssl_certificate_key /etc/sslmate/{{SITE_DOMAIN}}.com.key;
  ssl_certificate /etc/sslmate/{{SITE_DOMAIN}}.com.chained.crt;

where {{SITE_DOMAIN}} is of course your domain name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment