How to Setup WordPress on Nginx, PHP-FPM and MySQL

WordPress website can be served using Apache or NGINX. Apache is the most popular Web server and most widely used for WordPress blogging platform. Apache is a great option and has served many of the world’s largest Web sites. Alternate web server for Apache is Nginx, pronounced “Engine X”. Nginx is an open source web server and a reverse proxy server for HTTP, SMTP, POP3 and IMAP protocols. Many websites and the web developer have moved to NGINX because it’s scalable, low resources, can handle many users concurrency and good website performance. For largest and busiest website, i would suggest you to host you websites and blog on Virtual Private Server (VPS) or dedicated server and run NGINX as a web server. Assumed that MySQL has been prepared and configured for WordPress and was tested on CentOS 6.5.

1. Prepared Nginx Repository :

[root@vps ~]# vi /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

2. Install NGINX, PHP, php-fpm amd MySQL server :

[root@vps ~]# yum install nginx php php-cli php-mysql php-gd php-xml php-fpm mysql mysql-server -y

3. Open /etc/php.ini and set cgi.fix_pathinfo=0:

[root@vps ~]# vi /etc/php.ini
..
..
; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
; http://www.php.net/manual/en/ini.core.php#ini.cgi.fix-pathinfo
cgi.fix_pathinfo=0
..
..

4. Set date.timezone in /etc/php.ini :

[Date]
; Defines the default timezone used by the date functions
; http://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone
date.timezone = "Asia/Kuala_Lumpur"

5. This server configuration was setup for PHP-FPM use a UNIX Socket.

[root@vps ~]# vim /etc/php-fpm.d/www.conf

Specify .sock path :

..
listen = /var/run/php-fpm.sock
..

Change user to run php-fpm :

..
user = nginx
..
..
group = nginx
..

6. Backup NGINX config file :

[root@vps ~]# cp -p /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

7. Create sites-available folder under /etc/nginx :

[root@vps ~]# mkdir /etc/nginx/sites-available

8. Adjust NGINX Worker Processes & Connections. NGINX workers equal the number of processors :

Check Number CPU on your VPS server :

[root@vps ~]# lscpu | grep '^CPU(s)'
CPU(s):                1

or

[root@vps ~]# cat /proc/cpuinfo | grep processor
processor       : 0
..
worker_processes  1;
..
..
worker_connections  1024;
..

Configure nginx.conf as below :

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;
    gzip_types text/css text/x-component application/x-javascript application/javascript  text/javascript text/x-js text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;

    
    include /etc/nginx/sites-available/*.conf;
}

9. Create common.conf and wordpress.conf under /etc/nginx/conf.d/ folder :

[root@vps ~]# vim /etc/nginx/conf.d/common.conf
[root@vps ~]# vim /etc/nginx/conf.d/wordpress.conf
[root@vps ~]# vim /etc/nginx/conf.d/common.conf

Add the following :

# Global configuration file.
# ESSENTIAL : Configure Nginx Listening Port
listen 80;
# ESSENTIAL : Default file to serve. If the first file isn't found,
index index.php index.html index.htm;
# ESSENTIAL : no favicon logs
location = /favicon.ico {
    log_not_found off;
    access_log off;
}
# ESSENTIAL : robots.txt
location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
}
# ESSENTIAL : Configure 404 Pages
error_page 404 /404.html;
# ESSENTIAL : Configure 50x Pages
error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
# SECURITY : Deny all attempts to access hidden files .abcde
location ~ /\. {
    deny all;
}
# PERFORMANCE : Set expires headers for static files and turn off logging.
location ~* ^.+\.(js|css|swf|xml|txt|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
    access_log off; log_not_found off; expires 30d;

Explanation :

listen 80; 

-Specifies the listening port of the server.

index index.php index.html index.htm;

-Specifies the default file to serve (WordPress index.php). For HTML sites, please include index.html & index.htm;.

location = /robots.txt {allow all;} 

-Allows the access to robots.txt

Create wordpress.conf :

[root@vps ~]# vim /etc/nginx/conf.d/wordpress.conf

Add the following :

# WORDPRESS : Rewrite rules, sends everything through index.php and keeps the appended query string intact
location / {
    try_files $uri $uri/ /index.php?q=$uri&$args;
}

# SECURITY : Deny all attempts to access PHP Files in the uploads directory
location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
}
# REQUIREMENTS : Enable PHP Support
location ~ \.php$ {
    # SECURITY : Zero day Exploit Protection
    try_files $uri =404;
    # ENABLE : Enable PHP, listen fpm sock
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# PLUGINS : Enable Rewrite Rules for Yoast SEO SiteMap
rewrite ^/sitemap_index\.xml$ /index.php?sitemap=1 last;
rewrite ^/([^/]+?)-sitemap([0-9]+)?\.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;

Explanation :

try_files $uri $uri/ /index.php?q=$uri&$args 

-rewrite rule required to allow you to choose your custom permalink structure on WordPress.

location ~* /(?:uploads|files)/.*\.php$ {deny all;} 

-this will prevent malicious code from being uploaded and executed from the WordPress media directory.

location ~ \.php$ {...}

-since WordPress is a php site, we need to tell NGINX how to a pass our php scripts to PHP5.

try_files $uri =404; 

-this is a security rule, you only want to either serve a determined php file or go to a 404 error.

10. Create a virtual server under /etc/nginx/sites-available/ directory :

[root@vps ~]# vi /etc/nginx/sites-available/ehowstuff.local.conf

Assumed that you want to configure a WordPress site with www.ehowstuff.local domain, please setup as below :

server {
    server_name ehowstuff.local;
    rewrite ^/(.*)$ http://www.ehowstuff.local/$1 permanent;
}

server {
        server_name www.ehowstuff.local;
        root /var/www/html/wordpress;
        access_log /var/log/nginx/www.ehowstuff.local.access.log;
        error_log /var/log/nginx/www.ehowstuff.local.error.log;
        include conf.d/common.conf;
        include conf.d/wordpress.conf;
}

Please change the following for virtual server ehowstuff.local.conf :

server_name: Define the server block for the URL.
root: Where you keep your website file
access log & error log: Paths for your logs

11. Start php-fpm and nginx :

[root@vps ~]# /etc/init.d/php-fpm start
Starting php-fpm:                                          [  OK  ]
[root@vps ~]# /etc/init.d/nginx start
Starting nginx:                                            [  OK  ]

12. Make php-fpm and nginx start at boot :

[root@vps ~]# chkconfig php-fpm on
[root@vps ~]# chkconfig nginx on

13. Verify that the required port already present.

[root@vps ~]# netstat -plunt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp        0      0 0.0.0.0:111                 0.0.0.0:*                   LISTEN      1042/rpcbind
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      3174/nginx
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      1096/sshd
tcp        0      0 0.0.0.0:3306                0.0.0.0:*                   LISTEN      3394/mysqld
tcp        0      0 :::111                      :::*                        LISTEN      1042/rpcbind
tcp        0      0 :::22                       :::*                        LISTEN      1096/sshd
udp        0      0 0.0.0.0:111                 0.0.0.0:*                               1042/rpcbind
udp        0      0 0.0.0.0:793                 0.0.0.0:*                               1042/rpcbind
udp        0      0 :::111                      :::*                                    1042/rpcbind
udp        0      0 :::793                      :::*