WordPress powers over 40% of all websites on the internet, yet most people overpay for hosting. Managed WordPress plans from providers like WP Engine, Kinsta, or SiteGround charge $25-100+ per month for resources you can get for a fraction of the price on a VPS. In this guide, you'll set up a production-ready WordPress site on a DanubeData DD Nano VPS—2 dedicated AMD Zen4 vCPUs, 2GB DDR5 RAM, and 40GB NVMe SSD—for just €4.49/month.
Why Host WordPress on a VPS?
- Full Root Access: Install any PHP extension, caching layer, or server software you need—no host restrictions
- Dedicated Resources: No noisy neighbors slowing your site during traffic spikes
- Cost Savings: A DD Nano VPS at €4.49/month replaces $25-100/month managed hosting
- GDPR Compliance: Your data stays in Germany on European infrastructure
- Performance: NVMe storage and DDR5 memory deliver fast page loads
- Scalability: Upgrade to a larger plan with one click when you outgrow your current setup
What You'll Build
By the end of this guide, you'll have a production-ready WordPress site with:
- A lean LEMP stack (Linux, Nginx, MariaDB, PHP 8.3) optimized for 2GB RAM
- Free SSL/TLS certificates via Let's Encrypt with automatic renewal
- FastCGI caching for sub-second page loads without plugins
- Gzip and Brotli compression for smaller page sizes
- Security hardening with a firewall, Fail2Ban, and secure headers
- Automated daily backups
Prerequisites
- A DanubeData account
- A domain name with DNS access (e.g.,
example.com) - Basic familiarity with the Linux command line
Step 1: Create Your DD Nano VPS
- Log in to the DanubeData dashboard
- Click Create VPS
- Select the DD Nano plan (2 vCPU, 2GB RAM, 40GB NVMe)
- Choose Ubuntu 24.04 LTS as the operating system
- Add your SSH public key for secure access
- Click Create—your VPS will be ready in under 60 seconds
Once the VPS is running, note the IPv4 address from your dashboard. Point your domain's DNS A record to this IP:
# DNS records to configure at your registrar
example.com A YOUR_VPS_IP
www.example.com A YOUR_VPS_IP
Allow DNS propagation before continuing. You can verify with:
dig +short example.com
Step 2: Initial Server Setup
SSH into your VPS and secure the base system:
# Connect to your VPS
ssh root@YOUR_VPS_IP
# Update system packages
apt update && apt upgrade -y
# Create a non-root user
adduser deploy
usermod -aG sudo deploy
# Copy SSH key to the new user
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
# Configure firewall
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
# Switch to new user for remaining steps
su - deploy
Harden SSH Access
Edit the SSH configuration to disable password authentication:
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
Step 3: Install the LEMP Stack
We'll use MariaDB instead of MySQL—it uses less memory and performs better on constrained systems.
Install Nginx
sudo apt install nginx -y
sudo systemctl enable nginx
Install MariaDB
sudo apt install mariadb-server -y
sudo systemctl enable mariadb
# Secure the installation
sudo mysql_secure_installation
During the secure installation wizard:
- Set a strong root password
- Remove anonymous users: Y
- Disallow root login remotely: Y
- Remove test database: Y
- Reload privilege tables: Y
Create the WordPress Database
sudo mysql -u root -p
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Generate a strong password with openssl rand -base64 24 and save it somewhere safe.
Install PHP 8.3
sudo apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd
php8.3-intl php8.3-mbstring php8.3-xml php8.3-zip
php8.3-imagick php8.3-bcmath php8.3-opcache -y
Step 4: Optimize PHP for 2GB RAM
With 2GB of RAM, every megabyte counts. Tune PHP-FPM to use memory efficiently:
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
Find and update the following values:
; Use static process management for predictable memory usage
pm = static
pm.max_children = 4
pm.max_requests = 500
; Match the user Nginx runs as
user = www-data
group = www-data
Why these values? Each PHP-FPM worker uses roughly 40-60MB of RAM. With 4 workers, PHP uses around 200MB, leaving plenty of room for Nginx, MariaDB, and the OS. If your site has very light traffic, you can reduce pm.max_children to 3.
Next, tune the PHP configuration:
sudo nano /etc/php/8.3/fpm/php.ini
Update these settings:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 128M
max_execution_time = 300
max_input_vars = 3000
; OPcache settings - critical for performance
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 60
opcache.validate_timestamps = 1
Restart PHP-FPM:
sudo systemctl restart php8.3-fpm
Step 5: Optimize MariaDB for 2GB RAM
Create a custom MariaDB configuration tuned for a single WordPress site on limited memory:
sudo nano /etc/mysql/mariadb.conf.d/99-wordpress.cnf
[mysqld]
# InnoDB settings
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# Query cache (useful for WordPress read-heavy workload)
query_cache_type = 1
query_cache_size = 32M
query_cache_limit = 2M
# Connection limits
max_connections = 50
table_open_cache = 400
# Temp tables
tmp_table_size = 32M
max_heap_table_size = 32M
# Slow query log (enable to find bottlenecks)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
sudo systemctl restart mariadb
Step 6: Download and Configure WordPress
# Download WordPress
cd /tmp
curl -LO https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
# Move to web root
sudo mv wordpress /var/www/wordpress
sudo chown -R www-data:www-data /var/www/wordpress
sudo chmod -R 755 /var/www/wordpress
# Create wp-config.php
cd /var/www/wordpress
sudo cp wp-config-sample.php wp-config.php
Edit the configuration with your database credentials:
sudo nano /var/www/wordpress/wp-config.php
Update the database settings:
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'YOUR_STRONG_PASSWORD_HERE' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', 'utf8mb4_unicode_ci' );
Generate unique authentication keys at api.wordpress.org/secret-key and replace the placeholder lines in wp-config.php.
Add these performance and security options before the line /* That's all, stop editing! */:
/** Limit post revisions to save database space */
define( 'WP_POST_REVISIONS', 5 );
/** Increase memory available to WordPress */
define( 'WP_MEMORY_LIMIT', '128M' );
/** Disable the built-in file editor for security */
define( 'DISALLOW_FILE_EDIT', true );
/** Auto-save interval in seconds */
define( 'AUTOSAVE_INTERVAL', 120 );
/** Force HTTPS for admin and logins */
define( 'FORCE_SSL_ADMIN', true );
Step 7: Configure Nginx with FastCGI Caching
FastCGI caching serves pages directly from disk without hitting PHP at all. This is the single biggest performance optimization for WordPress on a small VPS—cached pages load in under 50ms.
sudo nano /etc/nginx/sites-available/wordpress
# FastCGI cache configuration
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:10m inactive=60m max_size=512m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Redirect www to non-www
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/wordpress;
index index.php index.html;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/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;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
# 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;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
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
client_max_body_size 64M;
# FastCGI cache bypass rules
set $skip_cache 0;
# Don't cache logged-in users
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
# Don't cache POST requests or URLs with query strings
if ($request_method = POST) {
set $skip_cache 1;
}
if ($query_string != "") {
set $skip_cache 1;
}
# Don't cache admin or login pages
if ($request_uri ~* "/wp-admin/|/wp-login.php|/xmlrpc.php|wp-.*.php") {
set $skip_cache 1;
}
# WordPress permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP handling with FastCGI caching
location ~ .php$ {
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
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;
# FastCGI cache
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache-Status $upstream_cache_status;
}
# Block access to sensitive files
location ~ /. {
deny all;
}
location ~* /(?:uploads|files)/.*.php$ {
deny all;
}
location = /wp-config.php {
deny all;
}
# Block XML-RPC (common attack vector)
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
# Cache static assets for 1 year
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp|avif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Deny access to sensitive WordPress files
location ~* /(wp-config.php|readme.html|license.txt) {
deny all;
}
}
Enable the site and create the cache directory:
# Remove default site
sudo rm /etc/nginx/sites-enabled/default
# Enable WordPress site
sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
# Create cache directory
sudo mkdir -p /var/cache/nginx/wordpress
sudo chown www-data:www-data /var/cache/nginx/wordpress
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Step 8: Set Up Free SSL with Let's Encrypt
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Obtain certificate (Nginx must be running on port 80 first)
sudo certbot --nginx -d example.com -d www.example.com
# Verify auto-renewal is active
sudo certbot renew --dry-run
Certbot automatically configures a systemd timer that renews certificates before they expire. No cron jobs needed.
Step 9: Complete the WordPress Installation
Open https://example.com in your browser. You'll see the WordPress installation wizard:
- Select your language
- Enter a site title, admin username, and a strong password
- Enter your email address
- Click Install WordPress
Log in at https://example.com/wp-admin to access the dashboard.
Recommended Plugins
Keep your plugin count low to conserve memory. These are the essentials:
- Nginx Helper: Purges the FastCGI cache when you publish or update content
- Wordfence Security: Firewall and login protection
- UpdraftPlus: Scheduled backups to cloud storage
- Yoast SEO or Rank Math: Search engine optimization
- ShortPixel or Imagify: Image compression to save storage
Important: You do not need a caching plugin like WP Super Cache or W3 Total Cache. Nginx FastCGI caching handles this at the server level, which is faster and uses less memory than any PHP-based caching plugin.
Step 10: Set Up Automated Backups
Create a backup script that saves your database and WordPress files daily:
sudo nano /home/deploy/backup-wordpress.sh
#!/bin/bash
BACKUP_DIR="/home/deploy/backups"
DATE=$(date +%Y%m%d)
RETENTION_DAYS=14
mkdir -p "$BACKUP_DIR"
# Backup database
mariadb-dump -u wpuser -p'YOUR_STRONG_PASSWORD_HERE' wordpress | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Backup WordPress files (excluding cache)
tar -czf "$BACKUP_DIR/wp_files_$DATE.tar.gz"
--exclude='/var/www/wordpress/wp-content/cache'
/var/www/wordpress/wp-content/
# Remove old backups
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
echo "[$(date)] Backup completed: db_$DATE.sql.gz, wp_files_$DATE.tar.gz"
chmod +x /home/deploy/backup-wordpress.sh
# Schedule daily backups at 3 AM
(crontab -l 2>/dev/null; echo "0 3 * * * /home/deploy/backup-wordpress.sh >> /home/deploy/backups/backup.log 2>&1") | crontab -
Tip: For off-site backup redundancy, configure UpdraftPlus to send backups to DanubeData Object Storage (S3-compatible, €3.99/month with 1TB included).
Step 11: Security Hardening
Install Fail2Ban
Protect against brute-force login attempts:
sudo apt install fail2ban -y
sudo nano /etc/fail2ban/jail.d/wordpress.conf
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
findtime = 300
Create the filter:
sudo nano /etc/fail2ban/filter.d/wordpress-login.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
ignoreregex =
sudo systemctl restart fail2ban
Enable Automatic Security Updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
Step 12: Set Up Swap Space
A small swap file acts as a safety net during occasional memory spikes, preventing the Linux OOM killer from terminating your services:
# Create a 1GB swap file
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Optimize swappiness (low value = prefer RAM)
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Memory Usage Breakdown
Here's how the 2GB of RAM is allocated across the stack:
| Service | RAM Usage | Notes |
|---|---|---|
| Ubuntu 24.04 OS | ~150 MB | Base system with systemd |
| Nginx | ~30 MB | Lightweight with worker_processes auto |
| MariaDB (InnoDB) | ~350 MB | 256MB buffer pool + overhead |
| PHP-FPM (4 workers) | ~200 MB | ~50MB per worker |
| OPcache | ~128 MB | Shared across all PHP workers |
| Buffers/Cache | ~150 MB | Linux disk cache (freed when needed) |
| Total | ~1,008 MB | ~1GB free + 1GB swap safety net |
This leaves plenty of headroom for traffic spikes. The FastCGI cache means most visitors are served static files directly by Nginx, so PHP-FPM and MariaDB stay idle under normal load.
Performance Benchmarks
Here's what you can expect from this setup on a DD Nano VPS:
| Metric | Cached Pages | Uncached Pages |
|---|---|---|
| Time to First Byte (TTFB) | < 30ms | 150-300ms |
| Requests/second | 1,000+ | 30-50 |
| Concurrent Users | 500+ | ~20 |
| Monthly Visitors Supported | 100,000+ | ~10,000 |
With FastCGI caching enabled, a DD Nano VPS comfortably handles 100,000+ monthly visitors for a typical blog or business website.
Cost Comparison: DanubeData vs Managed WordPress Hosting
| Provider | Plan | Monthly Cost | Storage | Bandwidth | Visitors |
|---|---|---|---|---|---|
| DanubeData DD Nano | 2 vCPU, 2GB RAM | €4.49/mo | 40GB NVMe | 20TB | 100,000+ |
| WP Engine | Startup | $30/mo | 10GB | 50GB | 25,000 |
| Kinsta | Single 35k | $35/mo | 10GB | 100GB CDN | 35,000 |
| SiteGround | GrowBig | $25/mo | 20GB | Unmetered* | ~25,000 |
| Cloudways | 2GB | $28/mo | 50GB | 2TB | ~30,000 |
At €4.49/month, the DanubeData DD Nano VPS costs 5-8x less than managed WordPress hosting while delivering more storage, more bandwidth, and better performance. Over a year, that's a saving of €250-350.
When to Upgrade
The DD Nano plan handles most WordPress sites with ease. Consider upgrading to the DD Micro (3 vCPU, 4GB RAM, 80GB NVMe, €7.49/month) if you:
- Run WooCommerce with a large product catalog
- Need to host multiple WordPress sites on one server
- Use memory-heavy plugins (page builders, complex forms)
- Want to add Redis object caching for dynamic-heavy sites
- Consistently see traffic above 200,000 monthly visitors
Upgrading on DanubeData is seamless—your data and IP address stay the same.
Quick Reference: Useful Commands
# Check memory usage
free -h
# Monitor resource usage in real time
htop
# View Nginx error log
sudo tail -f /var/log/nginx/error.log
# View PHP-FPM error log
sudo tail -f /var/log/php8.3-fpm.log
# Purge FastCGI cache
sudo rm -rf /var/cache/nginx/wordpress/*
# Restart all services
sudo systemctl restart nginx php8.3-fpm mariadb
# Check SSL certificate expiry
sudo certbot certificates
# Test Nginx configuration
sudo nginx -t
Get Started
- Create your DanubeData account
- Deploy a DD Nano VPS with Ubuntu 24.04
- Follow this guide to set up WordPress
- Point your domain and go live
Perfect for: Personal blogs, business websites, portfolios, small WooCommerce shops, membership sites, and anyone who wants fast, affordable WordPress hosting in Europe with GDPR compliance.
Questions about hosting WordPress on a DanubeData VPS? Contact our team or explore our documentation for more guides.