| WordPress Suitability
| Good for standard installs
| Better for custom kernels/modules
Recommendation: Use LXC for standard WordPress tenants, VMs for tenants requiring custom kernels or specific hardware access.
Proxmox Host Requirements
- Minimum: 32GB RAM, 8-core CPU, SSD storage
- Recommended: 64GB+ RAM, 16+ core CPU, NVMe storage
- Network: Multiple NICs for separation (management, tenant traffic, storage)
Host Hardening
- Regular security updates via
pve-enterprise or no-subscription repository
- Fail2Ban for SSH protection
- SSH key authentication only
- Regular backups of
/etc/pve, /var/lib/vz, and host configuration
2. Network Architecture
Network Segmentation
Proxmox Host
āāā Management Network (10.0.0.0/24) - Host access, updates
āāā Tenant Network (10.0.10.0/24) - WordPress tenant traffic
āāā Storage Network (10.0.20.0/24) - Ceph/NFS/iSCSI storage
āāā DMZ Network (10.0.30.0/24) - Reverse proxy, public-facing
āāā Backup Network (10.0.40.0/24) - Backup traffic isolation
VLAN Configuration
- VLAN 10: Tenant Network
- VLAN 20: Storage Network
- VLAN 30: DMZ Network
- VLAN 40: Backup Network
DHCP & DNS
- Internal DHCP server (dnsmasq or Pi-hole) for tenant networks
- Internal DNS for service discovery (wordpress-tenant1.internal)
- Split-horizon DNS for external vs internal resolution
Firewall Rules
- Proxmox built-in firewall enabled
- Default deny, explicit allow principles
- Specific rules for:
- Management access (SSH, web GUI) from trusted IPs only
- Tenant network to reverse proxy (HTTP/HTTPS)
- Reverse proxy to tenant networks
- Storage network access for backup/storage services
- Monitoring system access to all networks
3. Storage Strategy
ZFS Pool Layout
rpool/
āāā rpool/ROOT/pve-1 (Proxmox OS)
āāā rpool/data
ā āāā vm-100-disk-0
ā āāā vm-101-disk-0
ā āāā ...
āāā rpool/tenant-data (tenant-specific data)
āāā rpool/backups (VM/CT backups)
āāā rpool/iso (ISO images for installation)
Storage Recommendations
- Use SSD/NVMe for rpool (OS and VM disks)
- Consider separate ZIL/SLOG for synchronous write-heavy workloads
- L2ARC cache for read-heavy workloads if budget allows
- Regular scrubs and monitoring via
zpool status
Backup Strategy
- Automated snapshots of VM/CT disks before major changes
- Regular vzdump backups to external storage
- Off-site replication for critical tenant data
- Backup verification and restore testing procedures
4. Internal DNS & Domain Naming
DNS Strategy
- DNS Server: Run an internal DNS server (e.g., Pi-hole, PowerDNS, or BIND) on Proxmox
- Domain Structure:
.internal for service domains (proxmox.homelab.internal, wordpress.homelab.internal)
- Tenant domains mapped to internal IPs (tenant1.homelab.internal, tenant2.homelab.internal)
Naming Conventions
- Hostnames:
tenantX.internal, wp-serviceX.internal
- Domain Naming:
tenant1.homelab.internal, tenant2.homelab.internal
- DNS Records:
- A records for LXC/VM IP addresses
- CNAME aliases for service aliases (
mysql.homelab.internal, redis.homelab.internal)
DNS Security
- Split-Horizon DNS: External clients get cloud DNS, internal traffic uses internal DNS
- DNSSEC implementation for security
- DNS over TLS/HTTPS for encrypted queries
5. Application Stack (Bedrock + Sage)
Bedrock Implementation
Directory Structure per Tenant
/srv/wordpress/tenant1/
āāā .env
āāā composer.json
āāā composer.lock
āāā config/
ā āāā application.php
ā āāā environments/
ā āāā development.php
ā āāā staging.php
ā āāā production.php
āāā web/
ā āāā wp/
ā ā āāā index.php (Bedrock bootstraps)
ā ā āāā ...
ā āāā wp-config.php (Bedrock generated)
ā āāā .htaccess
āāā vendor/
āāā wp-content/
āāā mu-plugins/
āāā plugins/
āāā themes/
ā āāā sage/ (tenant-specific Sage theme)
ā āāā ...
āāā uploads/
Environment Management
.env files for each environment (dev/staging/prod)
- Vault for secret management (AWS Secrets Manager, HashiCorp Vault, or SOPS)
- Environment-specific Composer dependencies
- Automated
.env generation from templates
Sage 10+ Implementation
Theme Structure per Tenant
/srv/wordpress/tenant1/web/wp-content/themes/sage/
āāā assets/
ā āāā build/ (compiled assets)
ā āāā scripts/
ā āāā styles/
ā āāā images/
āāā composer.json
āāā package.json
āāā tailwind.config.js
āāā webpack.mix.js or vite.config.js
āāā resources/
ā āāā views/
ā āāā fonts/
ā āāā images/
āāā functions.php
āāā sage.php
Build Process
- Local development:
yarn start or npm run dev
- Production builds:
yarn run production or npm run build
- Asset versioning for cache busting
- Critical CSS extraction
- Image optimization pipeline
Multi-tenancy Approaches
Approach 1: Isolated Instances (Recommended)
- Each tenant gets separate LXC container/VM
- Separate database, filesystem, and configuration
- Maximum isolation and security
- Higher resource usage but simpler troubleshooting
Approach 2: Shared Core, Separate Content
- Single WordPress codebase
- Separate databases per tenant
- Shared uploads directory with tenant subdirectories
- Requires multi-tenancy plugin or custom code
- Lower resource usage but complex shared state
Approach 3: Multisite Network
- WordPress multisite with domain mapping
- Shared codebase and database
- Separate tables for site-specific data
- Built-in but limited plugin/theme compatibility
Recommendation: Approach 1 (Isolated Instances) for maximum security and flexibility in a homelab environment.
6. WordPress VM/LXC Template Creation
Template Development
LXC Template Creation Script
#!/bin/bash
# create-wordpress-template.sh
set -euo pipefail
TEMPLATE_NAME="wordpress-ubuntu-2204-base"
TEMPLATE_STORAGE="local-zfs"
TEMPLATE_VMID=9000
# Create base container
pct create $TEMPLATE_VMID local:vz/ubuntu-22.04-standard_22.04-1_amd64.tar.gz \
--storage $TEMPLATE_STORAGE \
--hostname $TEMPLATE_NAME \
--cores 2 \
--memory 2048 \
--swap 512 \
--rootfs $TEMPLATE_STORAGE:10 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--features nesting=1 \
--password 'changeme' \
--start 1
# Wait for container to start
sleep 30
# Install base packages
pct exec $TEMPLATE_VMID -- apt-get update
pct exec $TEMPLATE_VMID -- apt-get install -y \
nginx \
php8.1-fpm \
php8.1-mysql \
php8.1-xml \
php8.1-curl \
php8.1-gd \
php8.1-mbstring \
php8.1-zip \
php8.1-intl \
php8.1-bcmath \
php8.1-imagick \
composer \
git \
unzip \
curl \
wget \
vim \
htop \
fail2ban \
ufw \
certbot \
python3-certbot-nginx
# Configure PHP-FPM
pct exec $TEMPLATE_VMID -- sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/8.1/fpm/php.ini
pct exec $TEMPLATE_VMID -- sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 64M/' /etc/php/8.1/fpm/php.ini
pct exec $TEMPLATE_VMID -- sed -i 's/post_max_size = 8M/post_max_size = 64M/' /etc/php/8.1/fpm/php.ini
pct exec $TEMPLATE_VMID -- sed -i 's/max_execution_time = 30/max_execution_time = 120/' /etc/php/8.1/fpm/php.ini
pct exec $TEMPLATE_VMID -- sed -i 's/memory_limit = 128M/memory_limit = 256M/' /etc/php/8.1/fpm/php.ini
# Configure NGINX for WordPress
pct exec $TEMPLATE_VMID -- cat > /etc/nginx/snippets/wordpress.conf << 'EOF'
# WordPress specific configuration
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Deny access to sensitive files
location ~* \.(htaccess|htpasswd|ini|log|sh|inc|bak|sql)$ {
deny all;
}
location = /wp-config.php {
deny all;
}
# Disable XML-RPC
location = /xmlrpc.php {
deny all;
}
EOF
# Configure security hardening
pct exec $TEMPLATE_VMID -- cat > /etc/nginx/snippets/security-headers.conf << 'EOF'
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';" always;
EOF
# Setup fail2ban for WordPress
pct exec $TEMPLATE_VMID -- cat > /etc/fail2ban/jail.d/wordpress.conf << 'EOF'
[wordpress-auth]
enabled = true
filter = wordpress-auth
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 5
bantime = 3600
findtime = 600
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/*error.log
maxretry = 10
bantime = 3600
findtime = 600
EOF
pct exec $TEMPLATE_VMID -- cat > /etc/fail2ban/filter.d/wordpress-auth.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
^<HOST> .* "POST /xmlrpc.php
ignoreregex =
EOF
# Enable services
pct exec $TEMPLATE_VMID -- systemctl enable nginx php8.1-fpm fail2ban
# Install WP-CLI
pct exec $TEMPLATE_VMID -- curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/wp-cli/v2.8.1/utils/wp-cli.phar
pct exec $TEMPLATE_VMID -- chmod +x /usr/local/bin/wp
# Clean up
pct exec $TEMPLATE_VMID -- apt-get clean
pct exec $TEMPLATE_VMID -- rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Stop container and convert to template
pct stop $TEMPLATE_VMID
pct template $TEMPLATE_VMID
echo "Template $TEMPLATE_NAME created successfully with VMID $TEMPLATE_VMID"
VM Template Creation (Packer)
{
"builders": [{
"type": "proxmox-iso",
"proxmox_url": "https://pve1.internal:8006/api2/json",
"api_token_id": "packer@pve!packer",
"api_token_secret": "{{user `proxmox_token`}}",
"node": "pve1",
"vm_name": "wordpress-ubuntu-2204-template",
"iso_file": "local:iso/ubuntu-22.04.3-live-server-amd64.iso",
"iso_checksum": "sha256:...",
"disk_size": "20G",
"memory": 4096,
"cores": 2,
"ssh_username": "ubuntu",
"ssh_password": "ubuntu",
"ssh_wait_timeout": "10000s",
"network_adapters": [{
"bridge": "vmbr0",
"model": "virtio",
"tag": "10"
}]
}],
"provisioners": [{
"type": "shell",
"inline": [
"sudo apt-get update",
"sudo apt-get install -y nginx php8.1-fpm php8.1-mysql php8.1-xml php8.1-curl php8.1-gd php8.1-mbstring php8.1-zip php8.1-intl php8.1-bcmath php8.1-imagick composer git unzip curl wget vim htop fail2ban ufw certbot python3-certbot-nginx"
]
}, {
"type": "shell",
"scripts": [
"scripts/configure-php.sh",
"scripts/configure-nginx.sh",
"scripts/harden-system.sh"
]
}],
"post-processors": [{
"type": "proxmox-template",
"vm_id": 9001
}]
}
Template Usage
# Create new tenant from LXC template
pct clone 9000 101 --hostname tenant1 --storage local-zfs --start 1
# Create new tenant from VM template
qm clone 9001 201 --name tenant1 --full --storage local-zfs
qm start 201
7. Reverse Proxy & SSL
Reverse Proxy Options
| Feature
| Nginx Proxy Manager
| Traefik
| Caddy
|
| UI Management
| Excellent
| Basic
| None
|
| Docker/LXC Labels
| Limited
| Native
| Limited
|
| Auto SSL
| Yes
| Yes
| Yes
|
| Learning Curve
| Low
| Medium
| Low
|
Best For
| Beginners
| Containers
| Simplicity
Recommendation: Traefik for dynamic container orchestration integration.
Traefik Configuration
Static Configuration (/etc/traefik/traefik.yml)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
file:
directory: /etc/traefik/dynamic
watch: true
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
letsencrypt:
acme:
email: admin@homelab.local
storage: /etc/traefik/acme.json
httpChallenge:
entryPoint: web
api:
dashboard: true
insecure: true
log:
level: INFO
format: json
accessLog:
format: json
Dynamic Tenant Configuration (/etc/traefik/dynamic/tenant1.yml)
http:
routers:
tenant1:
rule: "Host(`tenant1.homelab.internal`)"
entryPoints:
- websecure
service: tenant1
tls:
certResolver: letsencrypt
middlewares:
- security-headers
- rate-limit
services:
tenant1:
loadBalancer:
servers:
- url: "http://10.0.10.101"
passHostHeader: true
middlewares:
security-headers:
headers:
customRequestHeaders:
X-Forwarded-Proto: "https"
customResponseHeaders:
X-Frame-Options: "SAMEORIGIN"
X-XSS-Protection: "1; mode=block"
X-Content-Type-Options: "nosniff"
Referrer-Policy: "strict-origin-when-cross-origin"
Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';"
rate-limit:
rateLimit:
average: 100
burst: 50
SSL/TLS Implementation
- Let's Encrypt: Wildcard certificates for
*.homelab.internal
- Individual Certs: For external domains (
tenant1.com, tenant2.com)
- DNS-01 Challenge: For wildcard certs (requires Cloudflare/Route53 API)
- Auto Renewal: Traefik handles automatic renewal
- HSTS: Enabled via middleware
- OCSP Stapling: Configured in Traefik
8. Database Strategy
Database Options
| Approach
| Isolation
| Resource Usage
| Maintenance
| Best For
|
| Individual DB per tenant
| High
| Higher
| Distributed
| Security-critical
|
| Centralized DB with schemas
| Medium
| Lower
| Centralized
| Cost optimization
|
| Managed DB Service
| High
| N/A
| None
| No-ops preference
Recommendation: Centralized MariaDB with per-tenant databases/schemas.
Database Host Setup
# Create dedicated LXC for MariaDB
pct create 100 local:vz/ubuntu-22.04-standard_22.04-1_amd64.tar.gz \
--hostname mariadb \
--storage local-zfs \
--cores 4 \
--memory 4096 \
--swap 1024 \
--rootfs local-zfs:50 \
--net0 name=eth0,bridge=vmbr0,ip=10.0.10.100/24,gw=10.0.10.1 \
--start 1
# Install MariaDB
pct exec 100 -- apt-get update
pct exec 100 -- apt-get install -y mariadb-server
# Configure for multi-tenant
pct exec 100 -- cat > /etc/mysql/mariadb.conf.d/99-multi-tenant.cnf << 'EOF'
[mysqld]
innodb_buffer_pool_size = 2G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
max_connections = 500
query_cache_type = ON
query_cache_size = 64M
thread_cache_size = 50
table_open_cache = 2000
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
EOF
# Create databases per tenant
pct exec 100 -- mysql -e "
CREATE DATABASE wp_tenant1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_tenant1'@'%' IDENTIFIED BY 'secure_password';
GRANT ALL PRIVILEGES ON wp_tenant1.* TO 'wp_tenant1'@'%';
FLUSH PRIVILEGES;
"
Database Security
- Firewall: Only allow connections from tenant network (10.0.10.0/24)
- SSL: Require SSL connections
- Backups: Automated daily + hourly binlog
- Monitoring: Prometheus mysqld_exporter
9. Media Storage
Storage Options
| Option
| Pros
| Cons
|
| Local (ZFS)
| Simple, fast, included in backups
| Limited by disk, no CDN
|
| MinIO (S3)
| Scalable, CDN-ready, redundant
| Additional complexity
|
| Cloud (S3/R2)
| Managed, global CDN
| Cost, external dependency
Recommendation: Start with local ZFS, migrate to MinIO when needed.
MinIO Setup (Optional)
# Deploy MinIO as LXC
pct create 102 local:vz/ubuntu-22.04-standard_22.04-1_amd64.tar.gz \
--hostname minio \
--storage local-zfs \
--cores 2 \
--memory 4096 \
--rootfs local-zfs:100 \
--net0 name=eth0,bridge=vmbr0,ip=10.0.20.10/24 \
--start 1
# Install MinIO
pct exec 102 -- curl -o /usr/local/bin/minio https://dl.min.io/server/minio/release/linux-amd64/minio
pct exec 102 -- chmod +x /usr/local/bin/minio
pct exec 102 -- useradd -r minio-user -s /bin/false
pct exec 102 -- mkdir -p /data/minio
pct exec 102 -- chown minio-user:minio-user /data/minio
# Systemd service
pct exec 102 -- cat > /etc/systemd/system/minio.service << 'EOF'
[Unit]
Description=MinIO
After=network-online.target
[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Configuration
pct exec 102 -- cat > /etc/default/minio << 'EOF'
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=secure_password
MINIO_VOLUMES="/data/minio"
MINIO_OPTS="--console-address :9001"
EOF
WordPress Integration
# Install WP Offload Media plugin via WP-CLI
wp plugin install amazon-s3-and-cloudfront --activate --path=/srv/wordpress/tenant1/web
# Configure via constants in wp-config.php
define('AS3CF_SETTINGS', serialize([
'provider' => 'minio',
'endpoint' => 'http://minio.internal:9000',
'region' => 'us-east-1',
'bucket' => 'tenant1-media',
'key' => 'access_key',
'secret' => 'secret_key',
'use_path_style_endpoint' => true,
]));
10. CI/CD & Automation
GitHub Actions Workflow
# .github/workflows/deploy-wordpress.yml
name: Deploy WordPress Tenant
on:
push:
branches: [main]
paths:
- 'tenants/**'
workflow_dispatch:
inputs:
tenant:
description: 'Tenant slug to deploy'
required: true
type: choice
options:
- tenant1
- tenant2
- tenant3
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact: ${{ steps.upload.outputs.artifact }}
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
working-directory: tenants/${{ github.event.inputs.tenant || 'tenant1' }}
run: npm ci
- name: Build assets (Sage 10+)
working-directory: tenants/${{ github.event.inputs.tenant || 'tenant1' }}
run: npm run build
- name: Create deployment package
working-directory: tenants/${{ github.event.inputs.tenant || 'tenant1' }}
run: |
mkdir -p ../deploy
rsync -av --exclude node_modules --exclude .git --exclude '*.log' \
. ../deploy/${{ github.event.inputs.tenant || 'tenant1' }}/
cd ../deploy
tar -czf ${{ github.event.inputs.tenant || 'tenant1' }}.tar.gz ${{ github.event.inputs.tenant || 'tenant1' }}/
- name: Upload artifact
id: upload
uses: actions/upload-artifact@v4
with:
name: deployment-${{ github.event.inputs.tenant || 'tenant1' }}
path: tenants/deploy/*.tar.gz
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: deployment-${{ github.event.inputs.tenant || 'tenant1' }}
path: ./deploy
- name: Deploy to Proxmox
env:
SSH_KEY: ${{ secrets.PROXMOX_SSH_KEY }}
PROXMOX_HOST: ${{ secrets.PROXMOX_HOST }}
TENANT: ${{ github.event.inputs.tenant || 'tenant1' }}
run: |
echo "$SSH_KEY" > key.pem
chmod 600 key.pem
# Extract and deploy
tar -xzf deploy/*.tar.gz -C deploy/
# Copy to Proxmox container
scp -i key.pem -r deploy/${{ github.event.inputs.tenant || 'tenant1' }}/* \
root@$PROXMOX_HOST:/srv/wordpress/${{ github.event.inputs.tenant || 'tenant1' }}/
# Run post-deploy
ssh -i key.pem root@$PROXMOX_HOST "
cd /srv/wordpress/${{ github.event.inputs.tenant || 'tenant1' }}
composer install --no-dev --optimize-autoloader
wp cache flush --path=/srv/wordpress/${{ github.event.inputs.tenant || 'tenant1' }}/web
systemctl reload nginx php8.1-fpm
"
11. Monitoring & Logging
Stack Components
| Component
| Purpose
| Port
|
| Prometheus
| Metrics collection
| 9090
|
| Grafana
| Dashboards
| 3000
|
| Node Exporter
| Host metrics
| 9100
|
| MySQL Exporter
| DB metrics
| 9104
|
| PHP-FPM Exporter
| App metrics
| 9253
|
| Blackbox Exporter
| HTTP probes
| 9115
|
| Loki
| Log aggregation
| 3100
|
| Promtail
| Log shipping
| 9080
Prometheus Config (/etc/prometheus/prometheus.yml)
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'proxmox'
static_configs:
- targets: ['pve1.internal:9221']
- job_name: 'node'
static_configs:
- targets:
- 'pve1.internal:9100'
- '10.0.10.101:9100'
- '10.0.10.102:9100'
- job_name: 'mysql'
static_configs:
- targets: ['10.0.10.100:9104']
- job_name: 'php-fpm'
static_configs:
- targets:
- '10.0.10.101:9253'
- '10.0.10.102:9253'
- job_name: 'blackbox'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://tenant1.homelab.internal
- https://tenant2.homelab.internal
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 10.0.30.10:9115
Key Dashboards
- Host Overview: CPU, RAM, Disk, Network per Proxmox node
- Container Overview: Per-tenant resource usage
- WordPress Health: Requests/sec, Response time, Error rate, PHP memory
- Database Performance: Queries/sec, Slow queries, Connections, Buffer pool
- Business Metrics: Page views, Load time (LCP), Conversion funnel
12. Backup & Disaster Recovery
Backup Strategy
| Layer
| Frequency
| Retention
| Method
|
| Proxmox VM/CT
| Daily
| 14 daily, 4 weekly, 3 monthly
| vzdump to NFS
|
| Application Files
| Every 6 hours
| 7 days
| rsync to MinIO
|
| Database
| Hourly binlog, Daily dump
| 30 days
| mysqldump + xtrabackup
|
| Configuration
| Hourly
| 90 days
| Git repo + etckeeper
Disaster Recovery Plan
RTO: < 4 hours | RPO: < 1 hour
Recovery Procedures
- Detection: Alert from monitoring (Prometheus/Alertmanager)
- Assessment: Determine scope (single tenant vs. multi-tenant vs. host)
- Failover:
- Single tenant: Restore from latest backup to new container
- Host failure: Spin up on DR node, restore from vzdump
- Validation: Smoke tests, DB integrity check, SSL verification
- DNS Cutover: Update internal DNS records
|
|
|
|
| |
|