Setting up WordPress is a pretty common task. However all too often I see people installing WordPress, and setting the ownership to ‘apache:apache’ recursively. While this makes life easier for the administrator, it opens up a host of security issues.
Taken directly from WordPress’s best practice guide on permissions:
Typically, all files should be owned by your user (ftp) account on your web server, and should be writable by that account. On shared hosts, files should never be owned by the web server process itself (sometimes this is www, or apache, or nobody user).
Most people know that using FTP is bad. However if you plan on using the wp-admin portal for media uploads, plugin updates, and core updates, you MUST have an FTP server installed and running. Using the Pecl SSH2 library looks like it would work in theory, but in reality, it doesn’t. Or at least, I haven’t found a way to make it work for the wp-admin portal without giving permission errors for this, that and everything in between since it needs weaker permissions. So while your users can use SSH/SCP to upload content via the command line, if they choose to do most of the WordPress tasks through wp-admin like most people would, use the FTP option from within /wp-admin.
This guide is going to show how you can setup WordPress properly accordingly to the note above from WordPress’s best practices guide on permissions. This guide will assume that you already have a working LAMP stack installed.
FTP Server Setup
First, install an FTP server called vsftpd:
[root@web01 ~]# yum install vsftpd
[root@web01 ~]# chkconfig vsftpd on
Now disable anonymous logins since vsftpd enables this by default for some reason:
[root@web01 ~]# vim /etc/vsftpd/vsftpd.conf
...
anonymous_enable=NO
...
[root@web01 ~]# service vsftpd restart
Then confirm you have a firewall in place that has a default to deny policy. So in the example below, I am only allowing in ports 80 and 443 from the world. Then I have SSH restricted to my IP address. Everything else is blocked, including that FTP server.
[root@web01 ~]# vim /etc/sysconfig/iptables
# Generated by iptables-save v1.4.7 on Fri Nov 13 19:24:15 2015
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [2:328]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -i eth0 -s xx.xx.xx.xx/32 -p tcp -m tcp --dport 22 -m comment --comment "Allow inbound SSH from remote ip" -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
# Completed on Fri Nov 13 19:24:15 2015
Database Setup
Create a database for your new WordPress site by:
[root@web01 ~]# mysql
mysql> create database your_database;
Now grant access for that database to a user:
[root@web01 ~]# mysql
mysql> grant all on your_database.* to 'your_db_user'@'localhost' identified by 'your_secure_db_password';
mysql> flush privileges;
mysql> quit
Apache Setup
First, create a FTP/SCP user:
[root@web01 ~]# mkdir -p /var/www/vhosts/example.com
[root@web01 ~]# chmod 755 /var/www/vhosts/example.com
[root@web01 ~]# useradd -d /var/www/vhosts/example.com example_site_user
[root@web01 ~]# passwd example_site_user
Now setup the Apache vhost:
[root@web01 ~]# vim /etc/httpd/vhost.d/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
#### This is where you put your files for that domain
DocumentRoot /var/www/vhosts/example.com
### Enable this if you are using a SSL terminated Load Balancer
SetEnvIf X-Forwarded-Proto https HTTPS=on
#RewriteEngine On
#RewriteCond %{HTTP_HOST} ^example.com
#RewriteRule ^(.*)$ http://www.example.com [R=301,L]
<Directory /var/www/vhosts/example.com>
Options -Indexes +FollowSymLinks -MultiViews
AllowOverride All
Order deny,allow
Allow from all
</Directory>
CustomLog /var/log/httpd/example.com-access.log combined
ErrorLog /var/log/httpd/example.com-error.log
# New Relic PHP override
<IfModule php5_module>
php_value newrelic.appname example.com
</IfModule>
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
</VirtualHost>
##
# To install the SSL certificate, please place the certificates in the following files:
# >> SSLCertificateFile /etc/pki/tls/certs/example.com.crt
# >> SSLCertificateKeyFile /etc/pki/tls/private/example.com.key
# >> SSLCACertificateFile /etc/pki/tls/certs/example.com.ca.crt
#
# After these files have been created, and ONLY AFTER, then run this and restart Apache:
#
# To remove these comments and use the virtual host, use the following:
# VI - :39,$ s/^#//g
# RedHat Bash - sed -i '39,$ s/^#//g' /etc/httpd/vhost.d/example.com.conf && service httpd reload
# Debian Bash - sed -i '39,$ s/^#//g' /etc/apache2/sites-available/example.com && service apache2 reload
##
# <VirtualHost _default_:443>
# ServerName example.com
# ServerAlias www.example.com
# DocumentRoot /var/www/vhosts/example.com
# <Directory /var/www/vhosts/example.com>
# Options -Indexes +FollowSymLinks -MultiViews
# AllowOverride All
# </Directory>
#
# CustomLog /var/log/httpd/example.com-ssl-access.log combined
# ErrorLog /var/log/httpd/example.com-ssl-error.log
#
# # Possible values include: debug, info, notice, warn, error, crit,
# # alert, emerg.
# LogLevel warn
#
# SSLEngine on
# SSLCertificateFile /etc/pki/tls/certs/2016-example.com.crt
# SSLCertificateKeyFile /etc/pki/tls/private/2016-example.com.key
# SSLCACertificateFile /etc/pki/tls/certs/2016-example.com.ca.crt
#
# <IfModule php5_module>
# php_value newrelic.appname example.com
# </IfModule>
# <FilesMatch \"\.(cgi|shtml|phtml|php)$\">
# SSLOptions +StdEnvVars
# </FilesMatch>
#
# BrowserMatch \"MSIE [2-6]\" \
# nokeepalive ssl-unclean-shutdown \
# downgrade-1.0 force-response-1.0
# BrowserMatch \"MSIE [17-9]\" ssl-unclean-shutdown
#</VirtualHost>
Then restart Apache to apply the changes:
[root@web01 ~]# service httpd restart
WordPress Setup
Download a copy of WordPress, uncompress, and move the files into place by:
[root@web01 ~]# cd /var/www/vhosts/example.com
[root@web01 ~]# wget http://wordpress.org/latest.tar.gz && tar -xzf latest.tar.gz
[root@web01 ~]# mv wordpress/* ./ && rmdir ./wordpress && rm -f latest.tar.gz
Update the files and directories ownership to lock it down accordingly:
[root@web01 ~]# chown -R example_site_user:example_site_user /var/www/vhosts/example.com
Then open up a few files so wp-admin can manage the .htaccess, and so it can install plugins, upload media, and use the cache if you choose to configure it:
[root@web01 ~]# mkdir /var/www/vhosts/example.com/wp-content/uploads
[root@web01 ~]# mkdir /var/www/vhosts/example.com/wp-content/cache
[root@web01 ~]# touch /var/www/vhosts/example.com/.htaccess
[root@web01 ~]# chown apache:apache /var/www/vhosts/example.com/wp-content/uploads
[root@web01 ~]# chown apache:apache /var/www/vhosts/example.com/wp-content/cache
[root@web01 ~]# chown apache:apache /var/www/vhosts/example.com/.htaccess
And thats it! Once you have the domain setup in DNS, you should be able to navigate to the domain, and follow the WordPress installation wizard to complete the setup. Afterwards, log into wp-admin, and try to update a plugin, or install a new one. When it prompts you for the FTP information, be sure to use:
Hostname: localhost
FTP Username: example_site_user
FTP Username: example_site_user_pw
Connection Type: FTP