Self-hosting gives you full control over your website and automation workflows. This guide shows you how to deploy WordPress and n8n on a single VPS using Docker Compose—giving you a powerful content management system and workflow automation platform for a fraction of the cost of managed services.
Why Self-Host WordPress and n8n?
WordPress Benefits
- Full Control: No platform restrictions, install any plugin or theme
- Cost Savings: Managed WordPress hosting costs $30-100+/month, VPS hosting costs under $10/month
- Performance: Dedicated resources with no noisy neighbors
- Privacy: Your data stays on your server in your chosen location
- Customization: PHP settings, caching, and server configuration
n8n Benefits
- No Usage Limits: Self-hosted n8n has no execution limits (unlike n8n Cloud's 2,500 executions/month on free tier)
- Data Privacy: Sensitive workflow data never leaves your server
- Custom Integrations: Install community nodes and custom code
- Cost Effective: n8n Cloud Pro costs $20+/month, self-hosted is free
- Full API Access: Integrate with any internal system
What You'll Build
By the end of this guide, you'll have:
- ✅ WordPress with MySQL database and persistent storage
- ✅ n8n workflow automation with SQLite/PostgreSQL backend
- ✅ Nginx reverse proxy handling both applications
- ✅ Free SSL certificates via Let's Encrypt
- ✅ Automatic container restarts and health checks
- ✅ Automated backup system
Prerequisites
VPS Requirements
- OS: Ubuntu 22.04 or 24.04 LTS
- RAM: 2GB minimum (4GB recommended for both services)
- Storage: 40GB+ NVMe SSD
- CPU: 2 vCPUs minimum
Recommended: A DanubeData VPS with 2 vCPUs, 4GB RAM, and 80GB NVMe storage costs €8.99/month—perfect for running both WordPress and n8n with room to grow.
Domain Setup
- Domain for WordPress (e.g.,
example.com,www.example.com) - Subdomain for n8n (e.g.,
n8n.example.comorautomation.example.com) - Both domains pointed to your VPS IP via A records
Step 1: Install Docker and Docker Compose
SSH into your VPS and run:
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add your user to docker group (log out and back in after)
sudo usermod -aG docker $USER
# Install Docker Compose plugin
sudo apt install docker-compose-plugin -y
# Verify installation
docker --version
docker compose version
Log out and back in for group changes to take effect:
exit
# SSH back in
Step 2: Create Project Structure
# Create main project directory
mkdir -p ~/hosting && cd ~/hosting
# Create subdirectories
mkdir -p nginx/conf.d nginx/ssl
mkdir -p wordpress/wp-content
mkdir -p n8n/data
mkdir -p mysql/data
mkdir -p backups
Your directory structure:
~/hosting/
├── docker-compose.yml
├── .env
├── nginx/
│ ├── conf.d/
│ │ ├── wordpress.conf
│ │ └── n8n.conf
│ └── ssl/
├── wordpress/
│ └── wp-content/
├── n8n/
│ └── data/
├── mysql/
│ └── data/
└── backups/
Step 3: Create Environment File
Create .env file with your configuration:
# Domain Configuration
WORDPRESS_DOMAIN=example.com
N8N_DOMAIN=n8n.example.com
# MySQL Configuration
MYSQL_ROOT_PASSWORD=your_secure_root_password_here
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=your_secure_wordpress_password_here
# WordPress Configuration
WORDPRESS_TABLE_PREFIX=wp_
# n8n Configuration
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=your_secure_n8n_password_here
N8N_ENCRYPTION_KEY=your_32_char_encryption_key_here
# Generate secure passwords with:
# openssl rand -base64 32
Security tip: Generate strong passwords:
openssl rand -base64 32
Step 4: Create Docker Compose File
Create docker-compose.yml:
version: '3.8'
services:
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./wordpress:/var/www/wordpress
networks:
- hosting
depends_on:
- wordpress
- n8n
# WordPress
wordpress:
image: wordpress:6.7-php8.3-fpm
container_name: wordpress
restart: unless-stopped
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
volumes:
- ./wordpress:/var/www/html
networks:
- hosting
depends_on:
mysql:
condition: service_healthy
# MySQL Database
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
networks:
- hosting
command: --default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# n8n Workflow Automation
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
environment:
- N8N_HOST=${N8N_DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://${N8N_DOMAIN}/
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=Europe/Berlin
- TZ=Europe/Berlin
volumes:
- ./n8n/data:/home/node/.n8n
networks:
- hosting
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:5678/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Redis (optional - for WordPress object caching)
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
volumes:
- redis-data:/data
networks:
- hosting
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
hosting:
driver: bridge
volumes:
redis-data:
Step 5: Configure Nginx for WordPress
Create nginx/conf.d/wordpress.conf:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
root /var/www/wordpress;
index index.php index.html;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/wordpress/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/wordpress/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
# Upload size limit (important for WordPress media)
client_max_body_size 64M;
# WordPress permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP handling via WordPress container
location ~ .php$ {
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 300;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
# Block access to sensitive files
location ~ /. {
deny all;
}
location ~* /(?:uploads|files)/.*.php$ {
deny all;
}
location = /wp-config.php {
deny all;
}
# Cache static assets
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Block xmlrpc.php (common attack vector)
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
}
Step 6: Configure Nginx for n8n
Create nginx/conf.d/n8n.conf:
server {
listen 80;
server_name n8n.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name n8n.example.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/n8n/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/n8n/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Proxy to n8n
location / {
proxy_pass http://n8n:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_buffering off;
chunked_transfer_encoding off;
}
# Webhook endpoints (important for n8n)
location /webhook/ {
proxy_pass http://n8n:5678/webhook/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
location /webhook-test/ {
proxy_pass http://n8n:5678/webhook-test/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
}
Step 7: Obtain SSL Certificates
Stop any services using port 80 and get certificates:
# Install certbot
sudo apt install certbot -y
# Get certificate for WordPress domain
sudo certbot certonly --standalone -d example.com -d www.example.com
# Get certificate for n8n domain
sudo certbot certonly --standalone -d n8n.example.com
# Create SSL directories
mkdir -p ~/hosting/nginx/ssl/wordpress
mkdir -p ~/hosting/nginx/ssl/n8n
# Copy certificates
sudo cp /etc/letsencrypt/live/example.com/fullchain.pem ~/hosting/nginx/ssl/wordpress/
sudo cp /etc/letsencrypt/live/example.com/privkey.pem ~/hosting/nginx/ssl/wordpress/
sudo cp /etc/letsencrypt/live/n8n.example.com/fullchain.pem ~/hosting/nginx/ssl/n8n/
sudo cp /etc/letsencrypt/live/n8n.example.com/privkey.pem ~/hosting/nginx/ssl/n8n/
# Fix permissions
sudo chown -R $USER:$USER ~/hosting/nginx/ssl/
Automatic Certificate Renewal
Create renewal script ~/hosting/renew-certs.sh:
#!/bin/bash
certbot renew --quiet
# Copy renewed certificates
cp /etc/letsencrypt/live/example.com/fullchain.pem /home/$USER/hosting/nginx/ssl/wordpress/
cp /etc/letsencrypt/live/example.com/privkey.pem /home/$USER/hosting/nginx/ssl/wordpress/
cp /etc/letsencrypt/live/n8n.example.com/fullchain.pem /home/$USER/hosting/nginx/ssl/n8n/
cp /etc/letsencrypt/live/n8n.example.com/privkey.pem /home/$USER/hosting/nginx/ssl/n8n/
# Reload nginx
docker exec nginx nginx -s reload
chmod +x ~/hosting/renew-certs.sh
# Add to crontab (runs twice daily)
(crontab -l 2>/dev/null; echo "0 3,15 * * * /home/$USER/hosting/renew-certs.sh") | crontab -
Step 8: Launch Your Stack
cd ~/hosting
# Start all services
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f
Expected output:
NAME SERVICE STATUS PORTS
mysql mysql running 3306/tcp
nginx nginx running 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
n8n n8n running 5678/tcp
redis redis running 6379/tcp
wordpress wordpress running 9000/tcp
Step 9: Complete WordPress Setup
Visit https://example.com and complete the WordPress installation wizard:
- Select your language
- Enter site title, admin username, and password
- Enter your email address
- Click "Install WordPress"
Recommended WordPress Plugins
Install these essential plugins:
- Redis Object Cache: Connect to your Redis container for faster caching
- Wordfence Security: Firewall and malware scanner
- UpdraftPlus: Automated backups to cloud storage
- WP Super Cache or W3 Total Cache: Page caching
- Yoast SEO: Search engine optimization
Configure Redis Object Cache
Add to wp-config.php (before "That's all, stop editing!"):
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
Then install and activate the "Redis Object Cache" plugin and enable it.
Step 10: Access n8n
Visit https://n8n.example.com and log in with your credentials from the .env file.
First Workflow: WordPress to n8n Integration
Create a workflow that monitors your WordPress site:
- Add Webhook trigger node
- Copy the webhook URL
- In WordPress, install the WP Webhooks plugin
- Configure it to send new post notifications to your n8n webhook
- Add actions: email notification, Slack message, social media post
Popular n8n Use Cases
- Content Publishing: Auto-post WordPress content to social media
- Lead Management: Send form submissions to CRM
- Backup Notifications: Alert when backups complete or fail
- Uptime Monitoring: Check WordPress health and alert on issues
- Comment Moderation: Auto-approve or flag comments with AI
- Analytics Reports: Weekly traffic summaries via email
Backup Strategy
Create ~/hosting/backup.sh:
#!/bin/bash
BACKUP_DIR="/home/$USER/hosting/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup MySQL
docker exec mysql mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD}" --all-databases > $BACKUP_DIR/mysql_$DATE.sql
# Backup WordPress files
tar -czf $BACKUP_DIR/wordpress_$DATE.tar.gz -C /home/$USER/hosting/wordpress .
# Backup n8n data
tar -czf $BACKUP_DIR/n8n_$DATE.tar.gz -C /home/$USER/hosting/n8n/data .
# Delete backups older than 7 days
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
echo "Backup completed: $DATE"
chmod +x ~/hosting/backup.sh
# Schedule daily backups at 2 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /home/$USER/hosting/backup.sh") | crontab -
Performance Optimization
WordPress Optimization
Add to wp-config.php:
// Increase memory limit
define('WP_MEMORY_LIMIT', '256M');
// Reduce post revisions
define('WP_POST_REVISIONS', 5);
// Disable file editing in admin
define('DISALLOW_FILE_EDIT', true);
// Auto-save interval (seconds)
define('AUTOSAVE_INTERVAL', 120);
PHP Optimization
Create php-custom.ini and mount in WordPress container:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
Monitoring & Health Checks
Container Health
# Check all container status
docker compose ps
# View resource usage
docker stats
# Check specific container logs
docker compose logs -f wordpress
docker compose logs -f n8n
Simple Uptime Monitoring with n8n
Create a workflow in n8n:
- Add Schedule Trigger (every 5 minutes)
- Add HTTP Request node to check https://example.com
- Add IF node to check response status
- Add Email or Slack node for alerts
Security Hardening
Firewall Setup
# Enable UFW
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
Fail2Ban for WordPress
sudo apt install fail2ban -y
# Create WordPress jail
sudo nano /etc/fail2ban/jail.d/wordpress.conf
Add:
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
Troubleshooting Common Issues
| Issue | Cause | Solution |
|---|---|---|
| WordPress white screen | PHP memory limit | Increase WP_MEMORY_LIMIT in wp-config.php |
| n8n webhooks not working | WEBHOOK_URL misconfigured | Verify WEBHOOK_URL in .env matches your domain |
| SSL certificate errors | Certificate path wrong | Check nginx ssl paths and file permissions |
| Database connection refused | MySQL not ready | Wait for MySQL health check to pass |
| Uploads failing | client_max_body_size too low | Increase in nginx.conf (default 64M) |
| n8n losing workflows | Volume not persisted | Verify n8n/data volume mount |
Cost Comparison: Self-Hosted vs Managed
| Service | Managed Cost | Self-Hosted (DanubeData VPS) | Savings |
|---|---|---|---|
| WordPress Hosting | $30-100/month (WP Engine, Kinsta) | Included | $360-1200/year |
| n8n Cloud Pro | $20-50/month | Included | $240-600/year |
| DanubeData VPS (2 vCPU, 4GB) | N/A | €8.99/month | ~€108/year |
| Total Annual Cost | $600-1,800/year | ~€108/year | Save $500-1,700/year |
Why DanubeData VPS for WordPress & n8n?
| Feature | DanubeData VPS | DigitalOcean | Linode |
|---|---|---|---|
| 2 vCPU, 4GB RAM | €8.99/mo | $24/mo | $24/mo |
| Storage | 80GB NVMe | 80GB SSD | 80GB SSD |
| Bandwidth | 20TB | 4TB | 4TB |
| Location | Germany (GDPR) | Global | Global |
| Setup Time | < 60 seconds | ~2 minutes | ~2 minutes |
| Snapshots | Included | $2.40/mo | $2.50/mo |
Get Started Now
- Create your DanubeData account
- Deploy a VPS with Ubuntu 24.04 (2 vCPU, 4GB recommended)
- Point your domains to the VPS IP
- Follow this guide to install WordPress and n8n
- Optional: Add managed MySQL and Redis for even better performance
Perfect for: Bloggers, small businesses, agencies, developers, and anyone who wants full control over their web presence and automation workflows.
Questions about self-hosting WordPress or n8n? Contact our team or explore our documentation for more guides.