Tag Archives: NginX

Host Your Own WordPress Site

Introduction

A while back I decided to host my blog on my own servers and cut the cord with WordPress.com. There were no hard feelings really; I just didn’t like the limited options for plugin choices I had. I didn’t like all the extra features they forced me to use that made my blog (response time) slower.

The standalone version of WordPress is much more scaled down and fits my requirements better. The standalone version also allows me to pick and choose from a sea of amazing plugins at my disposal.

Prerequisite

You’ll need to have full (Linux) shell access to the server you’re intending to host this from. I set up my hosting using a CentOS 6.x machine; therefore the instructions I identify here are for that. That said, the instructions won’t stray too far off (requiring a tweak here an there) from those people who choose to use other Linux distributions.

You’ll want to first install a few packages:

# You'll basically need PHP, MySQL 
# As root (or a user with sudoer's permission) type the following:
sudo yum -y install php-fpm php-mbstring php-mcrypt \
             php-mysql nginx mysql mysql-server gawk \
             openssl

Optional FTP Support

FTP support is really nice to have with a WordPress setup! You can use it to automate the installation of plugins through the admin page. This is great for situations where you’re setting up a WordPress account for someone who isn’t too teksavvy. It also allows grants your users enough access to install plugins through WordPress’s administration interface.

I strongly suggest you read my blog entry on Configuring and Installing VSFTPD on CentOS 6 if you’re interested in going this route.

It’s important to note that WordPress functions just fine without an FTP(S) server too!

Step 1 of 8: Prepare our Environment.

To make life really easy (so you can cut and paste this right to your command line without any effort at all), lets create some environment variables.

Please note that this step MUST be ran before any of the other steps are. If you’re returning to this blog entry to resume from a step you left off at, be sure to apply these environment variables again!

Please note that you must be root or have sudoer’s permission to be able to perform any of these tasks successfully on your server.

# Our WordPress user
WPUSER=nuxref

# The FQD you will be serving your data from.  If you
# don't have your own domain, then set this to an
# underscore '_' (without the quotes '')
WPURL=nuxref.com

# Some Database Information
# - what are we going to call our database name?
DBNAME="wordpress_$WPUSER"
# - it's easier to just use the WordPress user account here
#   but if you want to change it to something else; here is
#   where you can do it:
DBUSER=$WPUSER
# - we will want to create a confusing password that others
#   can't guess. I don't recommend you use what i've identified
#   here because anyone else who knows you read my blog will
#   guess this first.  But here is where you should set your
#   database password you intend to use.
DBPASS="v3ryC0nFU51Ng-Pw%"

# If you plan on creating an FTP Account; you'll want to
# populate these variables too. This account does not have
# to be the same as the $WPUSER account. In fact making it
# different (even just slightly) would be a good idea!
# Below i just add '-ftp' to the end of the already
# determined user above.  Feel free to change this.
WPFTPUSER="$WPUSER-ftp"
# Set an FTP password; It would be a good idea to not use
# the one identified below as it's merely display only.
# some special character don't work with VSFTPD (like '!')
# if you plan on using it .
WPFTPPASS="4nt3rP455%rd"

# The following is only used for our SSL Key Generation
COUNTRY_CODE="7K"
PROV_STATE="Westerlands"
CITY="Lannisport"
SITE_NAME="Life as a Lannister"

Step 2 of 8: Create our User Account

You’ll want to create an isolated environment for our client (or you) to work within. By securing an environment; in the event anything is ever compromised, destruction will be limited to what we allow our client access to.

# First create a system directory to host our project.
sudo mkdir -p /opt/$WPUSER/html/static

# Create a dummy, favicon.ico file for now. If you feel
# ambitious, Google this if you're not sure what it's for
# so you can place your own custom one here
touch /opt/$WPUSER/html/static/favicon.ico

# Create System User
sudo useradd nuxref -M --system \
   --comment "$WPUSER WordPress Account" \
   --home /opt/$WPUSER \
   --shell /sbin/nologin

# Secure our new directory we created
chmod 711 /opt/$WPUSER
chown -R $WPUSER.$WPUSER /opt/$WPUSER

If you’ve followed my blog on Securing and Protecting Your CentOS 6 System then you might have wisely chosen to set up disk quotas. If not; then you can skip over to the next step.

# Detect the device using our home directory
DEV=$(df -l -P /opt/$WPUSER | awk 'END{print $1}')
# Restrict Users Disk Quota to 600MB
sudo setquota -u $WPUSER 180000 600000 0 0 $DEV

Step 3 of 8: Generate SSL Keys

We need to generate some Secure Socket Layer (SSL) keys so that we can provide a secure connection for logins. Otherwise our passwords we choose to work with the site could be exposed.

To make things simple, you can use my genssl tool first discussed in an earlier blog I wrote here. available for download from my github page and then just do the following:

# Generate a self signed key:
genssl -s $WPURL
# Install it:
sudo install -m 0400 $WPURL.key /etc/pki/tls/private/$WPUSER.key
sudo install -m 0444 $WPURL.crt /etc/pki/tls/certs/$WPUSER.crt

Or you can simply do the following:

# The following will generate SSL Keys (if you don't have any already)
sudo openssl req -nodes -new -x509 -days 730 -sha256 -newkey rsa:2048 
   -keyout /etc/pki/tls/private/$WPUSER.key 
   -out /etc/pki/tls/certs/$WPUSER.crt 
   -subj "/C=$COUNTRY_CODE/ST=$PROV_STATE/L=$CITY/O=$SITE_NAME/OU=IT/CN=$WPURL"
 
# Permissions; protect our Private Key
chmod 400 /etc/pki/tls/private/$WPUSER.key
 
# Permissions; protect our Public Key
chmod 444 /etc/pki/tls/certs/$WPUSER.crt

Step 4 of 8: Install our WordPress Bundle

Now we need to Download and install WordPress into our environment.

# WordPress Configuration
# Acquire latest version from here https://wordpress.org/download/
# (At the time it was 4.4.2)
wget --no-check-certificate https://wordpress.org/latest.tar.gz -O wordpress.$(date +'%Y.%m.%d').tgz

# Extract our downloaded copy
sudo tar xvfz wordpress.$(date +'%Y.%m.%d').tgz \
    -C /opt/$WPUSER/html --strip 1

# Apply some more permissions
sudo find /opt/$WPUSER/html -type d -exec chmod 755 {} \;
sudo find /opt/$WPUSER/html -type f -exec chmod 664 {} \;
sudo find /opt/$WPUSER/html -exec chown $WPUSER.apache {} \;

# Grant write permissions to a few tools our plugin installers
# will need access to later on:
find /opt/$WPUSER/html/wp-content/ -type d -exec chmod 775 {} \;
sudo chmod 660 /opt/$WPUSER/html/wp-config.php

Step 5 of 8: Configure and Prepare our Database

Now we need to configure our MySQL (or MariaDB) database. First make sure it is running:

# The below command will start the database if it isn't
# already running:
sudo service mysqld status &>/dev/null || \
   sudo service mysqld start

# Next make sure you're system is configured to start
# the database each and every time your server turns on
sudo chkconfig --level 345 mysqld on

Now we need to prepare our database that WordPress can use.

# SQL Initialization
( cat << _EOF
CREATE DATABASE $DBNAME;
GRANT ALL PRIVILEGES ON $DBNAME.* TO "$DBUSER"@"localhost" IDENTIFIED BY " $DBPASS";
FLUSH PRIVILEGES;
_EOF
) | sudo mysql

Step 6 of 8: Configure our Web Hosting Service

Okay now we need to host our website. Effectively linking the database we just prepared with the WordPress software we just installed. We do this as follows using NginX:

# By default (assuming a CentOS installation), we can
# plug into our configuration by writing our data in
# /etc/nginx/conf.d/
# So lets do just that:
cat << _EOF > /etc/nginx/conf.d/wordpress_$WPUSER.conf
#
# $WPUSER WordPress Web Hosting
#
server {
    # Support Web Traffic at port 80
    listen       80;
    server_name  $WPURL;
    root   /opt/$WPUSER/html;

    # Our log files
    access_log  /var/log/$WPUSER/$WPUSER.access.log  main;
    error_log  /var/log/$WPUSER/$WPUSER.error.log;

    # Our main handler
    location / {
        root   /opt/$WPUSER/html;
        index  index.html index.htm index.php;
        # Support Permalink changes
        try_files \$uri \$uri/ /index.php?q=\$request_uri;
    }

    # Anyone logging into our site should do it securely
    location /wp-admin/ {
       # Always redirect to secure site
       rewrite ^/(.*) https://$host/\$1 permanent;
    }
    location /wp-login/ {
       # Always redirect to secure site
       rewrite ^/(.*) https://\$host/\$1 permanent;
    }

    error_page  404              /404.html;
    location = /404.html {
        root   /usr/share/nginx/html;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # Support the favicon (for those wanting to use it)
    location = /favicon.ico {
        root   /opt/$WPUSER/html/static;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php\$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
        include        fastcgi_params;
    }

    # Deny access to the wp-config file
    location ~ /wp-config\.php {
        deny  all;
    }
}

server {
    # We should listen on a secure URL too so that we can
    # hide our admin login credentials from prying eyes
    listen       443;
    server_name  $WPURL;
    root   /opt/$WPUSER/html;

   ssl on;
   ssl_certificate /etc/pki/tls/certs/$WPUSER.crt;
   ssl_certificate_key /etc/pki/tls/private/$WPUSER.key;
   ssl_session_timeout  5m;

   # Secure our site by only allowing the TLS protocol
   ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
   ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK';
   ssl_prefer_server_ciphers on;
   ssl_session_cache  builtin:1000  shared:SSL:10m;

   access_log  /var/log/nginx/$WPUSER.access.log  main;
   error_log  /var/log/nginx/$WPUSER.error.log;

   location / {
      root   /opt/$WPUSER/html;
      index  index.html index.htm index.php;
      # Support Permalink changes
      try_files \$uri \$uri/ /index.php?q=\$request_uri;
   }

   error_page  404              /404.html;
   location = /404.html {
      root   /usr/share/nginx/html;
   }

   # redirect server error pages to the static page /50x.html
   #
   error_page   500 502 503 504  /50x.html;
   location = /50x.html {
       root   /usr/share/nginx/html;
   }

   # Handle favicon
   location = /favicon.ico {
       root   /opt/$WPUSER/html/static;
   }

   # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
   #
   location ~ \.php\$ {
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
      include        fastcgi_params;
   }

   # Deny access to the wp-config file
   location ~ /wp-config\.php {
      deny  all;
   }
}
_EOF

Now restart our web services

# Ensure our web browser and php handler will start
# even if our server is restarted
chkconfig --levels 345 php-fpm on
chkconfig --levels 345 nginx on

# The following just makes sure we reload and take
# on our new configuration.  If we're not running
# then we start the services up
service php-fpm status &>/dev/null && \
   service php-fpm restart || \
   service php-fpm start

service nginx status &>/dev/null && \
   service nginx restart || \
   service nginx start

Step 7 of 8: Optionally Setup an FTP Account

Most people can skip this step; it again presumes you’ve followed my other blog on Configuring and Installing VSFTPD on CentOS 6. If you have not gone here or have set up FTP your own way, you can also skip this step and move on with Configuring WordPress.

# Create a WordPress Plugins FTP Account
echo $WPFTPUSER >> /etc/vsftpd/users.passwd
echo $WPFTPPASS >> /etc/vsftpd/users.passwd
 
# Protect Password
chmod 600 /etc/vsftpd/users.passwd
chown root.root /etc/vsftpd/users.passwd
 
# Now convert content into a db structure
db_load -T -t hash -f /etc/vsftpd/users.passwd /etc/vsftpd/virtual.users.db
chmod 600 /etc/vsftpd/virtual.users.db
chown root.root /etc/vsftpd/virtual.users.db
 
cat << _EOF > /etc/vsftpd/virtual.users/$WPFTPUSER
local_root=/opt/$WPUSER/html
# -------------------------------------------------------------------------
# User
# -------------------------------------------------------------------------
guest_enable=YES
guest_username=apache
local_root=/opt/$WPUSER/html
# -------------------------------------------------------------------------
# Permissions
# -------------------------------------------------------------------------
# write_enabled is required if the user is to make use of any of the
# anon_* commands below
write_enable=YES
# give the user the ability to make directories
anon_mkdir_write_enable=YES
# give the user the ability delete and overwrite files
anon_other_write_enable=YES
# give the user the ability upload new files
anon_upload_enable=YES
# Give the user permission to do a simple directory listings
dirlist_enable=YES
# Give the user permission to download files
download_enable=YES
# if the user has can upload or make new directories, then this will be
# the umask applied to them
anon_umask=0002
# delete failed uploads (speaks for itself)
delete_failed_uploads=NO
_EOF
 
sudo chmod 600 /etc/vsftpd/virtual.users/$WPFTPUSER
sudo chown root.root /etc/vsftpd/virtual.users/$WPFTPUSER

# Ensure our FTP Server will restart if our server
# is ever restarted:
sudo chkconfig --level 345 vsftpd on
# Update Service (to read in new configuration)
sudo service vsftpd status &>/dev/null && \
    service vsftpd restart || \
    service vsftpd start

Step 8 of 8: Configure WordPress

If you successfully pulled off all of the earlier steps, then you shouldn’t have any trouble from this point forward. The hard part is done with!

Wordpress Database SetupWordpress InstallationYou now need to open up your browser and access your new WordPress website to continue with the setup. Simply visit your website by browsing to http://your.wordpress.url/ (whatever you set this up as).

First you’ll be immediately presented with webpage that needs some information about the database we set up back in Step 5 (an environment variables defined in Step 1.

After you press the [Submit] button, you’ll then be asked to define some basic information about the blog you intend to set up. You can change all this later, so don’t worry. The important fields here are the administrator user and password you create.

You’re done now and ready to use WordPress

Great WordPress Plugins

The following plugins are worthy of a mention:

Credit

Please note that this information took me several days to put together and test thoroughly. I may not blog often; but I want to re-assure the stability and testing I put into everything I intend share.

If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.