, 23 min read
PHP stuttering under load
This very blog is run by Simplified Saaze. Simplified Saaze is a static site generator, i.e., it reads Markdown files and produces HTML files, which are then served from a web-server. This is called static mode.
Simplified Saaze can also be used in a dynamic mode. In that mode the Markdown file is converted to HTML on the fly, i.e., in the exact moment when the web-server receives a request, Simplified Saaze produces the required HTML.
This blog post describes why I moved back from dynamic mode to static mode.
- A. Current setup
- B. Deployment scripts for static sites
- C. Running deployment scripts
- D. NGINX configuration
A. Current setup
1. File processing in Simplified Saaze. Simplified Saaze is written in PHP. It does the following for every Markdown file:
- reads collection file with
file_get_contents()
- recursively traverses the directory for all Markdown files with
scandir()
- read entire file with
file_get_contents()
- parses the file with
yaml_parse()
- file content is checked against keywords (like '[youtube]' or
$$
for math) - content is then converted to HTML using MD4C
- excerpt is computed from HTML using
strip_tags()
- content is word counted on original Markdown
- the (output) template PHP is run on the HTML, which in turn calls
eval()
on the entire HTML - that is then written to a file using
file_put_contents()
, or directly fed back to the web-server
Quite some file I/O and string processing.
The last few blog posts regarding stability mountains were quite large Markdown files:
-rw-r--r-- 1 klm klm 415K Jul 15 10:21 07-14-stability-mountains-for-bdf1.md
-rw-r--r-- 1 klm klm 810K Jul 15 10:30 07-14-stability-mountains-for-bdf2.md
-rw-r--r-- 1 klm klm 1.2M Jul 15 10:32 07-14-stability-mountains-for-bdf3.md
-rw-r--r-- 1 klm klm 1.6M Jul 15 10:36 07-14-stability-mountains-for-bdf4.md
-rw-r--r-- 1 klm klm 2.0M Jul 15 10:38 07-14-stability-mountains-for-bdf5.md
-rw-r--r-- 1 klm klm 2.4M Jul 15 10:39 07-14-stability-mountains-for-bdf6.md
-rw-r--r-- 1 klm klm 1.3M Jul 15 10:43 07-14-stability-mountains-for-tendler3.md
-rw-r--r-- 1 klm klm 1.7M Jul 15 10:44 07-14-stability-mountains-for-tendler4.md
-rw-r--r-- 1 klm klm 2.1M Jul 15 10:45 07-14-stability-mountains-for-tendler5.md
-rw-r--r-- 1 klm klm 2.5M Jul 15 10:46 07-14-stability-mountains-for-tendler6.md
-rw-r--r-- 1 klm klm 3.0M Jul 15 10:47 07-14-stability-mountains-for-tendler7.md
-rw-r--r-- 1 klm klm 1.2M Jul 15 10:51 07-14-stability-mountains-for-tischer3.md
-rw-r--r-- 1 klm klm 1.6M Jul 15 10:55 07-14-stability-mountains-for-tischer4.md
-rw-r--r-- 1 klm klm 2.0M Jul 15 10:56 07-14-stability-mountains-for-tischer5.md
-rw-r--r-- 1 klm klm 2.4M Jul 15 10:58 07-14-stability-mountains-for-tischer6.md
-rw-r--r-- 1 klm klm 2.8M Jul 15 10:59 07-14-stability-mountains-for-tischer7.md
-rw-r--r-- 1 klm klm 3.2M Jul 15 11:00 07-14-stability-mountains-for-tischer8.md
2. NGINX caching.
I had NGINX configured such that a once generated HTML file will be cached using fastcgi_no_cache
.
http {
root /srv/http;
fastcgi_cache_path /var/cache/nginx/ keys_zone=nginxpc:720m inactive=720m;
fastcgi_cache_key "$request_method$request_uri";
fastcgi_cache nginxpc;
fastcgi_cache_valid 720m;
...
}
I.e., files are cached for 720 minutes, that is 12 hours.
Caching is activated for below files:
server {
set $no_cache 0;
#Don't cache POST requests
if ($request_method = POST) {
set $no_cache 1;
}
if ($request_uri !~ "^/(aux|blog|music|gallery|jpilot|koehntopp|lemire|mobility|myrawest|nukeklaus|panorma|paternoster|saaze-example|vonhoff|wendt)") {
set $no_cache 1;
}
...
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
}
I had hoped that I can get the best of both worlds:
- speed of static files served by the web-server
- dynamic generation of HTML
The cache directory is quite full:
[root@ryzen /var/cache/nginx]# ls -l | wc
1110 9983 87565
3. All sub-blogs via a single index.php.
Below index.php
served all sub-blogs:
- my own blog
- J-Pilot
- Koehntop
- Lemire
- Mobility
- Myra West
- Nukeklaus
- Panorama
- Paternoster
- Saaze-Example
- Vonhoff
- Wendt
The PHP source code for index.php
is as follows.
<?php
$blog = array(
// prefix => array( directory with content, flag for cutting prefix, default sub-URL )
'/aux' => array('/home/klm/php/sndsaaze',0),
'/blog' => array('/home/klm/php/sndsaaze',0),
'/gallery' => array('/home/klm/php/sndsaaze',0),
'/music' => array('/home/klm/php/sndsaaze',0),
'/jpilot' => array('/home/klm/php/saaze-jpilot',1),
'/koehntopp' => array('/home/klm/php/saaze-koehntopp',1),
'/lemire' => array('/home/klm/php/saaze-lemire',1,'/blog'),
'/mobility' => array('/home/klm/php/saaze-mobility',1),
'/myrawest' => array('/home/klm/php/saaze-myrawest',1),
'/nukeklaus' => array('/home/klm/php/saaze-nukeklaus',1),
'/paternoster' => array('/home/klm/php/saaze-paternoster',1,'/posts'),
'/panorama' => array('/home/klm/php/saaze-panorama',1),
'/saaze-example' => array('/home/klm/php/saaze-example',1,'/blog'),
'/vonhoff' => array('/home/klm/php/saaze-vonhoff',1),
'/wendt' => array('/home/klm/php/saaze-wendt',1),
);
$query_string = isset($_SERVER['QUERY_STRING']) ? '/' . ltrim($_SERVER['QUERY_STRING'],'/') : '/blog';
if ($query_string === '/') $query_string = '/blog';
$dir = '/home/klm/php/sndsaaze';
foreach ($blog as $prefix => $dirFlag) { // linear search in blog[] array
if (str_starts_with($query_string,$prefix)) {
$dir = $dirFlag[0];
if ($dirFlag[1]) { // cut prefix and set rbase
$query_string = substr($query_string,strlen($prefix));
$GLOBALS['rbase'] = $prefix;
}
// Set default within the respective blog
if (strlen($query_string) <= 1 && isset($dirFlag[2])) $query_string = $dirFlag[2];
break;
}
}
//if (!isset($dir)) { echo "/srv/http/index.php: Not found in blog-array.\n"; return; }
// Composer is very slow, see https://eklausmeier.goip.de/blog/2023/10-29-simplified-saaze-monitored-with-phpspy
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/CollectionArray.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/Collection.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/Config.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/Entry.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/MarkdownContentParser.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/Saaze.php';
require '/home/klm/php/sndsaaze/vendor/eklausme/saaze/TemplateManager.php';
(new \Saaze\Saaze($dir))->run($query_string);
4. Problem statement. I received the following e-mail from Ahrefs:
The load time was larger than five seconds:
That's quite alarming.
5. OpCache trickery.
In return in php.ini
I enlarged from 256M
opcache.memory_consumption=512
I also activated in PHP-FPM's configuration file php-fpm.d/www.conf
the slowlog option:
slowlog = /var/log/nginx/$pool.log.slow
request_slowlog_timeout = 1
And, alas, this produced log entries like so:
[19-Jul-2025 13:52:45] [pool www] pid 38567
script_filename = /srv/http/index.php
[0x00007fed1f813100] run() /srv/http/index.php:47
[19-Jul-2025 13:52:58] [pool www] pid 38568
script_filename = /srv/http/index.php
[0x00007fed1f813100] run() /srv/http/index.php:47
[19-Jul-2025 21:09:18] [pool www] pid 52660
script_filename = /srv/http/index.php
[0x00007fed1f813100] run() /srv/http/index.php:47
Similar messages in PHP-FPM log:
Jul 19 11:45:30 ryzen systemd[1]: Started The PHP FastCGI Process Manager.
Jul 19 13:52:45 ryzen php-fpm[38565]: [WARNING] [pool www] child 38567, script '/srv/http/index.php' (request: "GET /index.php?blog/2025/07-14-stability-mountains-for-bdf6") executing too slow (1.067383 sec), logging
Jul 19 13:52:45 ryzen php-fpm[38565]: [NOTICE] child 38567 stopped for tracing
Jul 19 13:52:45 ryzen php-fpm[38565]: [NOTICE] about to trace 38567
Jul 19 13:52:45 ryzen php-fpm[38565]: [NOTICE] finished trace of 38567
Jul 19 13:52:58 ryzen php-fpm[38565]: [WARNING] [pool www] child 38568, script '/srv/http/index.php' (request: "GET /index.php?blog/2025/07-14-stability-mountains-for-tendler7") executing too slow (1.160562 sec), logging
Jul 19 13:52:58 ryzen php-fpm[38565]: [NOTICE] child 38568 stopped for tracing
Jul 19 13:52:58 ryzen php-fpm[38565]: [NOTICE] about to trace 38568
Jul 19 13:52:58 ryzen php-fpm[38565]: [NOTICE] finished trace of 38568
Jul 19 21:09:18 ryzen php-fpm[38565]: [WARNING] [pool www] child 52660, script '/srv/http/index.php' (request: "GET /index.php?feed.xml") executing too slow (1.209205 sec), logging
Jul 19 21:09:18 ryzen php-fpm[38565]: [NOTICE] child 52660 stopped for tracing
Jul 19 21:09:18 ryzen php-fpm[38565]: [NOTICE] about to trace 52660
Jul 19 21:09:18 ryzen php-fpm[38565]: [NOTICE] finished trace of 52660
Jul 20 04:17:53 ryzen php-fpm[38565]: [WARNING] [pool www] child 52660, script '/srv/http/index.php' (request: "GET /index.php?wendt/sitemap.html") executing too slow (1.013112 sec), logging
Jul 20 04:17:53 ryzen php-fpm[38565]: [NOTICE] child 52660 stopped for tracing
Jul 20 04:17:53 ryzen php-fpm[38565]: [NOTICE] about to trace 52660
Jul 20 04:17:53 ryzen php-fpm[38565]: [NOTICE] finished trace of 52660
This was due to the following bots reading the blog:
147.135.128.94|19/Jul/2025:13:52:36 +0200|200|698|GET /robots.txt HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:37 +0200|200|106356|GET /blog/2025/07-14-stability-mountains-for-bdf3 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:40 +0200|200|137803|GET /blog/2025/07-14-stability-mountains-for-bdf4 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:41 +0200|200|162942|GET /blog/2025/07-14-stability-mountains-for-bdf5 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:45 +0200|200|189812|GET /blog/2025/07-14-stability-mountains-for-bdf6 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:47 +0200|200|94930|GET /blog/2025/07-14-stability-mountains-for-tendler3 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:49 +0200|200|133534|GET /blog/2025/07-14-stability-mountains-for-tendler4 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:51 +0200|200|163925|GET /blog/2025/07-14-stability-mountains-for-tendler5 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:54 +0200|200|186668|GET /blog/2025/07-14-stability-mountains-for-tendler6 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:52:58 +0200|200|214906|GET /blog/2025/07-14-stability-mountains-for-tendler7 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
147.135.128.94|19/Jul/2025:13:53:00 +0200|200|109667|GET /blog/2025/07-14-stability-mountains-for-tischer3 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|klm.l5.ca:80|
17.241.75.159|13/Jul/2025:21:09:13 +0200|200|14206|GET /blog/2013/04-27-brian-koberlein-on-google-astronomy-blog HTTP/1.1|-|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15 (Applebot/0.1; +http://www.apple.com/go/applebot)|klm.us.to:80|
184.73.68.20|15/Jul/2025:21:09:10 +0200|200|22987|GET /blog/2013/09-12-dramatic-faster-sorting-in-linux-using-nsort HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|149.172.93.57:443|on
54.157.84.74|15/Jul/2025:21:09:12 +0200|200|1331|GET /jpilot/blog/1999-05-23-this-site-goes-live HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|klm.ddns.net:443|on
34.227.234.246|15/Jul/2025:21:09:14 +0200|200|17473|GET /blog/2023/06-27-performance-remarks-on-publicomag-website HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|149.172.93.57:443|on
18.205.91.101|15/Jul/2025:21:09:17 +0200|200|14573|GET /music/2024/01-12-music-from-ricardo-castro HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|eklausmeier.mywire.org:443|on
3.208.146.193|15/Jul/2025:21:09:18 +0200|200|1419|GET /jpilot/blog/2021-04-02-fixed-bugs-and-crashes HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|149.172.93.57:443|on
23.23.180.225|16/Jul/2025:21:09:18 +0200|200|14201|GET /blog/2017 HTTP/1.1|-|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36|klm.ix.tc:80|
194.233.171.60|17/Jul/2025:21:09:16 +0200|200|2184498|GET /feed.xml HTTP/2.0|-|Mozilla/5.0 (compatible; Miniflux/v2.2.10; +https://miniflux.app)|eklausmeier.goip.de:443|on
65.21.149.1|18/Jul/2025:21:09:16 +0200|200|2109321|GET /feed.xml HTTP/2.0|-|Mozilla/5.0 (compatible; Miniflux/v2.2.10; +https://miniflux.app)|eklausmeier.goip.de:443|on
80.71.159.94|19/Jul/2025:21:09:16 +0200|200|2109370|GET /feed.xml HTTP/2.0|-|Mozilla/5.0 (compatible; Miniflux/v2.2.10; +https://miniflux.app)|eklausmeier.goip.de:443|on
135.181.79.106|20/Jul/2025:04:17:51 +0200|200|214928|GET /blog/2025/07-14-stability-mountains-for-tendler7 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|eklausmeier.mywire.org:443|on
135.181.79.106|20/Jul/2025:04:17:54 +0200|200|109676|GET /blog/2025/07-14-stability-mountains-for-tischer3 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|eklausmeier.mywire.org:443|on
135.181.79.106|20/Jul/2025:04:17:56 +0200|200|131549|GET /blog/2025/07-14-stability-mountains-for-tischer4 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|eklausmeier.mywire.org:443|on
135.181.79.106|20/Jul/2025:04:17:59 +0200|200|160833|GET /blog/2025/07-14-stability-mountains-for-tischer5 HTTP/1.1|-|Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)|eklausmeier.mywire.org:443|on
This shows that quick successions of reads can trigger a slow response.
So, despite the OpCache trickery we have sporadic slow response times.
6. Back to static files. Above results lead to the conclusion that the dynamic mode of Simplified Saaze is not performant enough, when there are many large files involved. The 17 new blog posts about stability mountains triggered this. But any other significant increase in data would have done the same. So, my various static blogs are converted to static.
B. Deployment scripts for static sites
Below are the very simple shell scripts to deploy each of the above static sites.
The time
command included in those scripts is only for measuring how long it takes.
1. My blog.
#!/bin/bash
# Automate the steps to deploy content from Saaze to my eklausmeier.goip.de blog
cd /home/klm/php/sndsaaze
time php saaze -mortb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=en
ln -s blog/index.html
cd /srv/http
rm -rf oldblogs/klmblog/
mkdir -p oldblogs/klmblog
mv 404.html aux blog feed.xml gallery index.html music pagefind sitemap.html sitemap.xml oldblogs/klmblog/
cd /tmp/build
mv 404.html aux blog feed.xml gallery index.html music pagefind sitemap.html sitemap.xml /srv/http/
2. J-Pilot.
#!/bin/bash
# Deploy J-Pilot website
cd /home/klm/php/saaze-jpilot
time RBASE="/jpilot" php saaze -morb /tmp/build
cd /tmp/build
cp -pr /home/klm/php/saaze-jpilot/public/jpilot.css .
cp -pr /home/klm/php/saaze-jpilot/public/img .
rm index.html
ln -s blog/index.html
cd /srv/http/
rm -rf oldblogs/jpilot/
mv jpilot/ oldblogs/
mv /tmp/build/ jpilot
3. Koehntopp.
#!/bin/bash
# Deploy Kristian Koehntopp's blog
cd /home/klm/php/saaze-koehntopp/
time RBASE="/koehntopp" php saaze -mrtb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=en
cd /srv/http/
rm -rf oldblogs/koehntopp/
mv koehntopp/ oldblogs/
mv /tmp/build koehntopp
4. Lemire.
#!/bin/bash
# Deploy Daniel Lemire's blog, assuming static build is in /tmp/build
cd /home/klm/php/saaze-lemire/
time RBASE="/lemire" php saaze -rmb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=en
cp -pr /home/klm/php/saaze-lemire/public/jscss .
ln -s blog/index.html
cd /srv/http
rm -rf oldblogs/lemire/
mkdir -p oldblogs/lemire
mv lemire oldblogs/
mv /tmp/build/ /srv/http/lemire
5. Mobility.
#!/bin/bash
# Deploy Mobility website
cd /home/klm/php/saaze-mobility
time RBASE="/mobility" php saaze -morb /tmp/build
cd /tmp/build
cp -pr /home/klm/php/saaze-mobility/public/img .
cd /srv/http/
rm -rf oldblogs/mobility/
mv mobility/ oldblogs/
mv /tmp/build/ mobility
6. Myra West.
#!/bin/bash
# Deploy Myra West's blog
cd /home/klm/php/saaze-myrawest
time RBASE="/myrawest" php saaze -morb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=en
cp -pr /home/klm/php/saaze-myrawest/public/img .
cd /srv/http/
rm -rf oldblogs/myrawest/
mv myrawest/ oldblogs/
mv /tmp/build/ myrawest
7. Nukeklaus.
#!/bin/bash
# Deploy nukeklaus's blog: build directory is in /tmp/build (for speed reasons)
cd /home/klm/php/saaze-nukeklaus/
time RBASE="/nukeklaus" php saaze -mortb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=de
cp -pr /home/klm/php/saaze-nukeklaus/public/img/ .
cd /srv/http
rm -rf oldblogs/nukeklaus/
mv nukeklaus/ oldblogs/
mv /tmp/build/ /srv/http/nukeklaus
8. Panorama.
#!/bin/bash
# Deploy Panorama's restaurant blog
cd /home/klm/php/saaze-panorama
time RBASE="/panorama" php saaze -morb /tmp/build
cd /tmp/build
cp -pr /home/klm/php/saaze-panorama/public/img .
cd /srv/http
rm -rf oldblogs/panorama
mv panorama/ oldblogs/
mv /tmp/build/ panorama
9. Paternoster.
#!/bin/bash
# Deploy Paternoster's blog
cd /home/klm/php/saaze-paternoster
time RBASE="/paternoster" php saaze -morb /tmp/build
cd /tmp/build
cp -pr /home/klm/php/saaze-paternoster/public/img .
cp -pr /home/klm/php/saaze-paternoster/public/paternoster.css .
ln -s posts/index.html
cd /srv/http
rm -rf oldblogs/paternoster
mv paternoster/ oldblogs/
mv /tmp/build/ /srv/http/paternoster
10. Saaze-Example.
#!/bin/bash
# Deploy Saaze-Example
cd /home/klm/php/saaze-example
time RBASE="/saaze-example" php saaze -b /tmp/build
cd /tmp/build
rm index.html
ln -s blog/index.html
cp -p /home/klm/php/saaze-example/public/blogklm.css .
cd /srv/http/
rm -rf oldblogs/saaze-example/
mv saaze-example/ oldblogs/
mv /tmp/build/ saaze-example
11. Vonhoff.
#!/bin/bash
# Deploy Dr. Rolf Vonhoff's blog: build directory is in /tmp/build (for speed reasons)
cd /home/klm/php/saaze-vonhoff/
time RBASE="/vonhoff" php saaze -b /tmp/build
cp -pr public/img/ /tmp/build/
cd /srv/http
rm -rf oldblogs/vonhoff/
mv vonhoff/ oldblogs/
mv /tmp/build/ vonhoff
12. Wendt.
#!/bin/bash
# Deploy Wendt
cd /home/klm/php/saaze-wendt
time RBASE="/wendt" php saaze -morb /tmp/build
cd /tmp/build
time pagefind -s . --exclude-selectors aside --exclude-selectors footer --force-language=de
cd /srv/http/
rm -rf oldblogs/wendt/
mv wendt/ oldblogs/
mv /tmp/build/ wendt
13. All munged together.
#!/bin/bash
# Do all the blog deployments
time blogdeploy
time blogjpilotDeploy
time blogkoehntoppDeploy
time bloglemireDeploy
time blogmobilityDeploy
time blogmyrawestDeploy
time blognukeklausDeploy
time blogpanoramaDeploy
time blogpaternosterDeploy
time blogsaazeexampleDeploy
time blogvonhoffDeploy
time blogwendtDeploy
C. Running deployment scripts
Running the munged together scripts.
Runtime environment is Arch Linux as depicted in below table.
Type | Value |
---|---|
CPU | AMD Ryzen 7 5700G |
RAM | 64 GB |
OS | 6.15.7-arch1-1 #1 SMP PREEMPT_DYNAMIC |
PHP with JIT | PHP 8.4.10 (cli), Zend Engine v4.4.10 with Zend OPcache v8.4.10 |
Simplified Saaze | Version 2.5, 02-Dec-2024 |
1. My blog.
$ time blogAllDeploy | tee /tmp/blogAllDeploy.log
Building static site in /tmp/build...
execute(): filePath=./content/error.yml, nSIentries=0, totalPages=0, entries_per_page=20
execute(): filePath=./content/music.yml, nSIentries=89, totalPages=5, entries_per_page=20
execute(): filePath=./content/gallery.yml, nSIentries=16, totalPages=1, entries_per_page=20
execute(): filePath=./content/blog.yml, nSIentries=528, totalPages=27, entries_per_page=20
execute(): filePath=./content/aux.yml, nSIentries=8, totalPages=1, entries_per_page=20
Finished creating 5 collections, 4 with index, and 676 entries (0.69 secs / 191.97MB)
#collections=5, parseEntry=0.0310/676-5, md2html=0.1314, toHtml=0.1347/676, renderEntry=0.1374/676, renderCollection=0.0040/38, content=676/0
real 0m0.718s
user 0m0.532s
sys 0m0.184s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 715 files matching **/*.{html}
[Parsing files]
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
[Reading languages]
Discovered 1 language: en
[Building search indexes]
Total:
Indexed 1 language
Indexed 715 pages
Indexed 51664 words
Indexed 0 filters
Indexed 0 sorts
Finished in 3.101 seconds
real 0m3.146s
user 0m2.731s
sys 0m0.552s
real 0m4.119s
user 0m3.274s
sys 0m0.977s
2. J-Pilot.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-jpilot/content/pages.yml, nSIentries=0, totalPages=0, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-jpilot/content/doc.yml, nSIentries=10, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-jpilot/content/blog.yml, nSIentries=18, totalPages=1, entries_per_page=20
Finished creating 3 collections, 3 with index, and 29 entries (0.01 secs / 1.2MB)
#collections=3, parseEntry=0.0005/29-3, md2html=0.0008, toHtml=0.0006/29, renderEntry=0.0008/29, renderCollection=0.0002/5, content=29/0
real 0m0.033s
user 0m0.018s
sys 0m0.014s
real 0m0.051s
user 0m0.022s
sys 0m0.029s
3. Koehntopp.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-koehntopp/content/aux.yml, nSIentries=3, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-koehntopp/content/posts.yml, nSIentries=964, totalPages=49, entries_per_page=20
Finished creating 2 collections, 2 with index, and 967 entries (0.16 secs / 23.82MB)
#collections=2, parseEntry=0.0187/967-2, md2html=0.0267, toHtml=0.0142/967, renderEntry=0.0248/967, renderCollection=0.0098/52, content=967/0
real 0m0.186s
user 0m0.133s
sys 0m0.052s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 1019 files matching **/*.{html}
[Parsing files]
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
[Reading languages]
Discovered 1 language: en
[Building search indexes]
Total:
Indexed 1 language
Indexed 1019 pages
Indexed 58550 words
Indexed 0 filters
Indexed 0 sorts
Finished in 4.394 seconds
real 0m4.459s
user 0m3.863s
sys 0m0.738s
real 0m4.880s
user 0m4.010s
sys 0m1.006s
4. Lemire.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-lemire/content/blog.yml, nSIentries=2771, totalPages=139, entries_per_page=20
Finished creating 1 collections, 1 with index, and 4483 entries (0.52 secs / 95.75MB)
#collections=1, parseEntry=0.0673/4483-1, md2html=0.0904, toHtml=0.0576/4483, renderEntry=0.0563/4483, renderCollection=0.0128/140, content=4483/0
real 0m0.552s
user 0m0.358s
sys 0m0.190s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 4623 files matching **/*.{html}
[Parsing files]
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
[Reading languages]
Discovered 1 language: en
[Building search indexes]
Total:
Indexed 1 language
Indexed 4623 pages
Indexed 60265 words
Indexed 0 filters
Indexed 0 sorts
Finished in 11.176 seconds
real 0m11.351s
user 0m10.005s
sys 0m1.936s
real 12.96s
user 10.42s
sys 0
swapped 0
total space 0
5. Mobility.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-mobility/content/auxil.yml, nSIentries=21, totalPages=2, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-mobility/content/blog.yml, nSIentries=9, totalPages=1, entries_per_page=20
Finished creating 2 collections, 1 with index, and 31 entries (0.01 secs / 1.41MB)
#collections=2, parseEntry=0.0006/31-2, md2html=0.0008, toHtml=0.0014/31, renderEntry=0.0010/31, renderCollection=0.0002/2, content=31/0
real 0m0.033s
user 0m0.018s
sys 0m0.015s
real 0m0.068s
user 0m0.019s
sys 0m0.049s
6. Myra West.
Building static site in /tmp/build...
execute(): filePath=./content/blog.yml, nSIentries=40, totalPages=2, entries_per_page=20
Finished creating 1 collections, 1 with index, and 41 entries (0.01 secs / 3.07MB)
#collections=1, parseEntry=0.0009/41-1, md2html=0.0016, toHtml=0.0017/41, renderEntry=0.0018/41, renderCollection=0.0002/3, content=41/0
real 0m0.036s
user 0m0.023s
sys 0m0.013s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 45 files matching **/*.{html}
[Parsing files]
1 page found without an <html> element.
Pages without an outer <html> element will not be processed by default.
If adding this element is not possible, use the root selector config to target a different root element.
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
* "/sitemap.html" has no <html> element
[Reading languages]
Discovered 1 language: en
[Building search indexes]
Total:
Indexed 1 language
Indexed 44 pages
Indexed 3158 words
Indexed 0 filters
Indexed 0 sorts
Finished in 0.236 seconds
real 0m0.244s
user 0m0.212s
sys 0m0.039s
real 0m0.305s
user 0m0.238s
sys 0m0.074s
7. Nukeklaus.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-nukeklaus/content/aux.yml, nSIentries=4, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-nukeklaus/content/blog.yml, nSIentries=164, totalPages=9, entries_per_page=20
Finished creating 2 collections, 1 with index, and 178 entries (0.05 secs / 11.2MB)
#collections=2, parseEntry=0.0048/178-2, md2html=0.0055, toHtml=0.0063/178, renderEntry=0.0110/178, renderCollection=0.0013/10, content=178/0
real 0m0.079s
user 0m0.055s
sys 0m0.023s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 189 files matching **/*.{html}
[Parsing files]
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
[Reading languages]
Discovered 1 language: de
[Building search indexes]
Total:
Indexed 1 language
Indexed 189 pages
Indexed 23135 words
Indexed 0 filters
Indexed 0 sorts
Finished in 1.556 seconds
real 0m1.578s
user 0m1.370s
sys 0m0.225s
real 0m1.715s
user 0m1.430s
sys 0m0.301s
8. Panorama.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-panorama/content/auxil.yml, nSIentries=2, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-panorama/content/blog.yml, nSIentries=8, totalPages=1, entries_per_page=20
Finished creating 2 collections, 2 with index, and 10 entries (0.01 secs / 745kB)
#collections=2, parseEntry=0.0002/10-2, md2html=0.0002, toHtml=0.0013/10, renderEntry=0.0006/10, renderCollection=0.0001/4, content=10/0
real 0m0.030s
user 0m0.017s
sys 0m0.013s
real 0m0.068s
user 0m0.021s
sys 0m0.046s
9. Paternoster.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-paternoster/content/aux.yml, nSIentries=3, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-paternoster/content/posts.yml, nSIentries=210, totalPages=11, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-paternoster/content/notes.yml, nSIentries=470, totalPages=10, entries_per_page=50
execute(): filePath=/home/klm/php/saaze-paternoster/content/links.yml, nSIentries=249, totalPages=5, entries_per_page=50
Finished creating 4 collections, 4 with index, and 932 entries (0.08 secs / 6.56MB)
#collections=4, parseEntry=0.0149/936-4, md2html=0.0074, toHtml=0.0041/932, renderEntry=0.0113/932, renderCollection=0.0044/31, content=932/0
real 0m0.105s
user 0m0.065s
sys 0m0.040s
real 0m0.241s
user 0m0.073s
sys 0m0.166s
10. Saaze-Example.
Building static site in /srv/http/saaze-example...
execute(): filePath=/home/klm/php/saaze-example/content/auxil.yml, nSIentries=0, totalPages=0, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-example/content/music.yml, nSIentries=11, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-example/content/blog.yml, nSIentries=35, totalPages=2, entries_per_page=20
Finished creating 3 collections, 3 with index, and 47 entries (0.01 secs / 882kB)
#collections=3, parseEntry=0.0006/47-3, md2html=0.0003, toHtml=0.0006/47, renderEntry=0.0007/47, renderCollection=0.0005/6, content=47/0
real 0m0.035s
user 0m0.021s
sys 0m0.013s
real 0.04s
user 0.02s
sys 0
swapped 0
total space 0
11. Vonhoff.
Building static site in /tmp/build...
execute(): filePath=/home/klm/php/saaze-vonhoff/content/blog.yml, nSIentries=4, totalPages=1, entries_per_page=20
execute(): filePath=/home/klm/php/saaze-vonhoff/content/auxil.yml, nSIentries=1, totalPages=1, entries_per_page=20
Finished creating 2 collections, 2 with index, and 5 entries (0.01 secs / 745kB)
#collections=2, parseEntry=0.0001/5-2, md2html=0.0004, toHtml=0.0030/5, renderEntry=0.0004/5, renderCollection=0.0002/4, content=5/0
real 0m0.030s
user 0m0.018s
sys 0m0.012s
real 0m0.043s
user 0m0.019s
sys 0m0.024s
12. Wendt.
Building static site in /tmp/build...
execute(): filePath=./content/alexander.yml, nSIentries=770, totalPages=39, entries_per_page=20
execute(): filePath=./content/alte-weise.yml, nSIentries=131, totalPages=7, entries_per_page=20
execute(): filePath=./content/archi-bechlenberg.yml, nSIentries=5, totalPages=1, entries_per_page=20
execute(): filePath=./content/bernd-zeller.yml, nSIentries=332, totalPages=17, entries_per_page=20
execute(): filePath=./content/cora-stephan.yml, nSIentries=1, totalPages=1, entries_per_page=20
execute(): filePath=./content/david-berger.yml, nSIentries=1, totalPages=1, entries_per_page=20
execute(): filePath=./content/fake-news.yml, nSIentries=28, totalPages=2, entries_per_page=20
execute(): filePath=./content/film.yml, nSIentries=1, totalPages=1, entries_per_page=20
execute(): filePath=./content/hansjoerg-mueller.yml, nSIentries=2, totalPages=1, entries_per_page=20
execute(): filePath=./content/hausbesuch.yml, nSIentries=2, totalPages=1, entries_per_page=20
execute(): filePath=./content/joerg-friedrich.yml, nSIentries=2, totalPages=1, entries_per_page=20
execute(): filePath=./content/mag.yml, nSIentries=1235, totalPages=62, entries_per_page=20
execute(): filePath=./content/matthias-matussek.yml, nSIentries=1, totalPages=1, entries_per_page=20
execute(): filePath=./content/medien-kritik.yml, nSIentries=123, totalPages=7, entries_per_page=20
execute(): filePath=./content/politik-gesellschaft.yml, nSIentries=486, totalPages=25, entries_per_page=20
execute(): filePath=./content/redaktion.yml, nSIentries=112, totalPages=6, entries_per_page=20
execute(): filePath=./content/samuel-horn.yml, nSIentries=3, totalPages=1, entries_per_page=20
execute(): filePath=./content/spreu-weizen.yml, nSIentries=596, totalPages=30, entries_per_page=20
execute(): filePath=./content/wolfram-ackner.yml, nSIentries=6, totalPages=1, entries_per_page=20
Finished creating 19 collections, 19 with index, and 1248 entries (5.16 secs / 1.82GB)
#collections=19, parseEntry=0.7782/23712-19, md2html=1.2083, toHtml=1.2890/23712, renderEntry=0.1177/1248, renderCollection=0.0420/224, content=23712/0
real 0m5.193s
user 0m4.426s
sys 0m0.734s
Running Pagefind v1.3.0
Running from: "/tmp/build"
Source: ""
Output: "pagefind"
[Walking source directory]
Found 1473 files matching **/*.{html}
[Parsing files]
Did not find a data-pagefind-body element on the site.
↳ Indexing all <body> elements on the site.
[Reading languages]
Discovered 1 language: de
[Building search indexes]
Total:
Indexed 1 language
Indexed 1473 pages
Indexed 133262 words
Indexed 0 filters
Indexed 0 sorts
Finished in 18.948 seconds
real 0m19.155s
user 0m17.431s
sys 0m1.890s
real 0m24.780s
user 0m21.878s
sys 0m3.027s
The total runtime.
real 49.78s
user 41.82s
sys 0
swapped 0
total space 0
real 49.77s
user 0.00s
sys 0
swapped 0
total space 0
D. NGINX configuration
All the rewrite
rules, which were in place to generate HTML on the fly, can now be removed.
I added one rewrite
rule to not to redirect to the URL with a slash appended.
This spares one roundtrip due to a HTTP 301 return code.
# NGINX configuration for chieftec
# Elmar Klausmeier, 22-Aug-2023
# Elmar Klausmeier, 21-Jul-2025
#user http;
worker_processes 1;
error_log /var/log/nginx/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so;
load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;
events {
worker_connections 1024;
}
http {
root /srv/http;
index index.html;
client_max_body_size 15900M;
http2 on;
gzip on;
brotli on;
brotli_comp_level 10;
brotli_types application/xml image/svg+xml text/css text/csv text/javascript text/markdown text/plain text/vcard text/xml application/rss+xml;
gzip_types application/xml image/svg+xml text/css text/csv text/javascript text/markdown text/plain text/vcard text/xml application/rss+xml;
include 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"';
log_format hiawatha_format '$remote_addr|$time_local|$status|$bytes_sent|$request|$http_referer|$http_user_agent|$host:$server_port|$https';
access_log /var/log/nginx/access.log hiawatha_format;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
http3 on;
http3_hq on;
types_hash_max_size 4096;
server {
listen 80;
server_name localhost;
# Umkehrung von https://timmehosting.de/blog/nginx-trailing-slash-zu-urls-hinzufuegen-rewrite-rule
rewrite "^/(aux|blog|music|gallery|jpilot|koehntopp|lemire|mobility|myrawest|nukeklaus|panorama|paternoster|saaze-example|vonhoff|wendt)(/*|[^\.]*[^/])$" "/$1/$2/index.html" last;
#charset koi8-r;
#error_page 404 /rewrite/sndsaaze/public/index.php?/404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
location ~ \.(jpg|jpeg|png|webp|mp4|pdf)$ {
add_header Cache-Control "public, max-age=7776000";
}
location ~ \.php$ {
try_files $fastcgi_script_name =404;
# default fastcgi_params
include fastcgi_params;
# fastcgi settings
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
#fastcgi_index index.php;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
# fastcgi params
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ ^/ttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://eklausmeier.goip.de:7681/$1;
#proxy_pass http://84.119.108.23:7681/$1;
}
location ~ ^/nucttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://192.168.0.24:7681/$1;
}
}
# HTTPS server
#
server {
listen 443 quic;
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/letsencrypt/live/eklausmeier.goip.de-0002/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/eklausmeier.goip.de-0002/privkey.pem;
# From https://blog.qualys.com/product-tech/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#location / {
# used to advertise the availability of HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
#}
# Umkehrung von https://timmehosting.de/blog/nginx-trailing-slash-zu-urls-hinzufuegen-rewrite-rule
rewrite "^/(aux|blog|music|gallery|jpilot|koehntopp|lemire|mobility|myrawest|nukeklaus|panorama|paternoster|saaze-example|vonhoff|wendt)(/*|[^\.]*[^/])$" "/$1/$2/index.html" last;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.(jpg|jpeg|png|webp|mp4|pdf)$ {
add_header Cache-Control "public, max-age=7776000";
}
location ~ \.php$ {
try_files $fastcgi_script_name =404;
# default fastcgi_params
include fastcgi_params;
# fastcgi settings
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
#fastcgi_index index.php;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
# fastcgi params
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ ^/ttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://eklausmeier.goip.de:7681/$1;
}
location ~ ^/nucttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://192.168.0.24:7681/$1;
}
}
}