Don’t leave web server performance on the table.

If you followed the WordPress Install Guide you are leaving performance on the table for your website. The WordPress Install Guide defaults to Apache’s mod PHP which is definitely leaving performance on the table.

Here is how to fix that in Ubuntu 20.04:

  1. install php-fpm: sudo apt install php-fpm
  2. disable mod_php and mpm_prefork: sudo a2dismod php mpm_prefork
  3. enable mpm_event proxy_fcgi and setenvif: sudo a2enmod mpm_event proxy_fcgi setenvif
  4. enable the php-fpm configuration file: sudo a2enconf php7.4-fpm
  5. mark apache2 as a manually installed package: sudo apt-mark manual apache2
  6. remove mod_php: sudo apt purge libapache2-mod-php*

That takes care of the general Apache configuration. The next configuration adjustments need to be done at the vhost level. Substitute the correct information for YOUREMAIL, YOURDOMAIN, and WPDIR, and you are good to go.

<VirtualHost *:443>
Protocols h2 http/1.1
ServerName YOURDOMAIN
ServerAdmin YOUREMAIL@YOURDOMAIN.COM

DocumentRoot "WPDIR"

<Directory "WPDIR">
    <FilesMatch "\.php$">
 #Proxy php files to php-fpm
        SetHandler  "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/"
    </FilesMatch>
    Options All
    AllowOverride All
    DirectoryIndex index.php
    Require all granted
</Directory>

#Protect the uploads directory.
<Directory "WPDIR/wp-content/uploads/">
    <FilesMatch "\.php$">
        SetHandler  none
        Require all denied
    </FilesMatch>
</Directory>

#snip extraneous stuff
</VirtualHost>

Let me take a minute to talk about performance. I have a small Atom 230 based ITX computer that I use for some network administration tasks and as a CPU limited system for benchmarking.

robert@pallas:~$ lscpu
Architecture:                    x86_64
CPU op-mode(s):                  32-bit, 64-bit
Byte Order:                      Little Endian
Address sizes:                   32 bits physical, 48 bits virtual
CPU(s):                          2
On-line CPU(s) list:             0,1
Thread(s) per core:              2
Core(s) per socket:              1
Socket(s):                       1
NUMA node(s):                    1
Vendor ID:                       GenuineIntel
CPU family:                      6
Model:                           28
Model name:                      Intel(R) Atom(TM) CPU  230   @ 1.60GHz
Stepping:                        2
CPU MHz:                         1595.763
BogoMIPS:                        3191.77
L1d cache:                       24 KiB
L1i cache:                       32 KiB
L2 cache:                        512 KiB
NUMA node0 CPU(s):               0,1
Vulnerability Itlb multihit:     Not affected
Vulnerability L1tf:              Not affected
Vulnerability Mds:               Not affected
Vulnerability Meltdown:          Not affected
Vulnerability Spec store bypass: Not affected
Vulnerability Spectre v1:        Not affected
Vulnerability Spectre v2:        Not affected
Vulnerability Srbds:             Not affected
Vulnerability Tsx async abort:   Not affected
Flags:                           fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush dts acpi mmx fxsr sse sse2 ss h
                                 t tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts nopl cpuid aperfmperf pni dtes64 monitor ds_cpl
                                  tm2 ssse3 cx16 xtpr pdcm movbe lahf_lm dtherm
robert@pallas:~$

Pallas isn’t fast but it does make a good test bench for what performance difference you might see on a very limited server. I will leave the full benchmark tests below. To summarize the performance looks pretty similar from a users perspective. However, if I point out that the mpm_prefork test had a load average of 88.72 while the mpm_event test had a load average of 11.04 you can see that while mpm_event may not be faster than mp_prefork from a users perspective it does help with server load.

You can expect a return to this topic once I find a way to stress test HTTP 2 connections.

Prefork
robert@Venus:~$ sudo ab -c 100 -n 100000 -r http://192.168.1.12/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.12 (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        Apache/2.4.41
Server Hostname:        192.168.1.12
Server Port:            80

Document Path:          /
Document Length:        10918 bytes

Concurrency Level:      100
Time taken for tests:   110.875 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      1119200000 bytes
HTML transferred:       1091800000 bytes
Requests per second:    901.91 [#/sec] (mean)
Time per request:       110.875 [ms] (mean)
Time per request:       1.109 [ms] (mean, across all concurrent requests)
Transfer rate:          9857.63 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    4  14.6      3    1039
Processing:     2  107  77.7    108     611
Waiting:        1   29  27.3     21     403
Total:          3  111  78.9    112    1260

Percentage of the requests served within a certain time (ms)
  50%    112
  66%    151
  75%    170
  80%    182
  90%    212
  95%    239
  98%    273
  99%    298
 100%   1260 (longest request)
robert@Venus:~$
Event
robert@Venus:~$ sudo ab -c 100 -n 100000 -r http://192.168.1.12/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.12 (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        Apache/2.4.41
Server Hostname:        192.168.1.12
Server Port:            80

Document Path:          /
Document Length:        10918 bytes

Concurrency Level:      100
Time taken for tests:   118.068 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      1119200000 bytes
HTML transferred:       1091800000 bytes
Requests per second:    846.97 [#/sec] (mean)
Time per request:       118.068 [ms] (mean)
Time per request:       1.181 [ms] (mean, across all concurrent requests)
Transfer rate:          9257.12 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3  40.6      1    3143
Processing:    11  115  25.5    110     454
Waiting:        2  108  21.4    107     384
Total:         19  118  47.7    111    3235

Percentage of the requests served within a certain time (ms)
  50%    111
  66%    115
  75%    121
  80%    128
  90%    147
  95%    165
  98%    192
  99%    216
 100%   3235 (longest request)
robert@Venus:~$

—Robert