Self-hosted WordPress on a properly configured LEMP stack runs circles around shared hosting. The same site that crawls on a $5/month managed plan flies on a $5/month VPS — because you're not sharing a single Apache process with 500 other tenants. This guide walks through the full setup: Nginx, MariaDB, PHP 8.2-FPM, WordPress, SSL, caching, and the security hardening that keeps the bots out. Should take 30-45 minutes from a fresh VPS to a published site.
Prerequisites: Ubuntu 22.04 or 24.04 VPS, 2GB RAM minimum (1GB works for tiny sites with caching, 4GB recommended for anything serious), sudo user, domain pointing to the VPS IP. OliveVPS Pro at $7.99/mo sits at the sweet spot for a real production WordPress site.
Steps in this guide
Step 1: Install Nginx
sudo apt update
sudo apt install -y nginx
Open the firewall:
sudo ufw allow 'Nginx Full'
Full Nginx setup details are in our Nginx + Let's Encrypt guide if you want background.
Step 2: Install MariaDB and create the database
MariaDB is the open-source MySQL fork. Drop-in compatible, slightly faster, no Oracle.
sudo apt install -y mariadb-server
sudo mysql_secure_installation
The mysql_secure_installation wizard will ask several questions:
- Switch to unix_socket authentication: n (we'll use a dedicated user)
- Set root password: n (leave socket auth, it's safer)
- Remove anonymous users: y
- Disallow root login remotely: y
- Remove test database: y
- Reload privilege tables: y
Now create the WordPress database and user:
sudo mariadb
In the MariaDB prompt:
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'CHANGE_THIS_TO_A_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Pick a real password — at least 24 random characters. Save it somewhere (preferably your self-hosted Bitwarden).
Step 3: Install PHP 8.2-FPM with required extensions
sudo apt install -y php-fpm php-mysql php-curl php-gd php-mbstring \
php-xml php-xmlrpc php-soap php-intl php-zip php-imagick
Verify the version Ubuntu installed:
php -v
On 22.04 you'll get PHP 8.1; on 24.04 you'll get 8.3. Both are fine. The service name is versioned — find yours:
systemctl list-units 'php*-fpm*'
You'll see something like php8.1-fpm.service or php8.3-fpm.service. Note the version for the Nginx config below.
Step 4: Configure Nginx for WordPress
Create the document root:
sudo mkdir -p /var/www/yourdomain.com
sudo chown -R www-data:www-data /var/www/yourdomain.com
Create the Nginx config /etc/nginx/sites-available/yourdomain.com:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.php index.html;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock; # adjust to your version
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
# Block xmlrpc.php — most attacks target this
location = /xmlrpc.php {
deny all;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
}
Adjust the fastcgi_pass line to match your installed PHP version (e.g. php8.3-fpm.sock).
Enable, test, reload:
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Step 5: Download and configure WordPress
cd /tmp
curl -O https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
sudo cp -a wordpress/. /var/www/yourdomain.com/
sudo chown -R www-data:www-data /var/www/yourdomain.com
Generate the wp-config.php from the sample:
cd /var/www/yourdomain.com
sudo cp wp-config-sample.php wp-config.php
sudo nano wp-config.php
Edit the database section:
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'YOUR_DATABASE_PASSWORD' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
Generate fresh authentication keys at api.wordpress.org/secret-key/1.1/salt and replace the corresponding placeholder lines in wp-config.php.
Now visit http://yourdomain.com/wp-admin/install.php and walk through the WordPress installer. Pick a username that's NOT "admin" — that's the username every brute-force bot tries first.
Step 6: Install SSL with Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Update WordPress to know it's now HTTPS. In the WP admin → Settings → General, change both URLs to https://. Or add to wp-config.php:
define( 'WP_HOME', 'https://yourdomain.com' );
define( 'WP_SITEURL', 'https://yourdomain.com' );
define( 'FORCE_SSL_ADMIN', true );
// Trust proxy headers from Cloudflare/load balancers
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
$_SERVER['HTTPS'] = 'on';
}
Step 7: Performance: page caching with FastCGI cache
WordPress is slow by default because every request runs PHP that runs database queries that run PHP that builds HTML. Page caching skips all of that — Nginx serves cached HTML directly to anonymous visitors and only invokes PHP for logged-in users.
Add to the top of your Nginx config (above the server block):
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
Inside the server block, before the location ~ \.php$ block, add:
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $skip_cache 1; }
Inside the location ~ \.php$ block, add these lines:
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
Test and reload:
sudo nginx -t && sudo systemctl reload nginx
Verify caching is working — visit a page in incognito mode, view headers (browser dev tools → Network), look for an X-Cache-Status header you can add. Or load test with ab -n 1000 -c 10 https://yourdomain.com/ — you should see thousands of req/sec instead of dozens.
Install the Nginx Helper WordPress plugin to automatically purge the cache when you publish or edit content.
WordPress that actually purrs
NVMe storage so DB queries are instant, dedicated CPU so plugins don't slow you down, free daily backups so you sleep at night. Starting at $7.99/mo for the Pro plan.
See VPS Plans →Security hardening
WordPress is the most attacked CMS on the internet. Bare minimum hardening:
Disable file editing in admin
Add to wp-config.php:
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );
Limit login attempts
Install a plugin like Limit Login Attempts Reloaded. Bots will hammer /wp-login.php within hours of your site appearing online — this stops them.
Use 2FA
Install a 2FA plugin (Two Factor from the WordPress core team is solid). Even if your password leaks, attackers can't get in.
Tighten file permissions
cd /var/www/yourdomain.com
sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;
sudo chmod 600 wp-config.php
Block xmlrpc.php
Already in the Nginx config above. xmlrpc.php is rarely needed and is the target of countless brute-force attacks.
Keep things updated
Enable auto-updates for plugins and core (Settings → General). The most common WordPress compromises are old plugins with known vulnerabilities.
FAQ
Can WordPress run on a 1GB VPS?
Yes — small sites with FastCGI page caching run fine on 1GB. Without caching, you'll start swapping at 50-100 concurrent visitors and the site will get slow. Caching is non-negotiable on 1GB plans. For comfortable headroom, 2GB is the sweet spot.
Should I use Apache or Nginx for WordPress?
Nginx wins for performance, especially with FastCGI page caching. Apache with mod_php is slightly easier to configure but consumes more RAM per request. For new WordPress installs, Nginx is the modern choice.
How does this compare to managed WordPress hosting?
Managed hosting handles updates, backups, and caching for you, at $25-100/month. Self-hosted on a VPS costs $5-15/month and runs faster (because you're not on shared infrastructure), but you're responsible for updates and backups. The trade-off is your time vs your money.
What about object caching with Redis?
For sites with significant logged-in user activity (membership, ecommerce, BuddyPress), Redis object caching is a major win. Install redis-server and the Redis Object Cache plugin. For simple content sites, FastCGI page caching is enough.
Will a VPS handle 100k monthly visitors?
Comfortably, with proper caching. 100k monthly = roughly 2-3 concurrent visitors on average, with peaks of 20-50. A 2GB VPS with FastCGI caching handles 200+ requests per second cached, which is enough for sites in the 500k-1M monthly range.