, 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

1. File processing in Simplified Saaze. Simplified Saaze is written in PHP. It does the following for every Markdown file:

  1. reads collection file with file_get_contents()
  2. recursively traverses the directory for all Markdown files with scandir()
  3. read entire file with file_get_contents()
  4. parses the file with yaml_parse()
  5. file content is checked against keywords (like '[youtube]' or $$ for math)
  6. content is then converted to HTML using MD4C
  7. excerpt is computed from HTML using strip_tags()
  8. content is word counted on original Markdown
  9. the (output) template PHP is run on the HTML, which in turn calls eval() on the entire HTML
  10. 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:

  1. speed of static files served by the web-server
  2. 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:

  1. my own blog
  2. J-Pilot
  3. Koehntop
  4. Lemire
  5. Mobility
  6. Myra West
  7. Nukeklaus
  8. Panorama
  9. Paternoster
  10. Saaze-Example
  11. Vonhoff
  12. 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:

Photo

The load time was larger than five seconds:

Photo

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;
        }
    }

}