Technology Blog

Nginx and PHP-FPM for heavy load wordpress web server

0

To make it to read short I will not be describing all the time I lost during last half year trying to find out the “right settings” for the high traffic WordPress web server running I was responsible for, and this was on the top of my primary job, and that’s why it took so much time of blind testing and playing parameters with live production server, and as you understand I have no ability to make sharp actions and have long downtime, while having only 30-40 minutes per day. Anyway I found all needed information, tested everything, it works, I’m happy.

So the server is quite ordinary 2 CPU (8 cores total), 16 GB RAM, SSD drive, colocated in proper data center. Ubuntu OS, PHP 5.3.5 (with PHP5-FPM), MySQL 5.5, WordPress 3.3.1, APC cache.

Just to give you some ideas what I was trying while I was loosing my time. As far as you understand I couldn’t reinstall OS, so I was playing only with the packages installed on it. So as for database I use MySQL 5.1 after 5.1a and at the end 5.5. Honestly I didn’t find any big advantages that made my life easier or brought some performance benefits only simply because of a new version.

As for PHP, the first version I’ve started with already running on this server was PHP 5.1.6, after it was 5.2.3, then 5.2.5, then 5.2.17, then 5.3.3, and at last 5.3.5. Why I’m enumerating all these versions ? Early versions of PHP do not include the PHP5-FPM package, and means it was not comfortable to apply special patch contained this package. Version PHP 5.3.8 on latest Ubuntu server has perfectly runnig PHP5-FPM out of the box.

Now about PHP cache. I’ve tried e-accelerator at the beginning, then it was xcache and at last I’ve grounded on APC. I cannot answer why APC, but I simply never had any problem with, that is what I can not tell about two others, especially about xcache.

 

So now you are aware about these processes that preceded my successful completion of optimization and possibly your tried something similar already to do, so now I will describe it more in details.

 

So first of all and most important is OS limitations, this is where I’ve lost most of my time, trying to implement somebodies advices without any success.

 

Here is my Linux Kernel configuration file, /etc/sysctl.conf

 

fs.file-max = 262144

kernel.pid_max = 262144

net.ipv4.tcp_rmem = 4096 87380 8388608

net.ipv4.tcp_wmem = 4096 87380 8388608

net.ipv4.netfilter.ip_conntrack_max = 65536

net.core.rmem_max = 25165824

net.core.rmem_default = 25165824

net.core.wmem_max = 25165824

net.core.wmem_default = 131072

net.core.netdev_max_backlog = 8192

net.ipv4.tcp_window_scaling = 1

net.core.optmem_max = 25165824

net.core.somaxconn = 65536

net.ipv4.ip_local_port_range = 1024 65535

kernel.shmmax = 4294967296

vm.max_map_count = 262144

 

You can easily find in the man of all the descriptions of these parameters, but what I wish to mention that this configuration is good for my 16GB RAM server. I suppose more RAM means bigger values possible.

But all these settings above are useless without fixing ulimit parameter for you Linux. For Debian based Linux it should be done in /etc/security/limits.conf

 

it should be updated for any user which responsible for your running package

 

nginx soft nofile 131072

nginx soft nofile 131072

mysql soft nofile 131072

mysql soft nofile 131072

root soft nofile 131072

root soft nofile 131072

 

and don’t forget to add in /etc/pam.d/common-session the following string:

 

session required pam_limits.so

 

So here you are! Your server can breathe in much freely!

 

Now the database.

 

First and the main this is to switch your MySQL into InnoDB. Yes, it take more memory than MyISAM, it harder to process defragmentation for them, but the performance is why you are here on this page, so InnoDB and no more discussions.

Before I will publish my configuration file for MySQL, there is another important step you should do. Create a RAM disk of 1-2 GB for MySQL “temp” purposes (various caches, tables, and others).

For Debian based Linux it should be done in /etc/fstab:

 

ramdisk /tmp tmpfs mode=1777,size=2048m

 

To check that it’s working use command df -h

 

you will get something like this:

 

ramdisk 2.0G 140M 1.9G 7% /tmp

 

Now the /etc/mysql/my.cnf file (tuned in suffers, tears and blood)

[mysqld]

user = mysql

socket = /var/run/mysqld/mysqld.sock

port = 3306

basedir = /usr

datadir = /var/lib/mysql

tmpdir = /tmp

default-storage-engine = InnoDB

skip-external-locking

bind-address = 127.0.0.1

key_buffer = 512K

max_allowed_packet = 16M

thread_stack = 128K

thread_cache_size = 48

thread_concurrency = 8

innodb_file_io_threads = 8

myisam-recover = BACKUP

max_connections = 768

table_cache = 2048

query_cache_type = 1

query_cache_limit = 32M

query_cache_size = 384M

query_cache_min_res_unit = 256

query_prealloc_size = 65K

range_alloc_block_size = 128K

read_rnd_buffer_size = 1M

record_buffer = 1M

read_buffer_size = 4M

join_buffer_size = 2M

sort_buffer_size = 2M

bulk_insert_buffer_size = 8M

long_query_time = 2

sync_binlog = 1

log_bin = /var/log/mysql/mysql-bin.log

expire_logs_days = 5

max_binlog_size = 100M

innodb_additional_mem_pool_size = 64M

innodb_buffer_pool_size = 4096M

innodb_log_file_size = 256M

innodb_log_files_in_group = 2

innodb_log_buffer_size = 32M

innodb_open_files = 16384

open-files-limit = 16384

max_tmp_tables = 1024

table_open_cache = 2048

#tmp_table_size = 512M

#max_heap_table_size = 512M

wait_timeout = 1200

 

As I said, explanations will be later on, all parameters could be found in on MySQL manual.

 

Now most annoying, but where important for overall productivity of PHP Web server – PHP5-FPM.

 

I will provide configuration of 3 files:

php.ini

main.conf

www.conf

 

I change only 4 parameters in this file /etc/php5/fpm/php.ini:

 

memory_limit = 256M

post_max_size = 8M

upload_max_filesize = 10M

max_file_uploads = 20

 

and add support of APC with the following string:

 

extension=/usr/lib/php5/20090626/apc.so

 

This is it for php.ini

 

Now php handler parameters which could be found in /etc/php5/fpm/main.conf:

 

[global]

pid = /var/run/php5-fpm.pid

error_log = /var/log/php5-fpm.log

emergency_restart_threshold = 0

emergency_restart_interval = 0

process_control_timeout = 0

daemonize = yes

include =/etc/php5/fpm/pool.d/*.conf

 

And now /etc/php5/fpm/pool.d/www.conf:

[www]

listen = 127.0.0.1:9000

;listen = /tmp/php5-fpm.sock

listen.backlog = 65536

user = www-data

group = www-data

pm = dynamic

pm.max_children = 16

;pm.start_servers = 12

;pm.min_spare_servers = 8

;pm.max_spare_servers = 16

pm.max_requests = 1536

pm.status_path = /status

request_terminate_timeout = 0

rlimit_files = 65536

rlimit_core = unlimited

catch_workers_output = no

 

Please not that all configuration files above are not for simple copy paste but for understanding the idea why the values are like these.

 

It’s not recommended to use PHP5-FPM via unix socket for more than 300+ concurrent connections due to you will definitely get nasty error: sock failed (11: Resource temporarily unavailable) or connect() failed (11: Resource temporarily unavailable) depending on your version, (you may need to increase start_servers, or min/max_spare_servers), and and every 5th page will be return Error 502. By using tcp/ip as a handler you will get more stability but will loose around 10 connection on each 100 concurrent connection in performance.

 

And the other 2 tricky things to make it to run more smoothly is to gracefully restart PHP5-FPM every 9 minutes and clear the Query Cache on MySQL every 8 minutes. Again, the time depends on your load and content of your project.

 

For doing that you need Cron up and running and two files with special commands to execute:

 

To set the cron, you need crontab -e to set the time of execute and path to the files:

 

*/8 * * * * /etc/cron.my/mysql_query_flash

*/9 * * * * /etc/init.d/php5-fpm reload

 

Here the script of the /etc/cron.my/mysql_query_flash file:

mysql -u root -pYOURPASSWORD -e “flush query cache”;

 

 

Nginx configuration. Nothing interesting and tricky here.

 

As for/etc/nginx/nginx.conf file here you are:

 

user www-data;

worker_processes 8;

pid /var/run/nginx.pid;

timer_resolution 100ms;

worker_rlimit_nofile 32768;

worker_priority -5;

events {

worker_connections 4096;

multi_accept on;

use epoll;

}

http {

sendfile on;

tcp_nopush on;

tcp_nodelay on;

keepalive_timeout 60;

types_hash_max_size 2048;

server_tokens off;

server_names_hash_bucket_size 64;

server_name_in_redirect off;

client_body_buffer_size 8K;

client_header_buffer_size 2k;

client_max_body_size 16k;

large_client_header_buffers 4 2k;

include /etc/nginx/mime.types;

default_type application/octet-stream;

access_log off ; #/var/log/nginx/access.log;

error_log /var/log/nginx/error.log;

gzip on;

gzip_disable “msie6”;

gzip_vary on;

gzip_proxied any;

gzip_comp_level 6;

gzip_buffers 64 8k;

gzip_min_length 1024; gzip_http_version 1.1;

gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

include /etc/nginx/conf.d/*.conf;

}

 

APC configuration /etc/php5/conf.d/apc.ini I left in default setting but only increased the RAM parameter:

 

apc.shm_size = 128M

 

For now this is it. I use STATUS module from nginx that is quite informative and here is the average result during the Saturday and Sunday when I have the highest load.

I’m quite proud of it!

Active connections: 2617
server accepts handled requests
 62950 62950 300560
Reading: 198 Writing: 81 Waiting: 2338