High-Availability WordPress Deployment on Kubernetes
Learn to deploy high-availability WordPress on Kubernetes with MySQL StatefulSet, auto-scaling, SSL, monitoring, and production best practices.

Shamaila Mahmood
July 5, 2025


Try the setup in your local cluster in minutes
Do you want to try the Wordpress setup present in this article in your local cluster without manually copy pasting any yaml? Start by creating your project using our Wordpress template.
High-Availability WordPress Deployment on Kubernetes
Building a scalable, resilient WordPress platform with MySQL in Kubernetes using production-ready configurations and best practices for mid-level developers.
Introduction
WordPress powers over 40% of all websites on the internet, making it one of the most popular content management systems. When deploying WordPress at scale, you need high availability, automatic scaling, and robust data persistence. Kubernetes provides the perfect platform for achieving these goals.
This guide will walk you through deploying a production-ready, high-availability WordPress setup on Kubernetes.
Prerequisites
Before we begin, ensure you have:
- Kubernetes cluster (v1.20+) with at least 3 worker nodes
- kubectl configured to access your cluster
- Ingress controller (NGINX recommended)
- StorageClass configured for dynamic volume provisioning
- Basic understanding of Kubernetes concepts (Pods, Services, Deployments)
Verify your setup:
# Check cluster status
kubectl cluster-info
# Verify nodes are ready
kubectl get nodes
# Check storage classes
kubectl get storageclass
# Verify ingress controller
kubectl get pods -n ingress-nginx
Architecture Overview
Our high-availability WordPress deployment consists of:
Key Components:
- WordPress Deployment: 3 replicas for high availability
- MySQL StatefulSet: Single instance with persistent storage
- Services: ClusterIP for internal communication
- Ingress: External access with SSL termination
- HPA: Auto-scaling based on CPU/memory usage
- PVC: Persistent storage for database and media files
Step 1: Setup MySQL Database
We'll start with a robust MySQL deployment using StatefulSet for data persistence.
1.1: Create MySQL Configuration
# mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
labels:
app: mysql
data:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
# Performance tuning
MYSQL_INNODB_BUFFER_POOL_SIZE: "128M"
MYSQL_MAX_CONNECTIONS: "200"
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
labels:
app: mysql
type: Opaque
stringData:
MYSQL_ROOT_PASSWORD: "StrongRootPassword123!"
MYSQL_PASSWORD: "SecureWPPassword456!"
1.2: Deploy MySQL StatefulSet
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
labels:
app: mysql
spec:
serviceName: mysql-headless
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
envFrom:
- configMapRef:
name: mysql-config
- secretRef:
name: mysql-secret
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: mysql-config-volume
mountPath: /etc/mysql/conf.d
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 500m
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
volumes:
- name: mysql-config-volume
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
storageClassName: standard # Replace with your storage class
---
# MySQL Headless Service (required for StatefulSet)
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
---
# MySQL ClusterIP Service (for application access)
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
type: ClusterIP
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
protocol: TCP
1.3: Deploy MySQL
# Apply MySQL configuration
kubectl apply -f mysql-config.yaml
kubectl apply -f mysql-statefulset.yaml
# Verify MySQL deployment
kubectl get statefulset mysql
kubectl get pods -l app=mysql
kubectl get pvc -l app=mysql
# Check MySQL logs
kubectl logs mysql-0
# Test MySQL connectivity
kubectl exec -it mysql-0 -- mysql -u root -p -e "SHOW DATABASES;"
Step 2: Deploy WordPress Application
Now we'll deploy WordPress with high availability and proper configuration.
2.1: Create WordPress Configuration
# wordpress-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: wordpress-config
labels:
app: wordpress
data:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
# WordPress configuration
WORDPRESS_CONFIG_EXTRA: |
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);
---
apiVersion: v1
kind: Secret
metadata:
name: wordpress-secret
labels:
app: wordpress
type: Opaque
stringData:
WORDPRESS_DB_PASSWORD: "SecureWPPassword456!"
# WordPress auth keys (generate from https://api.wordpress.org/secret-key/1.1/salt/)
WORDPRESS_AUTH_KEY: "your-auth-key-here"
WORDPRESS_SECURE_AUTH_KEY: "your-secure-auth-key-here"
WORDPRESS_LOGGED_IN_KEY: "your-logged-in-key-here"
WORDPRESS_NONCE_KEY: "your-nonce-key-here"
2.2: Create WordPress Deployment
# wordpress-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
replicas: 3 # High availability with 3 replicas
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:6.3-php8.1-apache
envFrom:
- configMapRef:
name: wordpress-config
- secretRef:
name: wordpress-secret
ports:
- containerPort: 80
name: http
volumeMounts:
- name: wordpress-data
mountPath: /var/www/html
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# Health checks
livenessProbe:
httpGet:
path: /wp-admin/install.php
port: 80
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /wp-admin/install.php
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 2
# Security context
securityContext:
runAsNonRoot: false
runAsUser: 33 # www-data user
allowPrivilegeEscalation: false
volumes:
- name: wordpress-data
persistentVolumeClaim:
claimName: wordpress-pvc
---
# WordPress PVC for shared file storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-pvc
labels:
app: wordpress
spec:
accessModes:
- ReadWriteMany # Shared across multiple pods
resources:
requests:
storage: 10Gi
storageClassName: standard # Replace with your storage class
---
# WordPress Service
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
type: ClusterIP
selector:
app: wordpress
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
sessionAffinity: ClientIP # For session persistence
2.3: Deploy WordPress
# Apply WordPress configuration
kubectl apply -f wordpress-config.yaml
kubectl apply -f wordpress-deployment.yaml
# Verify WordPress deployment
kubectl get deployment wordpress
kubectl get pods -l app=wordpress
kubectl get pvc wordpress-pvc
# Check WordPress logs
kubectl logs -l app=wordpress
# Test WordPress service
kubectl port-forward svc/wordpress 8080:80
# Open browser to http://localhost:8080
Step 3: Configure Ingress and Load Balancing
Set up external access with SSL termination and load balancing.
3.1: Install cert-manager (for SSL certificates)
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
# Verify cert-manager installation
kubectl get pods -n cert-manager
3.2: Create SSL Certificate Issuer
# ssl-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com # Replace with your email
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
3.3: Create Ingress Configuration
# wordpress-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
# Rate limiting
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
tls:
- hosts:
- your-wordpress.com # Replace with your domain
secretName: wordpress-tls
rules:
- host: your-wordpress.com # Replace with your domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress
port:
number: 80
3.4: Deploy Ingress
# Apply SSL issuer and Ingress
kubectl apply -f ssl-issuer.yaml
kubectl apply -f wordpress-ingress.yaml
# Check certificate status
kubectl get certificate
kubectl describe certificate wordpress-tls
# Verify Ingress
kubectl get ingress
kubectl describe ingress wordpress-ingress
Step 4: Implement Auto-scaling
Configure Horizontal Pod Autoscaler for handling traffic spikes.
4.1: Create HPA Configuration
# wordpress-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: wordpress-hpa
labels:
app: wordpress
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: wordpress
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
4.2: Deploy HPA
# Apply HPA configuration
kubectl apply -f wordpress-hpa.yaml
# Monitor HPA status
kubectl get hpa wordpress-hpa
kubectl describe hpa wordpress-hpa
# Watch auto-scaling in action
kubectl get hpa wordpress-hpa -w
Step 5: Setup Monitoring and Health Checks
Implement comprehensive monitoring for your WordPress deployment.
5.1: Create ServiceMonitor for Prometheus
# wordpress-monitoring.yaml
apiVersion: v1
kind: ServiceMonitor
metadata:
name: wordpress-monitor
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
endpoints:
- port: http
path: /wp-admin/admin-ajax.php
interval: 30s
---
# WordPress health check endpoint
apiVersion: v1
kind: ConfigMap
metadata:
name: wordpress-health-check
data:
health-check.php: |
<?php
// Simple health check endpoint
$connection = mysqli_connect(
getenv('WORDPRESS_DB_HOST'),
getenv('WORDPRESS_DB_USER'),
getenv('WORDPRESS_DB_PASSWORD'),
getenv('WORDPRESS_DB_NAME')
);
if ($connection) {
echo "OK";
http_response_code(200);
} else {
echo "Database connection failed";
http_response_code(500);
}
?>
5.2: Create Alerting Rules
# wordpress-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: wordpress-alerts
labels:
app: wordpress
spec:
groups:
- name: wordpress
rules:
- alert: WordPressDown
expr: up{job="wordpress"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "WordPress is down"
description: "WordPress has been down for more than 1 minute"
- alert: WordPressHighCPU
expr: rate(container_cpu_usage_seconds_total{pod=~"wordpress-.*"}[5m]) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU usage on WordPress pods"
description: "WordPress CPU usage is above 80% for more than 5 minutes"
- alert: WordPressHighMemory
expr: container_memory_usage_bytes{pod=~"wordpress-.*"} / container_spec_memory_limit_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage on WordPress pods"
description: "WordPress memory usage is above 90% for more than 5 minutes"
Step 6: Backup and Recovery
Implement automated backup strategies for both database and WordPress files.
6.1: Create Database Backup CronJob
# mysql-backup.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backup-pvc
labels:
app: backup
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: standard
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
labels:
app: mysql-backup
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
metadata:
labels:
app: mysql-backup
spec:
containers:
- name: mysql-backup
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
command:
- /bin/bash
- -c
- |
set -e
BACKUP_FILE="/backup/mysql-backup-$(date +%Y%m%d-%H%M%S).sql"
echo "Starting MySQL backup to $BACKUP_FILE"
mysqldump -h mysql -u root -p$MYSQL_ROOT_PASSWORD \
--single-transaction \
--routines \
--triggers \
--all-databases > $BACKUP_FILE
echo "Backup completed successfully"
gzip $BACKUP_FILE
echo "Backup compressed"
# Keep only last 7 days of backups
find /backup -name "mysql-backup-*.sql.gz" -mtime +7 -delete
echo "Old backups cleaned up"
# List current backups
ls -la /backup/
volumeMounts:
- name: backup-storage
mountPath: /backup
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
6.2: WordPress Files Backup
# wordpress-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: wordpress-backup
labels:
app: wordpress-backup
spec:
schedule: "0 3 * * *" # Daily at 3 AM
jobTemplate:
spec:
template:
metadata:
labels:
app: wordpress-backup
spec:
containers:
- name: wordpress-backup
image: alpine:latest
command:
- /bin/sh
- -c
- |
set -e
apk add --no-cache tar gzip
BACKUP_FILE="/backup/wordpress-files-$(date +%Y%m%d-%H%M%S).tar.gz"
echo "Starting WordPress files backup to $BACKUP_FILE"
tar -czf $BACKUP_FILE -C /var/www/html \
--exclude='wp-content/cache/*' \
--exclude='wp-content/tmp/*' \
.
echo "WordPress files backup completed"
# Keep only last 7 days of backups
find /backup -name "wordpress-files-*.tar.gz" -mtime +7 -delete
echo "Old file backups cleaned up"
ls -la /backup/
volumeMounts:
- name: wordpress-data
mountPath: /var/www/html
readOnly: true
- name: backup-storage
mountPath: /backup
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumes:
- name: wordpress-data
persistentVolumeClaim:
claimName: wordpress-pvc
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
6.3: Deploy Backup Solutions
# Apply backup configurations
kubectl apply -f mysql-backup.yaml
kubectl apply -f wordpress-backup.yaml
# Check backup jobs
kubectl get cronjob
kubectl get jobs
# Monitor backup execution
kubectl logs -l app=mysql-backup
kubectl logs -l app=wordpress-backup
# Manual backup execution
kubectl create job mysql-backup-manual --from=cronjob/mysql-backup
Troubleshooting
Common Issues and Solutions
1. WordPress Pods Not Starting
# Check pod status and events
kubectl get pods -l app=wordpress
kubectl describe pod <wordpress-pod-name>
# Common causes:
# - Database connection issues
# - PVC mounting problems
# - Resource constraints
# Check database connectivity
kubectl exec -it <wordpress-pod> -- wp db check --allow-root
2. Database Connection Problems
# Test MySQL connectivity
kubectl exec -it mysql-0 -- mysql -u root -p -e "SHOW DATABASES;"
# Check MySQL service
kubectl get svc mysql
kubectl describe svc mysql
# Verify DNS resolution
kubectl exec -it <wordpress-pod> -- nslookup mysql
3. Storage Issues
# Check PVC status
kubectl get pvc
kubectl describe pvc wordpress-pvc
kubectl describe pvc mysql-data-mysql-0
# Check storage class
kubectl get storageclass
kubectl describe storageclass standard
4. Ingress Not Working
# Check Ingress status
kubectl get ingress
kubectl describe ingress wordpress-ingress
# Verify Ingress controller
kubectl get pods -n ingress-nginx
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# Check SSL certificate
kubectl get certificate
kubectl describe certificate wordpress-tls
5. Performance Issues
# Monitor resource usage
kubectl top pods -l app=wordpress
kubectl top pods -l app=mysql
# Check HPA status
kubectl get hpa
kubectl describe hpa wordpress-hpa
# View logs for errors
kubectl logs -l app=wordpress --tail=100
kubectl logs mysql-0 --tail=100
Production Considerations
Security Hardening
- Network Policies: Implement network segmentation
- Pod Security Standards: Use security contexts and policies
- Secret Management: Use external secret management (e.g., Vault)
- Regular Updates: Keep WordPress and plugins updated
- Vulnerability Scanning: Implement container image scanning
Performance Optimization
- Caching: Implement Redis for object caching
- CDN: Use CloudFront or similar for static assets
- Database Tuning: Optimize MySQL configuration
- Image Optimization: Use optimized WordPress images
- Resource Limits: Fine-tune resource allocation
Operational Excellence
- Monitoring: Implement comprehensive monitoring with Prometheus/Grafana
- Logging: Centralize logs with ELK stack or similar
- Alerting: Set up proactive alerting for issues
- Disaster Recovery: Test backup and restore procedures
- Documentation: Maintain operational runbooks
Example Production Enhancements
# Production-ready WordPress with Redis caching
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-prod
spec:
replicas: 5
template:
spec:
containers:
- name: wordpress
image: wordpress:6.3-php8.1-apache
env:
- name: WORDPRESS_CONFIG_EXTRA
value: |
define('WP_CACHE', true);
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
# Additional security and performance configurations
Conclusion
You've successfully deployed a high-availability WordPress platform on Kubernetes with:
✅ Scalable Architecture: Multiple WordPress replicas with load balancing ✅ Data Persistence: StatefulSet MySQL with persistent storage ✅ Auto-scaling: HPA for handling traffic spikes ✅ SSL/TLS: Automated certificate management ✅ Monitoring: Health checks and alerting ✅ Backup Strategy: Automated database and file backups ✅ Production Ready: Security, performance, and operational best practices
This setup can handle thousands of concurrent users and automatically scale based on demand. For production environments, consider implementing additional features like:
- Redis caching for improved performance
- CDN integration for global content delivery
- Advanced monitoring with Prometheus and Grafana
- GitOps deployment with ArgoCD or Flux
- Multi-region deployment for global availability
Your WordPress platform is now ready to serve high-traffic websites with enterprise-grade reliability and scalability.

Try the setup in your local cluster in minutes
Do you want to try the Wordpress setup present in this article in your local cluster without manually copy pasting any yaml? Start by creating your project using our Wordpress template.