Select Page

How to Install WordPress on AWS EC2

Build a LEMP stack

The stack

  • L – Linux OS: Amazon Linux 2023
  • E – Nginx: the webserver
  • M – MySQL Database: we’ll use MariaDB, a fork of MySQL
  • P – serving PHP

You might have heard of a LAMP stack. Well, this is not that, but it’s similar. Instead of running Apache for our web server, we’ll run NGINX. It requires a little more configuration to work with WordPress but it’s faster and can handle heavier workloads – advantages that won’t make the slightest difference to your brand-new website visited only by your mother (under duress), yet here we are…

Why build your own server

Because it’s fun, mostly, and so you can tell your own story!

What’s cool is that you get to build something from the ground up, and suffer all that it entails – many, many hours of Googling (Are we there yet? Not even close, son). Of course when I say from the ground up, I mean within reason – we’re not “taking lightening and sticking it in a rock until it learns to think”, but we do have control over how we configure our web server.

That control is what makes this project interesting for a certain type of lonely introvert. We gain visibility over the components of our web server and can configure them as we please.

With great power…

It’s worth keeping in mind any responsibility to minimise vulnerabilities in your configuration. This might involve setting file permissions appropriately, applying updates in a timely fashion or securing your database (we’ll cover this in a bit). Getting yourself familiar with AWS’s Shared Responsibility model is a good place to start.

What we’re configuring here is probably the most basic way to get up and running. There’s definitely more comprehensive ways to configure a web server… But everyone has to start somewhere:

Getting started

We’ll need a few things running before we start:

  1. [Optional] An IPv6-enabled VPC
  2. an EC2 instance
  3. a Security Group to allow web traffic
  4. a Domain Name
  5. a Hosted Zone associated with your Domain Name in Route 53

1. [Optional] IPv6 VPC

This comes first because if you want to enable IPv6 on your instance, you’ll need an IPv6 enabled VPC. The default VPC might meet your needs for the moment but it’d be a good idea to enable IPv6. The easiest way to do that would be to create a new VPC with a public subnet. If you do create your own VPC, remember to add IPv6 entries to your Route Table and Security Group.

2. EC2

AWS offers a lot of choice when selecting instance type. Since we’re starting with a project from scratch we don’t need anything fancy and can probably narrow it down to two instance types:

  • t2.micro
  • t4g.nano

If you still have access to AWS’s free tier, t2.micro might make sense, otherwise go for one of the t4g ARM instances. There’s no real reason to go for a t2 instance if you no longer have access to AWS’s free tier. You won’t be able to create a t4 instance from a t2 snapshot as the underlying architecture of the instances are different.

Along with choosing your instance type, you’ll need to specify your root volume’s size. Unless you know that you are going to build a sizeable website, the default of 8GB will probably meet your needs just fine.

Create a new key pair to connect to your instance over SSH, otherwise connecting by EC2 Instance Connect from the console is simple and secure.

3. A security group

Security Groups control the traffic that’s able to leave and reach the resource that it’s attached to, in our case, EC2. It’s a firewall. Our instance needs to communicate over the internet so we need to configure a Security Group that permits this type of traffic.

From the EC2 dashboard, create a new Security Group to allow inbound traffic from any source over HTTP and HTTPS. You can also allow inbound SSH traffic if you want to connect to your instance by SSH and can improve security by restricting the source to your current IP address. If you suddenly find yourself unable to connect by SSH, check that your public IP address hasn’t magically changed and update the source address in your Security Group if needed.

4. Domain name

Go get a domain… It’d be easier to get one from Route 53.

5. Hosted zone

Hopefully you already have a domain name. You’ll need one to install an SSL/TLS certificate. I’ll assume that your domain name is held in Route 53. However, if it’s not, transferring it over is a fairly simple process. Otherwise, you’ll want to refer to certbot’s documentation on various DNS Plugins. Check to see if there’s a Hosted Zone for your domain. If there isn’t, go ahead and create one.

Aight, let’s do this!!

SSH to EC2

Before using a key pair to connect to your instance over SSH, set its file permissions to restrict access:

# for Linux systems:
chmod 400 [PATH TO KEY]

# for Windows Systems (Source: https://stackoverflow.com/a/43317244)
$path = ".\[PATH TO KEY]"
icacls.exe $path /reset # Reset to remove explict permissions
icacls.exe $path /GRANT:R "$($env:USERNAME):(R)" # Give current user explicit read-permission
icacls.exe $path /inheritance:r # Disable inheritance and remove inherited permissionsr

Connect to your instance:

ssh -i "[PATH TO KEY]" ec2-user@[IP ADDRESS OF YOUR INSTANCE]

Install NGINX

Install NGINX:

sudo dnf install nginx -y

I know right, how easy was that?! Installing NGINX will only take a few moments. Once installed, we need to check whether it’s up and running by querying its status:

sudo systemctl status nginx

If the status check comes back as ‘inactive’, we can start the service with the following command:

sudo systemctl start nginx

Enabling NGINX ensures that it automatically runs at startup; useful if you need to reboot your instance:

sudo systemctl enable nginx

A final status check should show NGINX is active and enabled:

sudo systemctl status nginx

Install packages

You can now install most of the additional packages you’ll need. These include a database and various PHP packages needed to get things working on WordPress:

sudo dnf install php-mysqlnd php-fpm php-mysqli mariadb105-server php-json php-devel php-opcache php-gd php-intl php-zip php-common php-mbstring

Configure MariaDB

WordPress needs a database to store all the information that comprises the site itself, from the site’s theme, pages, posts and even login credentials. The steps to start and enable your database application, MariaDB, should look familiar:

sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo systemctl status mariadb

With the database application now active, run the secure installation command to be guided through a series of steps to secure MariaDB:

sudo mysql_secure_installation

You’ll be prompted to enter the password for MariaDB’s root account. By default the root account doesn’t have a password set so press Enter to sign in:

Enter current password (enter for none): [Press Enter]
Select Yes for the following prompts. You can type the letter ‘y’ and hit enter, or, look carefully to see which letter has been capitalised in the prompt. Pressing enter will select the capitalised option. For example: [Y/n] defaults to Y when you hit enter.

Ensure you change the root password and take care to store it somewhere safe:


Switch to unix_socket authentication [Y/n] n
Change the root password? [Y/n] y
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

With the database server secured, you can create the actual database that WordPress will use. You’ll also create a user within MariaDB (the database server) with credentials to access WordPress’s database. Lastly, WordPress will be supplied those user’s credentials to be able to communicate with the MySQL database.

Log in to the database server as root, entering the password you set for the root user in the previous step:

mysql -u root -p
Create a user with a strong password that will access the database (you’ll create and assign permissions to the database after this step). Substitute the following command with a unique username and password.

Take care not to include the single quote character [']for your username or password; you’ll break the command.

Don’t reuse passwords. Store them in a safe place:

CREATE USER 'wordpress-user'@'localhost' IDENTIFIED BY 'your_strong_password';
When creating a database, note the use of backticks [`]rather than quotation marks. They allow us to include hyphens in its name. Give the database a sensible name, such as:
CREATE DATABASE `wordpress-db`;

Grant all privileges to the database for the particular user created earlier:

GRANT ALL PRIVILEGES ON `wordpress-db`.* TO "wordpress-user"@"localhost";

Reload priviliges from the privilage table to pick up any changes we’ve made:

FLUSH PRIVILEGES;

Exit MySQL:

exit

Configure WordPress

Enable PHP-FPM:

sudo systemctl enable php-fpm

Download and extract the latest WordPress:

wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz

To configure WordPress, start by making a copy of the sample config already in the wordpress directory:

cp wordpress/wp-config-sample.php wordpress/wp-config.php

Use your favourite text editor… or nano (shots fired)… to edit the config file. Jokes aside, editors like vim (already installed on most distros) or nvim are really worth getting to know:

sudo nano wordpress/wp-config.php

Scroll past the file’s comments to find the following line:

define( 'DB_NAME', 'database_name_here' );
Replace 'database_name_here‘ with the name of the database you created earlier. If you called the database 'wordpress-db', you should amend the line to look like this:
define( 'DB_NAME', 'wordpress-db' );

Similarly, amend the following lines to match the database user and password you created earlier:

define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );

Scroll down further to find the section named ‘Authentication unique keys and salts’. As instructed, navigate to https://api.wordpress.org/secret-key/1.1/salt/ to generate a set of keys and salts of your own:

define( 'AUTH_KEY',         'put your unique phrase here' );
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
define( 'NONCE_KEY',        'put your unique phrase here' );
define( 'AUTH_SALT',        'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
define( 'NONCE_SALT',       'put your unique phrase here' );

That completes updating the config file. Exit the text editor. If it’s your first time using vim… lol…

Next, copy the contents of the wordpress directory to NGINX’s html directory. NGINX looks inside the html directory for content to serve. This will be where WordPress installs when we run it for the first time:

sudo cp -r wordpress/* /usr/share/nginx/html

Configure NGINX

Configure NGINX:

sudo nano /etc/nginx/nginx.conf
Within the nginx.conf file, find the server block. The first few lines will look something like this:
server {
    listen       80;
    server_name  localhost;

    location / {
        root   html;
        index  index.html index.htm;
      }

Those first few lines are where we need to make some changes:

server {
    server_name  my_site.com; # add your website's name
	root /usr/share/nginx/html; # tell nginx where your website files are kept

	client_body_buffer_size 32k; # improves WordPress compatibility
	client_max_body_size 64M; # improves WordPress compatibility

    location / {
        index  try_files $uri $uri/ /index.php?$args; # fixes permalinks
      }

Configure PHP

A note on file permissions:

By default, root owns the /usr/share/nginx directory. You can verify ownership by running ls -l /usr/share/ | grep nginx. However, we don’t really want any part of our web server to assume root privileges just to be able to modify those files.

One way to get around this is to change who owns that directory, and tell our server who the new owner is.

We’ll first specify a new user and group in the PHP-FPM config file. By default, the user and group are set to apache.

Later, we’ll change the ownership of the /usr/share/nginx directory to match our PHP-FPM config file.

Open the configuration file for PHP-FPM:

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

Scroll to update the user and group to ec2-user (the default user created when provisioning EC2 instances) and nginx:

; 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 user chosen to provide access to the same directories as httpd
user = ec2-user
; RPM: Keep a group allowed to write in log dir.
group = nginx

Depending on the weight of your theme, you’ll likely want to change a few PHP settings. Open the PHP config file:

sudo nano /etc/php.ini

Your theme may specify minimum recommendations for the following settings. If not, these are a reasonable place to start. Find and update:

upload_max_filesize = 64M
max_file_uploads = 50
max_execution_time = 120
post_max_size = 64M

All those configuration changes now must be picked up. Restart NGINX and PHP-FPM to do this:

sudo systemctl restart nginx.service php-fpm.service

Permissions

Earlier, you updated the user and group that our server will assume to make changes to the /usr/share/nginx directory.

The next step in that process is to create the nginx group and append ec2-user to it:

sudo usermod -a -G nginx ec2-user

For ec2-user to assume its new group membership, log out and log back in:

exit
Verify group membership. The output should look similar to nginx:x:999:ec2-user:
grep nginx /etc/group
Change the owner of the /usr/share/nginx directory, and all files within the directory:
sudo chown -R ec2-user:nginx /usr/share/nginx

Modify directory permissions:

sudo chmod 2775 /usr/share/nginx && find /usr/share/nginx -type d -exec sudo chmod 2775 {} \;

Modify file permissions:

find /usr/share/nginx -type f -exec sudo chmod 0664 {} \;

Restart NGINX to pick up those permissions:

sudo systemctl restart nginx

Test PHP

One way to now test whether PHP is being served is to echo the following PHP script into the html directory and navigate to it in the browser. You should see your PHP config.

I don’t really like doing this because everyone else can see your PHP config too. It’s cool to see your server coming together, but there are more secure ways to test what we want to test.

Given the scope of the project, it’s reasonable enough to copy it over, take a look online, then quickly delete the phpinfo.php file.

echo "<?php phpinfo(); ?>" > /usr/share/nginx/html/phpinfo.php
To view the file in your browser, navigate to: yourwebsite.com/phpinfo.php

If it works, you’ll see your PHP configuration. Now delete the file:

rm /usr/share/nginx/html/phpinfo.php

A better way to test PHP would be to echo a simple PHP script that doesn’t expose your configuration and navigate to that instead. Remember to delete the file afterwards:

echo "<?php echo 'Something other than hello-bleeding-world'; ?>" > /usr/share/nginx/html/test.php

And you can view PHP’s configuration in the terminal instead:

php -i

Install a SSL/TLS certificate

Excellent, you’re now ready to install a TLS certificate. SSL was deprecated years ago and replaced by TLS, but the name lingers.

Installing certbot from Let’s Encrypt is the way forward. The following commands are lifted from certbot, with slight modification. If you run into issues, I’d refer back to certbot and follow their instructions instead:

sudo dnf install augeas-libs

Create a python virtual environment so that we can isolate the installation of various python packages from the rest of the system:

sudo python3 -m venv /opt/certbot/
Upgrade pip, a python package manager, to the latest version:
sudo /opt/certbot/bin/pip install --upgrade pip
Use pip to install a few packages. One of those packages is a DNS Plugin for Route 53, assuming that your domain is held by AWS. If it’s not, you may want to refer to certbot’s User Guide:
sudo /opt/certbot/bin/pip install certbot certbot-nginx certbot-dns-route53

This next step is a Linux thing (rather than anything to do with the certificate itself). Create a symbolic link between the certbot app in our python virtual environment and where the system would naturally go looking for the certbot app (in other words, where all the other binaries are kept):

sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot

We’re close, but before you can install the certificate, you need to provide certbot with permissions to change record sets in Route 53. We can do this using an IAM Role.

First, head to Route 53. Check that you have a Hosted Zone associated with your domain. If not, select ‘Create hosted zone’ and follow the instructions to create a public hosted zone.

With your domain in the list of Hosted Zones, highlight it and note the Hosted Zone ID.

Now over to IAM to create a Role that EC2 can assume, allowing certbot to do what it needs in Route 53.

A Role consists of two parts, a Permissions Policy and a Trust Relationship. The Permissions policy is a JSON document stating which actions are permitted and where. The Trust Relationship is another JSON document stating who or what can perform those actions in the Permissions policy. The two together comprise a Role.

Within IAM, navigate to Policies and select Create Policy. Select JSON in Policy editor and paste the following JSON, updating your Hosted Zone ID:

{
    "Version": "2012-10-17",
    "Id": "certbot-dns-route53 sample policy",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:GetChange"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect" : "Allow",
            "Action" : [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource" : [
                "arn:aws:route53:::hostedzone/YOURHOSTEDZONEID"
            ]
        }
    ]
}

Click on Next and provide a meaningful Policy name, like CertbotDnsRoute53, and add a description. Create the policy.

Navigate to Roles. Create a role. Select AWS service as the Trusted Entity Type, and the Use Case is EC2. Select Next and search for the Policy you just created – CertbotDnsRoute53. Select it and click Next. Create a name, you can simply use the policy name as the role’s name too.

Whilst on this review page, you should see that a Trust Policy specifying ec2.amazonaws.com as permitted to Assume the Role.

Go ahead and create the policy.

Last step… attach the policy to our web server, to the instance of EC2. Head over to EC2 in the Management Console and find your instance. Highlight it, and click on Actions > Security > Modify IAM role. Select the IAM role you created in the dropdown menu and click Update IAM role.

Right, back to the terminal to install your cert:

sudo certbot --dns-route53 -i nginx

Your TLS certificate will expire, and will need to be renewed. We can set up automatical renewal:

echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q" | sudo tee -a /etc/crontab > /dev/null

Keep certbot updated by running the following roughly once a month:

sudo /opt/certbot/bin/pip install --upgrade certbot certbot-nginx certbot-dns-route53

WordPress image processing

These last commands are specific to WordPress being able to manipulate images.

Install php-pear, a repo of PHP apps:

sudo dnf install php-pear

Install the following apps:

sudo dnf install ImageMagick ImageMagick-devel ImageMagick-perl
Now install imagick:
sudo pecl install imagick
imagick tends to need a little configuration before it will run. Ensure that imagick has a .ini file, before opening it:
sudo nano /etc/php.d/20-imagick.ini
Paste the following snippet into 20-imagick.ini:
extension=imagick.so

Restart NGINX and PHP:

sudo systemctl restart nginx
sudo systemctl restart php-fpm

Exciting! You’re pretty much done! I mean you still have to go make a frontend and some content but woohoo! Head to your website in the browser and you should be prompted to complete the installation of WordPress.

You’re now free to create and edit your site, tell your story or do whatever it is you want to do. Go install a theme and do your thing!

Additional resources

I mentioned earlier there are better ways to configure a WordPress site. AWS have a pretty cool reference to what an elastic deployment could look like.

You may also like…

No Results Found

The page you requested could not be found. Try refining your search, or use the navigation above to locate the post.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *