Sibiu Independent — Stack Migration

May 2026

Migration of sibiuindependent.ro from Debian 12 + AApanel to FreeBSD 14.x minimal stack.


Current Architecture

[internet] → [Nginx Proxy Manager] → [dracula, Tailscale] → [transilvan, Tailscale]
Node Role OS Specs
dracula Web Debian 12 16 vCPU, 32GB RAM, 245GB disk
transilvan Database Debian 12 4 vCPU, 32GB RAM, 245GB disk

Both nodes are VMs running on separate bare-metal hosts within the same datacenter (~1ms RTT). All inter-service traffic runs over Tailscale (WireGuard). The NPM node handles TLS termination and public exposure. The web and DB nodes have no public-facing ports other than what Tailscale exposes.


Current Stack Audit

dracula (web)

Component Version / Detail
OS Debian GNU/Linux 12 (bookworm)
Web server nginx 1.30.1
PHP 8.5.2 (AApanel custom build) + FPM
CMS WordPress, single site: sibiuindependent.ro
Theme Newspaper / TagDiv (td-cloud-library, td-composer, td-standard-pack)
Caching W3 Total Cache + Memcached (page + object cache)
Sessions PHP file sessions
Media wp-content/uploads 6.5GB on disk, S3-compatible offload active
Cache on disk 4.8GB (W3TC page cache — throwaway)
WP code ~700MB (core + plugins + themes)
DB connection DB_HOST=transilvan resolved via /etc/hosts → Tailscale IP
Monitoring Telegraf, Beszel agent, Wazuh agent, Five Nines agent
Security Fail2ban, SSHGuard, sshguard
Mail Postfix + MTA-STS (SMTP plugin used, local MTA not needed)
Panel AApanel (btpanel)
Tailscale IP 100.99.157.56

Active PHP extensions: bcmath, curl, exif, fileinfo, gd, imagick, intl, json, mbstring, memcached, mysqli, mysqlnd, opcache, openssl, pcntl, pdo, pdo_mysql, pdo_sqlite, posix, soap, sockets, sodium, xml, xmlreader, xmlwriter, xsl, zip

transilvan (db)

Component Version / Detail
OS Debian GNU/Linux 12 (bookworm)
Database MariaDB 10.11.14 LTS
Database name sql_sibiuindepen
DB user sql_sibiuindepen
Data directory /www/server/data (~9.6GB raw)
Config /etc/mysql/mariadb.cnf + conf.d/
Monitoring Telegraf, Beszel agent, Wazuh agent, Five Nines agent
Panel AApanel (btpanel)
Binlog Active (mysql-bin.*, high sequence numbers)

OS Evaluation

Candidates assessed: OpenBSD, FreeBSD, Gentoo, Alpine Linux

OpenBSD

  • Security: Best in class — pledge(2), unveil(2), W^X enforced, secure malloc, pf
  • PHP 8.5: Not in packages (ports have 8.4). Source build possible but complex dependency tree
  • php-memcached: Problematic (libmemcached issues on OpenBSD)
  • MySQL 8.x: Not in packages (MariaDB only)
  • PHP JIT: Conflicts with W^X; must disable (negligible for WordPress)
  • Monitoring agents: Wazuh: no official support. Telegraf: community build
  • Verdict: Excellent security philosophy, but too much friction for this specific stack (PHP 8.5, MySQL 8.x, imagick). Viable only if stripping monitoring stack and accepting PHP 8.4 + Redis instead of memcached

FreeBSD

  • Security: pf, Capsicum, jails, strong defaults
  • PHP 8.5: Available in ports (lang/php85). All extensions in ports
  • MySQL 8.4 LTS: Available as databases/mysql84-server
  • imagick, intl, soap, redis: All in ports
  • ZFS: Native. Game-changer for database node (atomic snapshots, checksumming, lz4 compression)
  • Monitoring: Telegraf official package. Tailscale official package. Beszel: Go binary works
  • Wazuh: Community source build only — dropped per requirements
  • Verdict: Strongest all-round candidate. PHP 8.5 + all extensions + MySQL 8.4 + ZFS + pf + official Tailscale

Gentoo

  • Minimalism: Genuine — USE flags compile exactly what is needed, OpenRC available
  • PHP 8.5: In portage (rolling release guarantees latest)
  • MySQL 8.4: In portage
  • All agents: Still Linux, everything works natively
  • Maintenance: High ongoing cost — every update is a compile run. Production liability
  • Verdict: Philosophically compelling, operationally expensive for a production news site

Alpine Linux

  • Minimalism: musl libc + busybox, ~130MB base
  • PHP 8.5: Community repos, most extensions packaged
  • Init: OpenRC, no systemd
  • Migration effort: Lowest — still Linux, apk replaces apt, paths similar
  • Verdict: Best minimal Linux option; easier than FreeBSD but fewer operational benefits (no ZFS, no jails)

Decision: FreeBSD 14.x on both nodes

Tiebreaker: ZFS native on the DB node. zfs snapshot before any migration step, before any MySQL upgrade, before any schema change — this alone justifies the choice. PHP 8.5 builds from ports cleanly. MySQL 8.4 LTS is in pkg. All required PHP extensions are in ports. pf is simpler and more powerful than nftables for these firewall rules. Tailscale has official FreeBSD support.


Target Architecture

[internet] → [NPM node, Tailscale] → [dracula-new FreeBSD, Tailscale] → [transilvan-new FreeBSD, Tailscale]

dracula-new (FreeBSD 14.x)            transilvan-new (FreeBSD 14.x)
──────────────────────────            ─────────────────────────────
nginx (pkg)                           ZFS pool → /var/db/mysql
PHP 8.5 (ports build)                   recordsize=16K (InnoDB optimal)
  gd, imagick, intl, mbstring,           compression=lz4
  opcache, pdo_mysql, redis,             primarycache=metadata
  soap, sockets, sodium,              MySQL 8.4 LTS (ports)
  xml, zip, exif, fileinfo            pf: 3306 from dracula Tailscale IP only
nginx fastcgi_cache (filesystem)      Tailscale
Redis (pkg, unix socket)              SSH on Tailscale only
pf: 80 from NPM Tailscale IP only     Telegraf, Beszel
Tailscale
SSH on Tailscale only
Telegraf, Beszel

What is dropped

Component Reason
AApanel / btpanel Replaced by direct config management
W3 Total Cache Replaced by nginx fastcgi_cache + Redis Object Cache plugin
Memcached Replaced by Redis (unix socket, faster, simpler)
Fail2ban / SSHGuard No public SSH port, Tailscale-only
Postfix / sendmail SMTP plugin in use, no local MTA needed
Wazuh agent Not mandatory per requirements
Five Nines agent Not mandatory per requirements
memory-cleanup.sh Linux /proc hack, was on wrong server anyway
ngxblocker Not applicable to new stack
AApanel cron scripts acme renewal, site_total, backup.py — all panel-specific

Caching architecture change

Layer Old New
Page cache W3TC on-disk (4.8GB, PHP-generated) nginx fastcgi_cache (filesystem, PHP bypassed entirely on HIT)
Object cache W3TC + Memcached (TCP) Redis Object Cache plugin + Redis (unix socket)
Bytecode cache OPcache OPcache (unchanged, JIT disabled — no benefit for WordPress)
Sessions PHP file sessions Redis via session.save_path unix socket

Migration Data Inventory

Component Size Action
WordPress core + plugins + themes ~700MB rsync
wp-content/uploads 6.5GB rsync then cleanup (S3 is source of truth)
wp-content/cache 4.8GB Skip — regenerates automatically
w3tc-config 40KB Skip — W3TC removed
Database (sql_sibiuindepen) 9.6GB raw / ~3-4GB compressed dump mysqldump → import

Cron Inventory

Kept and migrated

Schedule Script Notes
*/5 * * * * wp-cron.php trigger Unchanged
0 3 * * * cache-warmer.sh Rewritten for nginx fastcgi_cache
0 15 * * * cache-warmer.sh Rewritten for nginx fastcgi_cache
0 3 * * 1 JS asset refresh (OneSignal, FB SDKs) Unchanged

Dropped

Script Reason
ngxblocker update Not using ngxblocker
analyze-nginx-attacks.sh Panel-specific monitoring
analyze-php-slow.sh Panel-specific monitoring
monitor-php-fpm.sh Panel-specific monitoring
memory-cleanup.sh Linux /proc, wrong server
AApanel acme_v2.py Panel SSL renewal
AApanel site_total_check.py Panel metric
AApanel backup.py Replaced by custom mysqldump script

New on new stack

Schedule Script
0 2 * * * mysqldump → /var/backups + optional S3 upload
0 0 * * 0 ZFS snapshot rotation (keep last 4 weeks)

Phase 0 — Preparation (live servers, non-disruptive)

# Capture configs before touching anything
scp -i /root/.ssh/id_ed25519 -P 79 \
  root@dracula:/www/wwwroot/sibiuindependent.ro/wp-config.php ./wp-config.php.bak

scp -i /root/.ssh/id_ed25519 -P 79 \
  root@dracula:/root/cache-warmer.sh ./cache-warmer.sh.original

# Test dump on transilvan — confirms dump works before migration day
ssh -i /root/.ssh/id_ed25519 -p 79 root@transilvan \
  "mysqldump -S /tmp/mysqld.sock --single-transaction \
   --routines --triggers --events sql_sibiuindepen \
   | gzip > /tmp/testdump.sql.gz && ls -lh /tmp/testdump.sql.gz"

# Record current NPM upstream for rollback reference
# Old dracula Tailscale IP: 100.99.157.56

Phase 1 — DB Node (transilvan-new)

1.1 FreeBSD base + ZFS

# During FreeBSD 14.x install: enable ZFS, create OS pool on first disk
# Add second disk for database isolation after install

zpool create -o ashift=12 data /dev/da1
zfs create -o mountpoint=/var/db/mysql data/mysql
zfs set recordsize=16K data/mysql        # InnoDB optimal block size
zfs set primarycache=metadata data/mysql # InnoDB manages its own buffer pool
zfs set compression=lz4 data/mysql       # free performance, transparent

1.2 MySQL 8.4 LTS

pkg install mysql84-server
sysrc mysql_enable=YES
sysrc mysql_dbdir=/var/db/mysql

/usr/local/etc/mysql/my.cnf:

[mysqld]
bind-address             = <transilvan-new Tailscale IP>
datadir                  = /var/db/mysql
socket                   = /tmp/mysql.sock
innodb_buffer_pool_size  = 24G
innodb_log_file_size     = 1G
innodb_flush_log_at_trx_commit = 1
innodb_flush_method      = O_DIRECT
max_connections          = 150
character-set-server     = utf8mb4
collation-server         = utf8mb4_unicode_ci
skip-name-resolve

1.3 pf firewall

/etc/pf.conf:

ext_if = "vtnet0"
ts_if  = "tailscale0"
web_ts = "<dracula-new Tailscale IP>"

set skip on lo0
block all
pass in  on $ts_if proto tcp from $web_ts to any port 3306
pass in  on $ts_if proto tcp to any port 22
pass out all
sysrc pf_enable=YES
service pf start

1.4 Tailscale

pkg install tailscale
sysrc tailscaled_enable=YES
service tailscaled start
tailscale up --hostname=transilvan-new

1.5 DB restore

# Dump from old transilvan, import to new
# Run from a machine with access to both nodes or pipe via SSH
ssh root@transilvan \
  "mysqldump -S /tmp/mysqld.sock --single-transaction \
   --routines --triggers --events sql_sibiuindepen" \
  | ssh root@transilvan-new "mysql -u root sql_sibiuindepen"

# Create app user (use same password as current DB_PASSWORD in wp-config)
mysql -u root -e "
  CREATE USER 'sql_sibiuindepen'@'<dracula-new Tailscale IP>'
    IDENTIFIED BY '<password>';
  GRANT ALL ON sql_sibiuindepen.* TO
    'sql_sibiuindepen'@'<dracula-new Tailscale IP>';
  FLUSH PRIVILEGES;"

1.6 DB backup cron

/root/db-backup.sh:

#!/bin/sh
DATE=$(date +%Y%m%d-%H%M)
DEST=/var/backups
mkdir -p $DEST
mysqldump -u root --single-transaction \
  --routines --triggers --events sql_sibiuindepen \
  | gzip > ${DEST}/db-${DATE}.sql.gz
find $DEST -name "*.sql.gz" -mtime +7 -delete

Crontab:

0 2 * * *  root  /root/db-backup.sh
0 0 * * 0  root  zfs snapshot data/mysql@weekly-$(date +%Y%m%d) && \
                  zfs list -t snapshot -o name | tail -n +6 | xargs -I{} zfs destroy {}

Phase 2 — Web Node (dracula-new)

2.1 Base packages

pkg install nginx redis git curl wget
sysrc nginx_enable=YES redis_enable=YES
service redis start

2.2 PHP 8.5 from ports

portsnap fetch extract
# Or: git clone https://git.FreeBSD.org/ports.git /usr/ports

cd /usr/ports/lang/php85
make config-recursive
# Enable: bcmath curl exif fileinfo gd gettext iconv imagick intl
#         mbstring opcache pcntl pdo pdo_mysql soap sockets sodium
#         xml xmlreader xmlwriter xsl zip
make install clean

# Install extensions individually or as group
pkg install php85-gd php85-imagick php85-intl php85-mbstring \
  php85-mysqli php85-pdo_mysql php85-redis php85-soap \
  php85-sockets php85-sodium php85-xml php85-zip \
  php85-opcache php85-exif php85-fileinfo php85-bcmath \
  php85-curl php85-pcntl php85-posix

pkg install ImageMagick7

sysrc php_fpm_enable=YES

2.3 PHP-FPM pool

/usr/local/etc/php-fpm.d/sibiuindependent.conf:

[sibiuindependent]
user  = www
group = www
listen = /var/run/php-fpm-si.sock
listen.owner = www
listen.group = www
pm                   = dynamic
pm.max_children      = 40
pm.start_servers     = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests      = 500

/usr/local/etc/php/php.ini (relevant):

opcache.enable                = 1
opcache.memory_consumption    = 256
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps   = 0
opcache.jit                   = 0
session.save_handler          = redis
session.save_path             = "unix:///var/run/redis/redis.sock"

2.4 nginx with fastcgi_cache

/usr/local/etc/nginx/nginx.conf:

worker_processes auto;
events { worker_connections 4096; }

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    tcp_nopush    on;
    server_tokens off;

    fastcgi_cache_path /var/cache/nginx/si
        levels=1:2
        keys_zone=si_cache:64m
        max_size=4g
        inactive=24h
        use_temp_path=off;

    fastcgi_cache_key "$scheme$request_method$host$request_uri";

    server {
        listen 80;
        server_name sibiuindependent.ro www.sibiuindependent.ro;
        root /var/www/sibiuindependent.ro;
        index index.php;

        set $skip_cache 0;
        if ($request_method = POST)        { set $skip_cache 1; }
        if ($query_string != "")           { set $skip_cache 1; }
        if ($request_uri ~* "/wp-admin/|/wp-login|/xmlrpc|/feed|sitemap") {
                                             set $skip_cache 1; }
        if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
                                             set $skip_cache 1; }

        location / {
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
            fastcgi_pass   unix:/var/run/php-fpm-si.sock;
            fastcgi_index  index.php;
            include        fastcgi_params;
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

            fastcgi_cache         si_cache;
            fastcgi_cache_valid   200 301 302 24h;
            fastcgi_cache_bypass  $skip_cache;
            fastcgi_no_cache      $skip_cache;
            add_header            X-Cache-Status $upstream_cache_status;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2)$ {
            expires    1y;
            add_header Cache-Control "public, immutable";
            access_log off;
        }
    }
}
mkdir -p /var/cache/nginx/si
chown www:www /var/cache/nginx/si
service nginx start

2.5 pf

/etc/pf.conf:

ext_if = "vtnet0"
ts_if  = "tailscale0"
npm_ts = "<NPM node Tailscale IP>"

set skip on lo0
block all
pass in  on $ts_if proto tcp from $npm_ts to any port 80
pass in  on $ts_if proto tcp to any port 22
pass out all
sysrc pf_enable=YES
service pf start

2.6 Tailscale

pkg install tailscale
sysrc tailscaled_enable=YES
service tailscaled start
tailscale up --hostname=dracula-new

2.7 rsync web files

# Exclude cache — does not migrate, regenerates on first request
rsync -avz --progress \
  -e "ssh -i /root/.ssh/id_ed25519 -p 79" \
  --exclude="wp-content/cache/" \
  --exclude="wp-content/w3tc-config/" \
  root@dracula:/www/wwwroot/sibiuindependent.ro/ \
  /var/www/sibiuindependent.ro/

chown -R www:www /var/www/sibiuindependent.ro

2.8 wp-config.php changes

Two changes on the new node only:

// 1. Update DB_HOST to new transilvan Tailscale IP
define( 'DB_HOST', '<transilvan-new Tailscale IP>' );

// 2. WP_CACHE stays true for Redis Object Cache drop-in
//    Remove the W3TC comment, keep the constant:
define( 'WP_CACHE', true );

Remove W3TC plugin and its drop-ins:

rm -rf /var/www/sibiuindependent.ro/wp-content/plugins/w3-total-cache
rm -f  /var/www/sibiuindependent.ro/wp-content/advanced-cache.php
rm -f  /var/www/sibiuindependent.ro/wp-content/object-cache.php

Install Redis Object Cache plugin (Till Krüss), activate, configure:

// In wp-config.php, before "That's all, stop editing":
define( 'WP_REDIS_PATH', '/var/run/redis/redis.sock' );
define( 'WP_REDIS_SCHEME', 'unix' );

2.9 Crons

*/5  * * * *  www   curl -s -o /dev/null https://sibiuindependent.ro/wp-cron.php?doing_wp_cron
0    3 * * *  root  /root/cache-warmer.sh >> /root/cache-warmer.log 2>&1
0   15 * * *  root  /root/cache-warmer.sh >> /root/cache-warmer.log 2>&1
0    3 * * 1  root  /root/js-refresh.sh >> /root/js-refresh.log 2>&1

Cache Warmer (rewritten for nginx fastcgi_cache)

/root/cache-warmer.sh:

#!/bin/sh
# Incremental cache warmer for nginx fastcgi_cache.
# Checks X-Cache-Status header: skips HITs, warms MISSes.
# Requires curl >= 7.84 (FreeBSD 14 ships curl 8.x — compatible).

SITE='https://sibiuindependent.ro'
CONCURRENCY=4
LOG='/root/cache-warmer.log'
URL_FILE='/tmp/cache-warmer-urls.txt'
DONE_FILE='/tmp/cache-warmer-done.txt'

echo "[$(date)] Starting cache warmer (incremental, nginx fastcgi_cache)" | tee -a "$LOG"

: > "$URL_FILE"
SITEMAPS=$(curl -sk "${SITE}/sitemap_index.xml" | \
  grep -oE 'https?://[^<]+\.xml' | grep -v image)
echo "[$(date)] Sub-sitemaps: $(echo "$SITEMAPS" | wc -l)" | tee -a "$LOG"

for SMAP in $SITEMAPS; do
    curl -sk "$SMAP" | grep -oE 'https?://[^<]+' | grep -v '\.xml' >> "$URL_FILE"
    sleep 0.3
done

TOTAL=$(wc -l < "$URL_FILE")
echo "[$(date)] Total URLs from sitemap: $TOTAL" | tee -a "$LOG"

: > "$DONE_FILE"
WARMED=0
SKIPPED=0
COUNT=0

while IFS= read -r URL; do
    (
        RESULT=$(curl -sk -o /dev/null \
          -w '%{http_code} %header{x-cache-status}' \
          -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' \
          -H 'Accept-Encoding: gzip' \
          --max-time 20 "$URL")
        echo "$RESULT $URL" >> "$DONE_FILE"
    ) &
    COUNT=$((COUNT + 1))
    if [ $((COUNT % CONCURRENCY)) -eq 0 ]; then
        wait
    fi
    sleep 0.05
done < "$URL_FILE"
wait

WARMED=$(grep -vc 'HIT' "$DONE_FILE" 2>/dev/null || echo 0)
SKIPPED=$(grep -c 'HIT' "$DONE_FILE" 2>/dev/null || echo 0)
echo "[$(date)] Done. Warmed: $WARMED, Skipped (HIT): $SKIPPED" | tee -a "$LOG"

Key difference from original

Original (W3TC) New (nginx fastcgi_cache)
Cache check method Stat filesystem for _index_slash_ssl.html HTTP X-Cache-Status: HIT header
Cache path /www/wwwroot/.../cache/page_enhanced/... /var/cache/nginx/si/ (hashed, not human-readable)
Logic Skip if file exists Skip if header is HIT
Concurrency Background subshells Background subshells (unchanged)
Sitemap parsing Unchanged Unchanged

JS Asset Refresh Cron

/root/js-refresh.sh:

#!/bin/sh
# Weekly refresh of self-hosted third-party JS assets
WEBROOT=/var/www/sibiuindependent.ro

curl -s https://cdn.onesignal.com/sdks/OneSignalSDK.js \
  | sed 's|//# sourceMappingURL=.*||' \
  > ${WEBROOT}/onesignal.js

curl -s https://cdn.onesignal.com/sdks/OneSignalPageSDKES6.js \
  | sed 's|//# sourceMappingURL=.*||' \
  > ${WEBROOT}/onesignal-es6.js

curl -s https://connect.facebook.net/en_US/sdk.js > ${WEBROOT}/fb-sdk.js
curl -s https://connect.facebook.net/ro_RO/sdk.js  > ${WEBROOT}/fb-sdk-ro.js

Phase 3 — Zero-downtime Cutover

Pre-cutover checklist (run on dracula-new before touching NPM)

# 1. Site responds via new stack directly
curl -sk -o /dev/null -w "%{http_code}" \
  http://<dracula-new Tailscale IP>/ \
  -H "Host: sibiuindependent.ro"
# Expected: 200

# 2. DB connection live
mysql -h <transilvan-new Tailscale IP> \
  -u sql_sibiuindepen -p sql_sibiuindepen \
  -e "SELECT COUNT(*) FROM wp_posts WHERE post_status='publish';"

# 3. Redis object cache connected
wp --path=/var/www/sibiuindependent.ro --allow-root redis-cache status

# 4. PHP extensions present
php85 -m | grep -E "imagick|redis|pdo_mysql|opcache|intl|soap|sodium"

# 5. nginx fastcgi_cache working — second request should be HIT
curl -sI http://<dracula-new Tailscale IP>/ -H "Host: sibiuindependent.ro" \
  | grep X-Cache-Status
# First request: MISS, second request: HIT

Cutover sequence

T-0   Disable cache warmer crons on OLD dracula (comment out both lines)

T-1   Final rsync of uploads (incremental delta only):
        rsync -avz --progress \
          -e "ssh -i /root/.ssh/id_ed25519 -p 79" \
          --exclude="wp-content/cache/" \
          root@dracula:/www/wwwroot/sibiuindependent.ro/wp-content/uploads/ \
          /var/www/sibiuindependent.ro/wp-content/uploads/

T-2   Final DB dump and import:
        ssh root@transilvan \
          "mysqldump -S /tmp/mysqld.sock --single-transaction \
           --routines --triggers --events sql_sibiuindepen" \
          | ssh root@transilvan-new "mysql -u root sql_sibiuindepen"

T-3   Switch NPM upstream:
        old: 100.99.157.56 (dracula Tailscale IP)
        new: <dracula-new Tailscale IP>
        (single upstream change in NPM — no DNS TTL involved, instant)

T-4   Smoke test:
        - Homepage loads
        - An article page loads
        - wp-admin accessible
        - Second page load shows X-Cache-Status: HIT

T-5   Enable cache warmer on new dracula

Rollback: Revert NPM upstream to 100.99.157.56. Old stack is untouched throughout. No DNS change required. Instant.

Old VMs: Keep powered on (no traffic) for 48 hours. Decommission after confirmed stable.

Post-cutover

# Run cache warmer immediately on new node
bash /root/cache-warmer.sh

# Monitor
tail -f /var/log/nginx/error.log
tail -f /var/log/php-fpm.log

# ZFS snapshot of DB post-migration
zfs snapshot data/mysql@post-migration-$(date +%Y%m%d)

Open Items at Time of Planning

  • Confirm new Tailscale hostnames for both nodes (determines pf rules and wp-config DB_HOST)
  • Confirm NPM node Tailscale IP (determines pf rule on dracula-new)
  • PHP 8.5 from ports — attempt first, fall back to pkg php84 if dependency issue
  • Redis Object Cache plugin — already in plugins dir or fresh install needed
  • W3TC features in use beyond caching: minify? If yes, switch to standalone nginx-based minification or keep W3TC minify only with nginx page cache
  • ZFS pool disk — confirm second disk available on transilvan-new VM
  • MySQL 8.4 vs MariaDB 11.4 LTS — plan targets MySQL 8.4; revisit if preferred
  • Beszel and Telegraf — confirm if keeping on new stack (both have FreeBSD builds)
Description
No description provided
Readme 143 KiB
Languages
Markdown 100%