There’s several ways to speedup WordPress:-
- Gzip or Deflate compression.
- Minify HTML, CSS and JS files.
- Browser’s Cache-Control.
- Front-end proxy/page cache.
Disclaimer: As I’m frequently updating the original guides and installers here on TeaNazaR.com, I will not be responsible for any brick issues if you were to follow my obsolete guides copied elsewhere. Thus subscribe to this post to get latest updates. Modifying any part of a device may void its warranty.
Most notably with WordPress’s W3 Total Cache plugin which have the ability to perform all four methods above. While this a plus point of having all-in-one, I have to take into consideration for the system resources. Trying to perform all these at once in a single PHP request apparently bogs down my tiny WDMyCloud 4TB NAS which only has 650MHz dual core and 256Mb ram.
So I decided to split the process. W3 Total Cache plugin will only perform the page caching production. Then introduce an external monitoring shell script to gzip and minify (htm/html/scss/css/js) the cached page when there’s changes on a lower priority run. Nginx on the other hand will handle the serving of GZIP cached contents. The advantage to this is that I can also automatically gzip all other necessary contents (e.g. jpg, png etc.) almost on-the-fly without impacting the main PHP process.
The setup are accomplished as follows:-
- Only let WordPress’s W3 Total Cache plugin cache the accessed pages (no gzip or minify), the rest are possibly left as default or your preferences: In the ‘Performance -> General Settings’, only enable below:
a) Enabled ‘Page cache’. ‘Page cache method’ set to ‘Disk Enhanced’. Go to the ‘Page Cache’ page and enable ‘Cache posts page/feeds/SSL’. It’s also recommended to prime and preload the cache if you have any sitemap plugin already installed. I did my cache priming with the external ‘wp-cron.php’ cronjob script as described in my other post here.
b) Enabled ‘Browser Cache’. Go to the ‘Browser Cache’ page enable all four ‘Set expires header’ and disable all four ‘Enable HTTP (gzip) compression’. - Place the script below somewhere on your server, make the script executable
chmod 0755 ./iNotifyMinifyGzip.sh (assuming the script is in the current path) and schedule a cronjob
crontab -e to run it at boot as a HTTPD user i.e. “www-data” (WP plugins will have permission issues upgrading if caches created as root), then add the schedule to a new line:1@reboot sudo -u www-data nice -n19 /home/Nazar/scripts/iNotifyMinifyGzip.sh >/dev/null 2>&1
You can change the nice priority accordingly, nice -n19 as the lowest and nice -n-20 as the highest (not recommended) or just omit the nice command to run at normal priority. For testing purposes, you can run it normally on the terminal ./iNotifyMinifyGzip.sh (assuming the script is in the current path). To run the process permanently in background, use sudo -u www-data nohup ./iNotifyMinifyGzip.sh >/dev/null 2>&1 &. If you’ve made changes to the script, just re-run it again and the previous process will get terminated. To stop it, issue killall iNotifyMinifyGzip.sh inotifywait. - Prepare the requirements and follow instructions in the script below to activate the auto monitoring:
iNotifyMinifyGzip.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | #!/bin/bash # iNotifyMinifyGzip.sh v3.7 by Nazar78 @ TeaNazaR.com ##################################################### # Simple script to pre-GZIP static files via Nginx's gzip_static module. # This is done via inotify-tools which will also minify HTML/JS/CSS files before gzipping. # '*.gz' files will be deleted/recreated automatically if the original has been removed/modified. # Changes only affects newer files during runtime. To force the process, 'touch' the files: # i.e 'find /var/www/wordpress -type f -name "*.html" -exec touch {} \;'. # There will be only one script instance running, existing will be killed if any. # # Requirements: # - Nginx's gzip_static module - Add to Nginx's config "gzip_static on;" # - GZIP CLI - On Debian, apt-get install gzip # - PHP CLI - On Debian, apt-get install php5-cli # - Perl CLI - On Debian, apt-get install perl # - inotify-tools - On Debian, apt-get install inotify-tools # - minify-2.1.7 - http://code.google.com/p/minify/ # # Known issues: # - inotifywait can miss some events if there's lots of files being processed at once. # The affected files will be processed at next file access, if possible. # - inotifywait doesn't follow symlinks and this is its limitation. # Mount bind the paths if needed. # # History: # v1.0 - 20150220 - 1st release. # v1.1 - 20150422 - Changed working path to '/tmp/' and fixed multiple request overlapping causing corrupted gzip. # v1.2 - 20150428 - Add some checkings for minify.php and gzip. # v1.3 - 20150520 - Added new ASIDE html tags cache so all pages reflect the latest when frontpage gets updated. # v1.4 - 20150527 - Fixed the known rename event issue, long filename issue and several others. # v1.5 - 20150529 - Update comment pages when main page gets updated which is being left out by WP Total Cache. # v1.6 - 20150616 - Fixed attribute change event which caused issues. # v1.7 - 20150623 - Fixed ASIDE html tags issues with Google translate and removed a bug from v1.5. # v1.8 - 20150825 - Remove pages paths when main page gets deleted which is being left out by WP Total Cache. # v1.9 - 20151005 - Fixed corrupted gzip by deleting when files still locked by other processes. # v2.0 - 20151006 - Fixed unable to recursively remove pages paths when main page gets deleted. # v2.1 - 20151009 - Fixed ASIDE html tags cache for WP JetPack Gallery 'This slideshow requires JavaScript'. # v2.2 - 20151016 - Tweaked ASIDE html tags cache for WP JetPack Gallery to load images only after page loaded. # v2.3 - 20151112 - Prettify ASIDE html tags cache 'Loading...' text. # v2.4 - 20151130 - Fixed not to force gzip newly created/modified images to prevent corruption. # v2.5 - 20160226 - Minor fixes on rm command and auto remove zero byte pages resulted from failed PHP process. # v2.6 - 20160926 - Minor fixes on regexp causing gallery not loading due to upgrade of WP Total Cache v0.9.5. # v2.7 - 20160929 - Fixed not to gzip a file that failed to be minified, instead, gzip the original file. # v2.8 - 20170503 - Improve detection of .old and _old files. # v2.9 - 20170509 - Support more page caching plugins and remove invalid pages resulted from failed PHP process. # v3.0 - 20171118 - Upgrade the ASIDE support for WP Core 4.9 Gallery from 'gallery-#' to 'media_gallery-#'. # v3.1 - 20180324 - Upgrade the ASIDE support for new Jetpack Gallery slideshow. # v3.2 - 20180521 - Fixed excluded items still being processed. Changed sub shell process to function. # v3.3 - 20190101 - Exclude already minified JS/CSS '*.min.js/css' from being erroneously re-minified again. # v3.4 - 20190722 - Fixed bash parameter substitution by escaping the tilde home expansion character. # v3.5 - 20200420 - Improve ASIDE html tags without double quotes. Allow Jetpack slideshow JS full URL. Add last modified timestamp variable to request URI. # v3.6 - 20220407 - Fixed getScript regex containing ID in script tags. # v3.7 - 20220630 - Added WP Total Cache request URI with '_slash'. # # PS: Feel free to distribute but kindly retain the credits (-: ##################################################### # Begin settings # GZIP_ROOTS - Absolute server root path to monitor recursively for changes. Each separated by new line. # Note that you'll need to restart the script if any of the 'GZIP_ROOTS' got renamed or replaced. GZIP_ROOTS=" /share/Web/wordpress " # GZIP_COMPRESSION - Gzip compress ratio. '1-9', least to best. GZIP_COMPRESSION=9 # MINIFY_CLI - Absolute path to command line minify 'minify.php'. MINIFY_CLI="/share/Web/www/sbin/minify-2.1.7/min_extras/cli/minify.php" # EXCLUDE_SUBDIRS - Excluded subdirectories to speedup monitoring process. Each separated by new line. EXCLUDE_SUBDIRS=" /share/Web/wordpress/wp-content/upgrade /share/Web/wordpress/wp-content/cache/config /share/Web/wordpress/wp-content/cache/db /share/Web/wordpress/wp-content/cache/object /share/Web/wordpress/wp-content/cache/tmp " # EXCLUDE_EXTENSIONS - Excluded file extensions to speedup monitoring process. Each separated by space. # 'gz' is automatically excluded in the monitoring process. EXCLUDE_EXTENSIONS="log sql zip tar pdf php pl cgi fcgi conf htaccess htpasswd htgroup bak old tmp crt key cnt sh csv src" # MONITOR_EXTENSIONS - Inotify monitor changes on file extensions. Each separated by space. # Note that 'EXCLUDE_EXTENSIONS' is priority. Excluded extensions will not be monitored even specified here. # 'htm html scss css js' are automatically monitored as they needs to be minified thus need not specify. MONITOR_EXTENSIONS="xml bmp jpeg jpg png gif ico swf ttf eot woff svg txt webm" # CACHE_ASIDE - Cache ASIDE html tags so all pages reflect the latest when frontpage gets updated. Each separated by space. # Note that this depends on your WP theme/plugin if they use <ASIDE> html tags. Caches are loaded via AJAX but SEO friendly. # This will parse single/multi-domain cache paths and create the frontpage aside cache accordingly. CACHE_ASIDE="recent-posts-2 recent-comments-2 tag_cloud-2 top-posts-2 archives-2 blog_subscription-2 gallery-10" # CACHE_PATH - ./wordpress/wp-content/cache/[PATH] relative path name specific to the caching plugin. CACHE_PATH="page_enhanced" # End settings FIND_ROOT() { DOC_ROOT= for i in "${GZIP_ROOTS_ARRAY[@]}";do [[ "$1" =~ "$i/" ]]&&DOC_ROOT="$i"&&break done } FIND_ASIDE() { ASIDE_ROOT= CACHE_ARRAY=($(find "$DOC_ROOT/wp-content/cache/${CACHE_PATH}"\ -mindepth 1 -maxdepth 1 -type d 2>/dev/null)) for i in "${CACHE_ARRAY[@]}";do [[ "$1" =~ "$i/" ]]&&ASIDE_ROOT="$i"&&break done } BUILD_CACHE() { ASIDE= DIR="${1%/*}" BASE="${DIR##*/}" FILE="$(cat "$1" 2>/dev/null)"||return rm -f "$DOC_ROOT/wp-content/cache/$BASE-*.html">/dev/null 2>&1 for i in "${CACHE_ASIDE_ARRAY[@]}";do ASIDE=$(echo "$FILE"|perl -s0777ne\ 'print $1 if /<aside id=.?$i.?.+?>(.+?)<\/aside>/igs' -- -i="$i" 2>/dev/null) if [ "$ASIDE" != "" ];then SLIDESHOW= if [[ "$i" =~ ^gallery-[0-9] ]] || [[ "$i" =~ ^media_gallery-[0-9] ]];then SLIDESHOW=$(echo "$FILE"|perl -s0777ne\ 'print $1 if /<script.+src=.?(.+?slideshow-shortcode.+?).?\s?(id=.+)?><\/script>/i' 2>/dev/null) if [ "$SLIDESHOW" != "" ];then SLIDESHOW="<script>(function($) { \$('.jetpack-slideshow-noscript').html('Loading...'); window.onload=function(){$.ajaxSetup({cache: true}); $.getScript('${SLIDESHOW}');};})(jQuery);</script>" fi fi echo "<script>if (!document.getElementById('$i')) document.location = '//$BASE'</script> $ASIDE$SLIDESHOW">"$DOC_ROOT/wp-content/cache/$BASE-$i.html" echo "Built aside tag cache for '$DOC_ROOT/wp-content/cache/$BASE-$i.html'" fi done if [ "$ASIDE" != "" ];then ASIDE_CACHED=("${ASIDE_CACHED[@]}" "$DOC_ROOT/wp-content/cache/${CACHE_PATH}/$BASE:1") ASIDE_CACHED_TS[$DOC_ROOT]=$(date +%Y%m%d%H%M%S) else ASIDE_CACHED=("${ASIDE_CACHED[@]}" "$DOC_ROOT/wp-content/cache/${CACHE_PATH}/$BASE:0") unset ASIDE_CACHED_TS[$DOC_ROOT] fi } PROCESS() { if [ -z "$PARENT" ];then ps -ef|grep -E '[a-zA-Z]+\s+[0-9]+\s+1\s.+iNotifyMinifyGzip.?sh'|\ awk '{print $2}'|xargs kill -9>/dev/null 2>&1 PARENT=1 fi while read D T E F;do # echo "DEBUG - [$D $T] $E: $F"&&continue [[ "$E" =~ ISDIR ]]||[ "$D $T $F" = "$PROCESSED" ]||\ [[ "${F%/*}" =~ $EXCLUDE_SUBDIRS ]]||[[ "${F##*/}" =~ \.($EXCLUDE_EXTENSIONS)$ ]]&&continue PROCESSED="$D $T $F" if [[ ! "$E" =~ (DELETE|MOVED_FROM) ]]&&[[ "$F" =~ \.($MONITOR_EXTENSIONS)$ ]];then [[ "$E" =~ CLOSE ]]&&[ -f "$F.gz" ]&&continue [[ "$E" =~ ATTRIB ]]&&eval [ "$(stat -c"%Y != %Z" "$F" 2>/dev/null) 2>/dev/null" ]&&continue echo "[$D $T] $E: $F" if [[ "$E" =~ (CREATE|MODIFY) ]]&&[[ "$F" =~ \.(bmp|jpeg|jpg|png|gif)$ ]];then rm -f "$F.gz">/dev/null 2>&1 continue fi TMPF="${F//\//\~}"&&TMPF="/tmp/iNotifyMinifyGzip_$RANDOM_${TMPF:0:227}.gz_" if [[ "$F" =~ \.(htm|html)$ ]];then if [ "$(grep -Ec "<\/(div|html|loc|script)>" "$F" 2>/dev/null)" = 0 ];then rm -f "$F" "$F.gz" "$F"*old>/dev/null 2>&1 echo "Removed invalid html page cache from '$F*'" continue fi FIND_ROOT "$F" FIND_ASIDE "$F" if [[ "$F" =~ $ASIDE_ROOT/_index(_slash)?.html$ ]]|| [[ ! "${ASIDE_CACHED[@]}" =~ "$ASIDE_ROOT:" ]]&& [ -e "$ASIDE_ROOT/${F##*/}" ];then BUILD_CACHE "$ASIDE_ROOT/${F##*/}" fi ASIDE= if [[ "$F" =~ /_index(_slash)?.html$ ]]&&[[ "${ASIDE_CACHED[@]}" =~ "$ASIDE_ROOT:1" ]];then ASIDE=$(perl -s0777pe\ 's/(<aside id=.?($L0).?.+?>).+?(<\/aside>)/"$1 Loading... <a href=\"\/wp-content\/cache\/$L5-$2.html\" onclick=\"window.location.reload(true)\"> ".pretty($2)."<\/a> $3$L1$2$L2$L5-$2$L3$L6$L4"/eigs;sub pretty {my $i = shift;$i =~ s/(-\d+$|_|-)/ /g; return join("", map(ucfirst, split(/(\w+)/, $i)))}'\ -- -L0="$L0" -L1="$L1" -L2="$L2" -L3="$L3" -L4="$L4" \ -L5="${ASIDE_ROOT##*/}" -L6="${ASIDE_CACHED_TS[$DOC_ROOT]}" "$F" 2>/dev/null) fi if [ "$ASIDE" != "" ];then if [ "$SLIDESHOW" != "" ];then ASIDE=$(echo "$ASIDE"|perl -s0777pe\ 's/<script.+src=.?.+?slideshow-shortcode.+?.?><\/script>//i' 2>/dev/null) fi MINIFIED=$(echo "$ASIDE"|php "$MINIFY_CLI" -t html -d"$DOC_ROOT") if [ "$MINIFIED" != "" ];then echo "$MINIFIED"|gzip -c$GZIP_COMPRESSION>"$TMPF" else echo "$ASIDE"|gzip -c$GZIP_COMPRESSION>"$TMPF" fi else MINIFIED=$(php "$MINIFY_CLI" -t html -d"$DOC_ROOT" "$F") if [ "$MINIFIED" != "" ];then echo "$MINIFIED"|gzip -c$GZIP_COMPRESSION>"$TMPF" else gzip -c$GZIP_COMPRESSION "$F">"$TMPF"||rm -f "$TMPF">/dev/null 2>&1 fi fi rm -f "$F"*old>/dev/null 2>&1 elif [[ "$F" =~ \.js$ ]]&&[[ ! "$F" =~ \.min\.js$ ]];then FIND_ROOT "$F" MINIFIED=$(php "$MINIFY_CLI" -t js -d"$DOC_ROOT" "$F") if [ "$MINIFIED" != "" ];then echo "$MINIFIED"|gzip -c$GZIP_COMPRESSION>"$TMPF" else gzip -c$GZIP_COMPRESSION "$F">"$TMPF"||rm -f "$TMPF">/dev/null 2>&1 fi elif [[ "$F" =~ \.(scss|css)$ ]]&&[[ ! "$F" =~ \.min\.(scss|css)$ ]];then FIND_ROOT "$F" MINIFIED=$(php "$MINIFY_CLI" -t css -d"$DOC_ROOT" "$F") if [ "$MINIFIED" != "" ];then echo "$MINIFIED"|gzip -c$GZIP_COMPRESSION>"$TMPF" else gzip -c$GZIP_COMPRESSION "$F">"$TMPF"||rm -f "$TMPF">/dev/null 2>&1 fi else gzip -c$GZIP_COMPRESSION "$F">"$TMPF"||rm -f "$TMPF">/dev/null 2>&1 fi mv -f "$TMPF" "$F.gz">/dev/null 2>&1||rm -f "$F.gz">/dev/null 2>&1 elif [ -f "$F.gz" ];then if [[ "$E" =~ (DELETE|MOVE) ]]&&[[ "$F" =~ /_index(_slash)?.html$ ]];then find "${F%/*}" -maxdepth 1 -type d -name "comment-page*" -o -name "page"|while read OLD;do rm -fr "$OLD">/dev/null 2>&1 echo "Removed stale caches from '$OLD'" done fi echo "[$D $T] $E: $F" rm -f "$F.gz" "$F"*old>/dev/null 2>&1 fi done } [ "$(which gzip 2>/dev/null)" = "" ]&&echo "Error: Unable to find 'GZIP CLI'."&&exit 1 [ "$(which php 2>/dev/null)" = "" ]&&echo "Error: Unable to find 'PHP CLI'."&&exit 1 [ "$(which perl 2>/dev/null)" = "" ]&&echo "Error: Unable to find 'PERL CLI'."&&exit 1 [ "$(which inotifywait 2>/dev/null)" = "" ]&&echo "Error: Unable to find 'inotify-tools'."&&exit 1 [ ! -e "$MINIFY_CLI" ]&&echo "Error: Unable to find '\$MINIFY_CLI'."&&exit 1 [[ "$GZIP_COMPRESSION" =~ [0-9] ]]&&[ "$GZIP_COMPRESSION" -le 9 ]||GZIP_COMPRESSION=9 [ "$(echo $GZIP_ROOTS)" = "" ]&&echo "Error: Please specify some '\$GZIP_ROOTS'."&&exit 1 for i in ${GZIP_ROOTS//\\n/};do [ ! -e "$i" ]&&echo "Error: Path does not exists '$i'."&&exit 1;done [ "$(echo $EXCLUDE_SUBDIRS)" = "" ]&&EXCLUDE_SUBDIRS="/dev/null"|| EXCLUDE_SUBDIRS=$(echo $EXCLUDE_SUBDIRS|sed 's/ /|/g') [ "$(echo $EXCLUDE_EXTENSIONS)" = "" ]&&EXCLUDE_EXTENSIONS="/dev/null"|| EXCLUDE_EXTENSIONS=$(echo gz gz_ $EXCLUDE_EXTENSIONS|sed 's/ /|/g') MONITOR_EXTENSIONS=$(echo htm html scss css js $MONITOR_EXTENSIONS|sed 's/ /|/g') CACHE_ASIDE_REGEX=$(echo $CACHE_ASIDE|sed 's/ /|/g') EVENTS="attrib,create,modify,close_nowrite,move,delete" ps -ef|grep iNotifyMinifyGzip.sh|grep -v grep|grep -v $$|awk {'print $2'}|xargs kill -9>/dev/null 2>&1 ps -ef|grep "inotifywait -mre$EVENTS"| grep -v grep|awk {'print $2'}|xargs kill -9>/dev/null 2>&1 rm -f /tmp/iNotifyMinifyGzip_*>/dev/null 2>&1 L0=$CACHE_ASIDE_REGEX L1='<script>(function($) {$( "#' L2='" ).load( "/wp-content/cache/' L3=".html?${0##*/}=" L4='");})(jQuery);</script>' GZIP_ROOTS_ARRAY=(${GZIP_ROOTS//\\n/}) CACHE_ASIDE_ARRAY=(${CACHE_ASIDE//\\n/}) declare -A ASIDE_CACHED_TS inotifywait -mre$EVENTS --timefmt '%F %T' --format '%T %e %w%f'\ --excludei "\.($EXCLUDE_EXTENSIONS)$|($EXCLUDE_SUBDIRS)|(^/.+/$)" $(echo $GZIP_ROOTS)|PROCESS exit $? |