BlogTutorialsWordPress & n8n: Self-Host Your Website and Automation on VPS

WordPress & n8n: Self-Host Your Website and Automation on VPS

Adrian Silaghi
Adrian Silaghi
December 19, 2025
15 min read
104 views
#wordpress #n8n #vps #self-hosting #docker #automation #cms

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.com or automation.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:

  1. Select your language
  2. Enter site title, admin username, and password
  3. Enter your email address
  4. 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:

  1. Add Webhook trigger node
  2. Copy the webhook URL
  3. In WordPress, install the WP Webhooks plugin
  4. Configure it to send new post notifications to your n8n webhook
  5. 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:

  1. Add Schedule Trigger (every 5 minutes)
  2. Add HTTP Request node to check https://example.com
  3. Add IF node to check response status
  4. 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

  1. Create your DanubeData account
  2. Deploy a VPS with Ubuntu 24.04 (2 vCPU, 4GB recommended)
  3. Point your domains to the VPS IP
  4. Follow this guide to install WordPress and n8n
  5. 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.

Share this article

Ready to Get Started?

Deploy your infrastructure in minutes with DanubeData's managed services.